@@ -46,6 +46,8 @@ zend_result (*old_stream_open)(zend_file_handle *) = nullptr;
4646php_stream_wrapper_ops wrapped_file_ops;
4747php_stream_wrapper wrapped_file_wrapper;
4848bool file_wrapper_installed = false ;
49+ bool runtime_hooks_installed = false ;
50+ bool opcode_table_started = false ;
4951
5052std::string lexical_path (const std::string &path) {
5153 return std::filesystem::path (path).lexically_normal ().string ();
@@ -184,7 +186,8 @@ int stat_embedded_file(php_stream_wrapper *, const char *filename, int flags,
184186
185187}
186188
187- extern " C" void typephp_opcode_table_install (void ) {
189+ extern " C" void typephp_opcode_table_startup (void ) {
190+ if (opcode_table_started) return ;
188191 size_t count = 0 ;
189192 const typephp_opcode_entry *table = typephp_project_opcode_table (&count);
190193 size_t raw_count = 0 ;
@@ -200,12 +203,27 @@ extern "C" void typephp_opcode_table_install(void) {
200203 for (size_t i = 0 ; i < raw_count; ++i) {
201204 raw_entries.emplace (raw_table[i].path , &raw_table[i]);
202205 }
203- old_compile_file = zend_compile_file;
204- old_resolve_path = zend_resolve_path;
205- old_stream_open = zend_stream_open_function;
206- zend_compile_file = embedded_compile_file;
207- zend_resolve_path = resolve_path;
208- zend_stream_open_function = stream_open;
206+ opcode_table_started = true ;
207+ }
208+
209+ extern " C" void typephp_opcode_table_request_startup (void ) {
210+ if (!opcode_table_started) return ;
211+
212+ // Zend extensions such as OPcache install their compiler hooks after
213+ // module MINIT. Chain TypePHP outside those hooks at the first request so
214+ // embedded opcodes are resolved before OPcache tries to stat the synthetic
215+ // zend_file_handle. Keep the process-wide chain for subsequent requests.
216+ if (!runtime_hooks_installed) {
217+ old_compile_file = zend_compile_file;
218+ old_resolve_path = zend_resolve_path;
219+ old_stream_open = zend_stream_open_function;
220+ zend_compile_file = embedded_compile_file;
221+ zend_resolve_path = resolve_path;
222+ zend_stream_open_function = stream_open;
223+ runtime_hooks_installed = true ;
224+ }
225+
226+ if (file_wrapper_installed) return ;
209227
210228 wrapped_file_ops = *php_plain_files_wrapper.wops ;
211229 wrapped_file_ops.stream_opener = open_embedded_file;
@@ -222,6 +240,35 @@ extern "C" void typephp_opcode_table_install(void) {
222240 zend_string_release (protocol);
223241}
224242
243+ extern " C" void typephp_opcode_table_install (void ) {
244+ typephp_opcode_table_startup ();
245+ typephp_opcode_table_request_startup ();
246+ }
247+
248+ extern " C" int typephp_embedded_file_exists (const char *path) {
249+ if (!path) return 0 ;
250+ const std::string name (path);
251+ return lookup (entries, name) != nullptr || lookup (raw_entries, name) != nullptr ;
252+ }
253+
254+ extern " C" int typephp_embedded_path_kind (const char *path) {
255+ if (!path) return 0 ;
256+ std::string name (path);
257+ if (lookup (entries, name) || lookup (raw_entries, name)) return 1 ;
258+ if (name.rfind (" file://" , 0 ) == 0 ) name.erase (0 , 7 );
259+ name = lexical_path (name);
260+ if (!name.empty () && name.back () != std::filesystem::path::preferred_separator) {
261+ name.push_back (std::filesystem::path::preferred_separator);
262+ }
263+ const auto contains_child = [&name](const auto &table) {
264+ for (const auto &item : table) {
265+ if (item.first .rfind (name, 0 ) == 0 ) return true ;
266+ }
267+ return false ;
268+ };
269+ return contains_child (entries) || contains_child (raw_entries) ? 2 : 0 ;
270+ }
271+
225272extern " C" void typephp_opcode_table_require (const char *path) {
226273 const typephp_opcode_entry *entry = lookup (entries, std::string (path));
227274 if (!entry) {
@@ -236,17 +283,7 @@ extern "C" void typephp_opcode_table_require(const char *path) {
236283 zend_destroy_file_handle (&handle);
237284}
238285
239- extern " C" void typephp_opcode_table_uninstall (void ) {
240- if (old_compile_file) {
241- zend_compile_file = old_compile_file;
242- zend_resolve_path = old_resolve_path;
243- zend_stream_open_function = old_stream_open;
244- old_compile_file = nullptr ;
245- old_resolve_path = nullptr ;
246- old_stream_open = nullptr ;
247- }
248- entries.clear ();
249- raw_entries.clear ();
286+ extern " C" void typephp_opcode_table_request_shutdown (void ) {
250287 if (file_wrapper_installed) {
251288 zend_string *protocol = zend_string_init (" file" , sizeof (" file" ) - 1 , 0 );
252289 php_unregister_url_stream_wrapper_volatile (protocol);
@@ -255,3 +292,24 @@ extern "C" void typephp_opcode_table_uninstall(void) {
255292 file_wrapper_installed = false ;
256293 }
257294}
295+
296+ extern " C" void typephp_opcode_table_shutdown (void ) {
297+ if (!opcode_table_started) return ;
298+ typephp_opcode_table_request_shutdown ();
299+ if (runtime_hooks_installed) {
300+ if (zend_compile_file == embedded_compile_file) zend_compile_file = old_compile_file;
301+ if (zend_resolve_path == resolve_path) zend_resolve_path = old_resolve_path;
302+ if (zend_stream_open_function == stream_open) zend_stream_open_function = old_stream_open;
303+ runtime_hooks_installed = false ;
304+ }
305+ old_compile_file = nullptr ;
306+ old_resolve_path = nullptr ;
307+ old_stream_open = nullptr ;
308+ entries.clear ();
309+ raw_entries.clear ();
310+ opcode_table_started = false ;
311+ }
312+
313+ extern " C" void typephp_opcode_table_uninstall (void ) {
314+ typephp_opcode_table_shutdown ();
315+ }
0 commit comments