[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird behaviour when a file is autoloaded in assignment of a constant #10232

Closed
fasterforward opened this issue Jan 5, 2023 · 6 comments
Closed

Comments

@fasterforward
Copy link
fasterforward commented Jan 5, 2023

Description

The following files:

test.php

<?php

error_reporting(E_ALL);
ini_set('display_errors', 1);

class Loader
{
    public function __construct()
    {
        spl_autoload_register([$this, 'include'], true);
    }
    public function include()
    {
        require 'test/constant.php';
    }
}

new Loader();
class B
{
    public const A = constant::TEST;
}

new B();
echo 'here';

test/constant.php

<?php

declare(strict_types=1);
require_once 'required.php';

class constant
{
    public const TEST = 'test';
}

test/required.php

<?php

Resulted in this output:

Warning: require_once(required.php): Failed to open stream: No such file or directory in /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php on line 21 Call Stack: 0.0183 365472 1. {main}() /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php:0 0.1607 397352 2. Loader->include('constant') /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php:24 0.1611 397752 3. require_once('/mnt/fasterforward/web01/elements/src/httpdocs/test/test.php') /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php:11 Fatal error: Uncaught Error: Failed opening required 'required.php' (include_path='.:/usr/share/pear:/usr/share/php:/usr/share/pear:/usr/share/php') in /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php on line 21 Error: Failed opening required 'required.php' (include_path='.:/usr/share/pear:/usr/share/php:/usr/share/pear:/usr/share/php') in /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php on line 21 Call Stack: 0.0183 365472 1. {main}() /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php:0 0.1607 397352 2. Loader->include('constant') /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php:24 0.1611 397752 3. require_once('/mnt/fasterforward/web01/elements/src/httpdocs/test/test.php') /mnt/fasterforward/web01/elements/src/httpdocs/test/test.php:11 

But I expected this output instead:

here

For some reason in when a file is autoloaded in assignment of a constant, the requires there do not get checked based on the location of that file.
When I put the autoloader outside of a class, require the file directly or specify constant as a type instead it works fine
In PHP 8.1.8 and PHP 7.4.30 with very simular php.ini's it works fine and the file gets required properly.
I thought it might be opcache but when I disable it the same problem happens.

PHP Version

PHP 8.2

Operating System

Red Hat Enterprise Linux release 8.7

@cmb69
Copy link
Member
cmb69 commented Jan 5, 2023

I don't think this could ever have worked, unless you set include_path properly. You claim that constant.php and required.php are in the same directory, but the includes don't match that structure, and there is no chdir() happening.

So, have you set include_path for the other PHP versions? If not, which SAPI do you use? Is test.php really supposed to be a level above the other files, or actually stored in the same directory? And from which directory are you running the test (or what is the docroot, respectively).

@fasterforward
Copy link
Author
fasterforward commented Jan 5, 2023

I see I made a tiny mistake in the name of the file "test/required.php" just fixed that.

But yes test.php is in the location as indicated one level above the files included. And constant.php and required.php are in the same directory.
The include_path is not modified and is '.:/usr/share/pear:/usr/share/php:/usr/share/pear:/usr/share/php' for all versions.
The SAPI is "FPM/FastCGI' for all versions as well.
The docroot is ""/mnt/fasterforward/web01/elements/src/httpdocs/" in this case, made a seperate folder for testing called ``test` but you can replicate this behaviour from the command-line as well.

Reading through the documentation and seems that you are right it seems that requiring like this is not supposed to work, but it still does mostly work and did completely work for the previous versions.
I just tested it, and this behavior is working in a brand new installation of PHP 8.2.1 for windows.

@fasterforward
Copy link
Author

Retested this bug on PHP 8.2.1. with FPM/FastCGI and Oracle Linux 8 same result

@ranvis
Copy link
Contributor
ranvis commented Jan 16, 2023

I think I have confirmed the issue on PHP 8.2.1 CLI.

From the document of include():

If the file isn't found in the include_path, include will finally check in the calling script's own directory and the current working directory before failing.

And the issue is about a misidentification of the script's directory.
What I tried follows:

 mkdir empty first second
 cat <<'END' > main.php
<?php
chdir(__DIR__ . '/empty');
set_include_path(getcwd());

spl_autoload_register(function () {
    require_once __DIR__ . '/second/SecondClass.php';
}, true);
$case = (int)$argv[1];
require_once __DIR__ . '/first/FirstClass.php';

new FirstClass();
END
 cat <<'END' > first/FirstClass.php
<?php
if ($case == 1): // fails
    class FirstClass { public const VALUE = SecondClass::VALUE; }
elseif ($case == 2): // fails
    class FirstClass { public $value = SecondClass::VALUE; }
elseif ($case == 3):
    class FirstClass { public function __construct() { SecondClass::VALUE; } }
elseif ($case == 4):
    class FirstClass {}
    SecondClass::VALUE;
endif;
END
 cat <<'END' > second/SecondClass.php
<?php
require_once 'required.php'; // should load the file in the same directory

class SecondClass { public const VALUE = 1; }
END
 cat <<'END' > required.php
<?php echo basename(__DIR__), "\n";
END
 cp required.php first/
 cp required.php second/

 php82 main.php 1  # first
 php82 main.php 2  # first
 php82 main.php 3  # second
 php82 main.php 4  # second

 php81 main.php 1  # second
 php81 main.php 2  # second
 php81 main.php 3  # second
 php81 main.php 4  # second

main.php explicitly loads FirstClass.php, which loads SecondClass.php by autoloading, and then SecondClass.php loads required.php.
require_once 'required.php' in second/SecondClass.php should load second/required.php, but first/required.php is loaded on some cases with PHP 8.2.

@ranvis
Copy link
Contributor
ranvis commented Jan 28, 2023

Since EG(filename_override) set on constant resolution always takes precedence,
require_once call on second/SecondClass.php is still resolved based on first/FirstClass.php's path.

ZEND_API zend_string *zend_get_executed_filename_ex(void) /* {{{ */
{
zend_string *filename_override = EG(filename_override);
if (filename_override != NULL) {
return filename_override;
}

I guess error reporting and path resolution should be differentiated here.

@FuZuK
Copy link
FuZuK commented Jan 26, 2024

Hi,
Yesterday faced with the same issue #13245
The behavior really changed, prior to 8.2 it works. We have a lot of code that uses it.
In doc it says that will firstly take a look in the directory of calling script and cwd (of course after include_path). But I think a lot of people are using that in this way like we did.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants