@@ -207,6 +207,79 @@ public function disableMaintenanceMode(): void
207207 }
208208 }
209209
210+ /**
211+ * Reset PHP OPcache for the web server process.
212+ *
213+ * OPcache in PHP-FPM is separate from CLI. After updating code files,
214+ * PHP-FPM may still serve stale cached bytecode, causing constructor
215+ * mismatches and 500 errors. This method creates a temporary PHP script
216+ * in the public directory, invokes it via HTTP to reset OPcache in the
217+ * web server context, then removes the script.
218+ *
219+ * @return bool Whether OPcache was successfully reset
220+ */
221+ private function resetOpcache (): bool
222+ {
223+ $ token = bin2hex (random_bytes (16 ));
224+ $ resetScript = $ this ->project_dir . '/public/_opcache_reset_ ' . $ token . '.php ' ;
225+
226+ try {
227+ // Create a temporary PHP script that resets OPcache
228+ $ scriptContent = '<?php '
229+ . 'if (function_exists("opcache_reset")) { opcache_reset(); echo "OK"; } '
230+ . 'else { echo "NO_OPCACHE"; } '
231+ . '@unlink(__FILE__); ' ;
232+
233+ $ this ->filesystem ->dumpFile ($ resetScript , $ scriptContent );
234+
235+ // Try to invoke it via HTTP on localhost
236+ $ urls = [
237+ 'http://127.0.0.1/_opcache_reset_ ' . $ token . '.php ' ,
238+ 'http://localhost/_opcache_reset_ ' . $ token . '.php ' ,
239+ ];
240+
241+ $ success = false ;
242+ foreach ($ urls as $ url ) {
243+ try {
244+ $ context = stream_context_create ([
245+ 'http ' => [
246+ 'timeout ' => 5 ,
247+ 'ignore_errors ' => true ,
248+ ],
249+ ]);
250+
251+ $ response = @file_get_contents ($ url , false , $ context );
252+ if ($ response === 'OK ' ) {
253+ $ this ->logger ->info ('OPcache reset via ' . $ url );
254+ $ success = true ;
255+ break ;
256+ }
257+ } catch (\Throwable $ e ) {
258+ // Try next URL
259+ continue ;
260+ }
261+ }
262+
263+ if (!$ success ) {
264+ $ this ->logger ->info ('OPcache reset via HTTP not available, trying CLI fallback ' );
265+ // CLI opcache_reset() only affects CLI, but try anyway
266+ if (function_exists ('opcache_reset ' )) {
267+ opcache_reset ();
268+ }
269+ }
270+
271+ return $ success ;
272+ } catch (\Throwable $ e ) {
273+ $ this ->logger ->warning ('OPcache reset failed: ' . $ e ->getMessage ());
274+ return false ;
275+ } finally {
276+ // Ensure the temp script is removed
277+ if (file_exists ($ resetScript )) {
278+ @unlink ($ resetScript );
279+ }
280+ }
281+ }
282+
210283 /**
211284 * Validate that we can perform an update.
212285 *
@@ -434,12 +507,20 @@ public function executeUpdate(
434507 ], 'Warmup cache ' , 120 );
435508 $ log ('cache_warmup ' , 'Warmed up application cache ' , true , microtime (true ) - $ stepStart );
436509
437- // Step 13: Disable maintenance mode
510+ // Step 13: Reset OPcache (if available)
511+ $ stepStart = microtime (true );
512+ $ opcacheResult = $ this ->resetOpcache ();
513+ $ log ('opcache_reset ' , $ opcacheResult
514+ ? 'Reset PHP OPcache for web server '
515+ : 'OPcache reset skipped (not available or not needed) ' ,
516+ true , microtime (true ) - $ stepStart );
517+
518+ // Step 14: Disable maintenance mode
438519 $ stepStart = microtime (true );
439520 $ this ->disableMaintenanceMode ();
440521 $ log ('maintenance_off ' , 'Disabled maintenance mode ' , true , microtime (true ) - $ stepStart );
441522
442- // Step 14 : Release lock
523+ // Step 15 : Release lock
443524 $ stepStart = microtime (true );
444525 $ this ->releaseLock ();
445526
@@ -494,6 +575,9 @@ public function executeUpdate(
494575 ], 'Clear cache after rollback ' , 120 );
495576 $ log ('rollback_cache ' , 'Cleared cache after rollback ' , true );
496577
578+ // Reset OPcache after rollback
579+ $ this ->resetOpcache ();
580+
497581 } catch (\Exception $ rollbackError ) {
498582 $ log ('rollback_failed ' , 'Rollback failed: ' . $ rollbackError ->getMessage (), false );
499583 }
@@ -682,12 +766,17 @@ function ($entry) use ($log) {
682766 $ this ->runCommand (['php ' , 'bin/console ' , 'cache:warmup ' ], 'Warm up cache ' );
683767 $ log ('cache_warmup ' , 'Warmed up application cache ' , true , microtime (true ) - $ stepStart );
684768
685- // Step 6: Disable maintenance mode
769+ // Step 6: Reset OPcache
770+ $ stepStart = microtime (true );
771+ $ this ->resetOpcache ();
772+ $ log ('opcache_reset ' , 'Reset PHP OPcache ' , true , microtime (true ) - $ stepStart );
773+
774+ // Step 7: Disable maintenance mode
686775 $ stepStart = microtime (true );
687776 $ this ->disableMaintenanceMode ();
688777 $ log ('maintenance_off ' , 'Disabled maintenance mode ' , true , microtime (true ) - $ stepStart );
689778
690- // Step 7 : Release lock
779+ // Step 8 : Release lock
691780 $ this ->releaseLock ();
692781
693782 $ totalDuration = microtime (true ) - $ startTime ;
@@ -817,7 +906,7 @@ public function startBackgroundUpdate(string $targetVersion, bool $createBackup
817906 'create_backup ' => $ createBackup ,
818907 'started_at ' => (new \DateTime ())->format ('c ' ),
819908 'current_step ' => 0 ,
820- 'total_steps ' => 14 ,
909+ 'total_steps ' => 15 ,
821910 'step_name ' => 'initializing ' ,
822911 'step_message ' => 'Starting update process... ' ,
823912 'steps ' => [],
@@ -890,7 +979,7 @@ public function executeUpdateWithProgress(
890979 bool $ createBackup = true ,
891980 ?callable $ onProgress = null
892981 ): array {
893- $ totalSteps = 12 ;
982+ $ totalSteps = 13 ;
894983 $ currentStep = 0 ;
895984
896985 $ updateProgress = function (string $ stepName , string $ message , bool $ success = true ) use (&$ currentStep , $ totalSteps , $ targetVersion , $ createBackup ): void {
0 commit comments