Hi Ilija,
I believe the removal of function caching is a bit of an overreaction: it was 
always known, to those who used it, that preloading will cache functions, and 
requires an include guard to avoid function redeclaration errors, and in fact 
it is a very useful feature.

There is nothing wrong with this behaviour, and it does what you expect it to 
do.

Regards,
Daniil Gentili

> On 20 Nov 2024, at 22:22, Ilija Tovilo <tovilo.il...@gmail.com> wrote:
> 
> Hi everyone
> 
> We recently received a bug report regarding the behavior of
> opcache_compile_file() [1]. The documentation specifies:
> 
> https://www.php.net/manual/en/function.opcache-compile-file.php
> 
>> This function compiles a PHP script and adds it to the opcode cache without 
>> executing it. This can be used to prime the cache after a Web server restart 
>> by pre-caching files that will be included in later requests.
> 
> Arguably, "without executing it" implies that, aside from putting the
> script into opcache, there are no other observable side-effects. This
> assumption is currently incorrect. To be more specific,
> opcache_compile_file() differs from require in two ways:
> 
> * The "main" function of the script (containing all the code at the
> top-level) is not executed.
> * Classes will not be added to the class table.
> 
> Confusingly, top-level functions _will_ be added to the function
> table. This has some weird consequences:
> 
> * opcache_compile_file() can be called multiple times on files
> containing classes. However, the same is not true for files containing
> functions. The second call will lead to a function redeclaration
> error.
> 
> ```php
> // index.php
> opcache_compile_file(__DIR__ . '/test.php');
> opcache_compile_file(__DIR__ . '/test.php');
> 
> // test.php
> class Foo {} // No problem
> function foo() {} // Fatal error: Cannot redeclare function foo()
> ```
> 
> * Similarly, after calling opcache_compile_file() on files containing
> classes, the same file may later be required without issues. This does
> not work for files containing functions for the same reason.
> 
> ```php
> // index.php
> opcache_compile_file(__DIR__ . '/test.php');
> require __DIR__ . '/test.php';
> 
> // test.php
> class Foo {} // No problem
> function foo() {} // Fatal error: Cannot redeclare function foo()
> ```
> 
> * Mixing functions with classes is incompatible. An attempt to use one
> of the classes from one of the functions will either error because of
> an undeclared class, or trigger the autoloader and include the file
> again, leading to a function redeclaration error.
> 
> ```php
> // index.php
> spl_autoload_register(function ($name) {
>    if ($name === 'Foo') {
>        require __DIR__ . '/b.php';
>    }
> });
> opcache_compile_file(__DIR__ . '/test.php');
> foo();
> 
> // test.php
> class Foo {}
> function foo() {
>    // Triggers the autoloader, the autoloader fails due to:
>    // Fatal error: Cannot redeclare function foo()
>    var_dump(new Foo());
> }
> ```
> 
> * Functions that are conditionally declared will not be added to the
> function table, since they are not top-level functions, even if the
> condition always evaluates to true.
> 
> ```php
> // index.php
> opcache_compile_file(__DIR__ . '/test.php');
> foo(); // Uncaught Error: Call to undefined function foo()
> 
> // test.php
> if (true) {
>    function foo() {}
> }
> ```
> 
> This behavior is inconsistent and confusing. Arguably, the correct
> behavior is to never register functions in opcache_compile_file() to
> begin with, since opcache_compile_file() exists to prime the cache,
> rather than execute code. I created a PR with this change [2]. This is
> breaking, since code using opcache_compile_file() might currently
> depend on functions being declared and then calling them directly.
> Such code would have to be adjusted to use require instead.
> 
> Are there any concerns with making this change for 8.5? Are there any
> use-cases this would break?
> 
> Of note is that it may be beneficial to provide a related function
> that allows compiling files and declaring symbols without executing
> the "main" function. This could be useful static analysis tools that
> want to reflect on files without executing them. That said, there are
> existing solutions in userland that solve this problem in a better
> way, like BetterReflection [3].
> 
> Ilija
> 
> [1] https://github.com/php/php-src/issues/16668
> [2] https://github.com/php/php-src/pull/16862
> [3] https://github.com/Roave/BetterReflection

Reply via email to