doctum: PHAR fails when relative cache || build path is used

With 5.2.1, the phar version fails to create directories within cache or build path, when relative cache/build paths are used in config. Here is an example run:

ptomulik@barakus:$ php bin/doctum.phar update --force -vvv docs/doctum-relative.conf.php 
 Updating project 

Version main
-------------


In Filesystem.php line 105:
                                                            
  [Symfony\Component\Filesystem\Exception\IOException]      
  Failed to create "docs/cache/html": mkdir(): File exists  
                                                            

Exception trace:
  at phar:///tmp/test/bin/doctum.phar/vendor/symfony/filesystem/Filesystem.php:105
 Symfony\Component\Filesystem\Filesystem->mkdir() at phar:///tmp/test/bin/doctum.phar/src/Project.php:380
 Doctum\Project->flushDir() at phar:///tmp/test/bin/doctum.phar/src/Project.php:431
 Doctum\Project->prepareDir() at phar:///tmp/test/bin/doctum.phar/src/Project.php:374
 Doctum\Project->getCacheDir() at phar:///tmp/test/bin/doctum.phar/src/Store/JsonStore.php:76
 Doctum\Store\JsonStore->getStoreDir() at phar:///tmp/test/bin/doctum.phar/src/Store/JsonStore.php:64
 Doctum\Store\JsonStore->flushProject() at phar:///tmp/test/bin/doctum.phar/src/Project.php:464
 Doctum\Project->parseVersion() at phar:///tmp/test/bin/doctum.phar/src/Project.php:125
 Doctum\Project->update() at phar:///tmp/test/bin/doctum.phar/src/Console/Command/Command.php:183
 Doctum\Console\Command\Command->update() at phar:///tmp/test/bin/doctum.phar/src/Console/Command/UpdateCommand.php:54
 Doctum\Console\Command\UpdateCommand->execute() at phar:///tmp/test/bin/doctum.phar/vendor/symfony/console/Command/Command.php:255
 Symfony\Component\Console\Command\Command->run() at phar:///tmp/test/bin/doctum.phar/vendor/symfony/console/Application.php:1009
 Symfony\Component\Console\Application->doRunCommand() at phar:///tmp/test/bin/doctum.phar/vendor/symfony/console/Application.php:273
 Symfony\Component\Console\Application->doRun() at phar:///tmp/test/bin/doctum.phar/vendor/symfony/console/Application.php:149
 Symfony\Component\Console\Application->run() at phar:///tmp/test/bin/doctum.phar/bin/doctum-binary.php:26
 include() at /tmp/test/bin/doctum.phar:9

update [--only-version ONLY-VERSION] [--force] [--output-format OUTPUT-FORMAT] [--no-progress] [--ignore-parse-errors] [--] <config>

The problem does not appear with non-phar version. Absolute paths work well.

I attach an archive containing a minimal example. The issue looks very strange and may be related to Symfony\Filesystem.

test.tar.gz

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 25 (25 by maintainers)

Commits related to this issue

Most upvoted comments

I released the 5.3.1-dev phar

Is this related: https://stackoverflow.com/a/48015791/5155484 ?

Yes, this is related. Seems that #22 does the job finally. If interceptFileFuncs() is called, the file functions look for relative files within phar:// filesystem (but we want it to work on real filesystem). The default stub, doctum used previosuly, called interceptFileFuncs() internally as stated in docs for createDefaultStub. Now, after removing the line with interceptFileFuncs() everything seems to work as expected.

I still can’t wrap my head around how did I came to completelly opposite conclusions previously (mean, that interceptFileFuncs() should be presend, while it appears, it should be actually absent).

Seems I messed up things. It’s something about Phar::interceptFileFuncs() but I wonder why, during my previous experiments I found I need it for is_dir() to work. Now it looks completely opposite - is_dir() doesn’t work with relative paths when interceptFileFuncs() is present.

Downloaded sami.phar and it works well with relative paths.

The issue is related to how certain filesystem-related functions work in phars, in this case is_dir() does not work as we expect. The interceptFileFuncs() should do the job, but it looks like it didn’t. Maybe we should take a closer look, how and where it should be placed.

Looks like it’s related to interceptFileFuncs(). If I add Phar::interceptFileFuncs() at the beginning of my bin/test.php, then it works as expected (checking for files in real filesystem). EDIT: looks it’s completely opposite (we need to remove all occurrences of interceptFileFuncs()).

#!/usr/bin/env php
<?php
Phar::interceptFileFuncs();

// ...

Another solution, I found, was to use box to create PHAR. Sami seemed to use box. For my bin/test.php I’ve used the following box.json configuration for box

{
  "output": "build/test.phar",
  "compactors": [
    "KevinGH\\Box\\Compactor\\Php"
  ],
  "main": "bin/test.php"
}

and just executed box compile. I guess, box prepends the Phar::interceptFileFuncs() call by default.

I investigated it further, with the following script (call it bin/test.php)

#!/usr/bin/env php
<?php

if (count($argv) > 1) {
    printf("file_exists(%s): %s\n", $argv[1], file_exists($argv[1]) ? 'true' : 'false');
    printf("is_dir(%s): %s\n", $argv[1], is_dir($argv[1]) ? 'true' : 'false');
}

First ran it normally providing relative name of an existing directory:

ptomulik@barakus:$ bin/test.php foo/
is_file(foo/): false
is_dir(foo/): true

then made a phar out of bin/test.php using your phar-generator-script.sh (with slight modification to use bin/test.php instead of bin/doctum.php). Running the phar version results with

ptomulik@barakus:$ php build/test.phar foo/
#!/usr/bin/env php
file_exists(foo/): false
is_dir(foo/): false

It looks, like file_exists() & is_dir() do not work well with relative paths from phar.

Some additional details:

ptomulik@barakus:$ php --version
PHP 7.4.11 (cli) (built: Oct  6 2020 10:34:39) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.11, Copyright (c), by Zend Technologies
    with Xdebug v2.9.6, Copyright (c) 2002-2020, by Derick Rethans

The same happens with php 7.3.

The issue looks quite strange to me. The exception appears to be thrown from within symfony, from mkdir() function (not sure, this is exactly same version of symfony/filesystem you use to create phar). The check in line 92 should prevent the “File exists” exception, except there exists a file with the same name as $dir (and it’s not a directory).

Hi!

Did this work before? With 5.0.0 Phar?

No.

ptomulik@barakus:$ php bin/doctum-5.0.0.phar update --force -vvv docs/doctum-relative.conf.php 
#!/usr/bin/env php
 Updating project 

Version main

In Filesystem.php line 100:
                                                            
  [Symfony\Component\Filesystem\Exception\IOException]      
  Failed to create "docs/cache/html": mkdir(): File exists  
                                                            

Exception trace:
  at phar:///tmp/test/bin/doctum-5.0.0.phar/vendor/symfony/filesystem/Filesystem.php:100
 Symfony\Component\Filesystem\Filesystem->mkdir() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Project.php:343
 Doctum\Project->flushDir() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Project.php:394
 Doctum\Project->prepareDir() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Project.php:337
 Doctum\Project->getCacheDir() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Store/JsonStore.php:76
 Doctum\Store\JsonStore->getStoreDir() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Store/JsonStore.php:64
 Doctum\Store\JsonStore->flushProject() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Project.php:427
 Doctum\Project->parseVersion() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Project.php:108
 Doctum\Project->update() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Console/Command/Command.php:79
 Doctum\Console\Command\Command->update() at phar:///tmp/test/bin/doctum-5.0.0.phar/src/Console/Command/UpdateCommand.php:53
 Doctum\Console\Command\UpdateCommand->execute() at phar:///tmp/test/bin/doctum-5.0.0.phar/vendor/symfony/console/Command/Command.php:258
 Symfony\Component\Console\Command\Command->run() at phar:///tmp/test/bin/doctum-5.0.0.phar/vendor/symfony/console/Application.php:911
 Symfony\Component\Console\Application->doRunCommand() at phar:///tmp/test/bin/doctum-5.0.0.phar/vendor/symfony/console/Application.php:264
 Symfony\Component\Console\Application->doRun() at phar:///tmp/test/bin/doctum-5.0.0.phar/vendor/symfony/console/Application.php:140
 Symfony\Component\Console\Application->run() at phar:///tmp/test/bin/doctum-5.0.0.phar/bin/doctum.php:15
 include() at /tmp/test/bin/doctum-5.0.0.phar:9

update [--only-version ONLY-VERSION] [--force] [--] <config>

The config within the minimal example is the following

<?php

use Doctum\Doctum;
use Symfony\Component\Finder\Finder;

$iterator = Finder::create()
  ->files()
  ->name("*.php")
  ->in(['src']);

return new Doctum($iterator, [
  'theme'     => 'default',
  'title'     => 'API Documentation',
  'build_dir' => 'docs/build/html',
  'cache_dir' => 'docs/cache/html',
]);