Skip to content

Fix GH-18714: opcache_compile_file() breaks class hoisting#21470

Open
iliaal wants to merge 1 commit intophp:masterfrom
iliaal:fix/gh-18714-opcache-compile-file-class-hoisting
Open

Fix GH-18714: opcache_compile_file() breaks class hoisting#21470
iliaal wants to merge 1 commit intophp:masterfrom
iliaal:fix/gh-18714-opcache-compile-file-class-hoisting

Conversation

@iliaal
Copy link
Contributor

@iliaal iliaal commented Mar 18, 2026

Summary

opcache_compile_file() followed by require_once on the same file fails with "Class not found" when the class is used before its declaration (relying on class hoisting).

Root cause: opcache_compile_file() sets ZEND_COMPILE_WITHOUT_EXECUTION which prevents simple parentless classes from being linked during compilation. The cached script stores the class as unlinked, and opcache's delayed early binding can't resolve it -- the class has no parent to look up but isn't marked as linked either, so the binding is silently skipped.

Fix (two parts):

  • Zend/zend_compile.c: Separate the "link unbound simple class" path from the "early bind" path. Linking a parentless, trait-free, interface-free class is purely structural (zend_build_properties_info_table + zend_inheritance_check_override + ZEND_ACC_LINKED flag) with no execution side effects. Allow it even with ZEND_COMPILE_WITHOUT_EXECUTION, but not during preloading which has its own class linking pipeline.

  • ext/opcache/zend_accelerator_module.c: opcache_compile_file() goes through persistent_compile_file() which calls zend_accel_load_script(), registering classes/functions in the runtime tables. This is a side effect that causes duplicate registration when require_once later loads the same file. Clean up these entries after opcache_compile_file() returns, temporarily disabling the hash table destructor to avoid freeing SHM-backed entries. Cleanup is skipped during preloading where registrations must persist.

Fixes #18714

opcache_compile_file() sets ZEND_COMPILE_WITHOUT_EXECUTION which
prevented simple parentless classes from being linked during
compilation. When the cached script was later loaded via require,
opcache's delayed early binding couldn't resolve the unlinked class,
causing "Class not found" errors for classes used before their
declaration.

Two changes:

1. zend_compile.c: Allow linking simple classes (no parent, no
   interfaces, no traits) even with ZEND_COMPILE_WITHOUT_EXECUTION.
   This is purely structural with no execution side effects. The
   change is scoped to non-preload compilation to avoid interfering
   with preloading's own class linking pipeline.

2. zend_accelerator_module.c: Clean up classes/functions registered
   by zend_accel_load_script() after opcache_compile_file() returns.
   The function should only cache, not pollute the runtime tables.
   Cleanup is skipped during preloading where registrations must
   persist.

Closes phpGH-18714
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Opcache does not handle class hoisting with @opcache_compile_file

1 participant