From e52cb86a9a6ea93592dffb4a54201b2ca5b9c354 Mon Sep 17 00:00:00 2001 From: Daniel Klabbers Date: Tue, 14 Apr 2026 14:20:16 +0200 Subject: [PATCH 1/3] feat: support opcache flushing --- composer.json | 2 + .../src/ExtensionManagerServiceProvider.php | 4 ++ .../src/Listener/ClearOPCacheAfterUpdate.php | 26 +++++++++++ framework/core/composer.json | 1 + .../core/src/Forum/ForumServiceProvider.php | 1 + .../core/src/Http/Middleware/ClearOPCache.php | 37 ++++++++++++++++ php-packages/composer-plugin/composer.json | 23 ++++++++++ .../composer-plugin/src/ComposerPlugin.php | 44 +++++++++++++++++++ 8 files changed, 138 insertions(+) create mode 100644 extensions/package-manager/src/Listener/ClearOPCacheAfterUpdate.php create mode 100644 framework/core/src/Http/Middleware/ClearOPCache.php create mode 100644 php-packages/composer-plugin/composer.json create mode 100644 php-packages/composer-plugin/src/ComposerPlugin.php diff --git a/composer.json b/composer.json index 75c9b3f641..f8c596dfaa 100644 --- a/composer.json +++ b/composer.json @@ -110,6 +110,7 @@ "flarum/suspend": "self.version", "flarum/tags": "self.version", "flarum/messages": "self.version", + "flarum/composer-plugin": "self.version", "flarum/phpstan": "self.version", "flarum/testing": "self.version" }, @@ -122,6 +123,7 @@ "doctrine/dbal": "^3.6.2", "dragonmantank/cron-expression": "^3.3", "fakerphp/faker": "^1.9.1", + "flarum/composer-plugin": "2.x-dev", "flarum/json-api-server": "^0.1.0", "franzl/whoops-middleware": "2.0", "guzzlehttp/guzzle": "*", diff --git a/extensions/package-manager/src/ExtensionManagerServiceProvider.php b/extensions/package-manager/src/ExtensionManagerServiceProvider.php index d40b0e1828..2c819df632 100755 --- a/extensions/package-manager/src/ExtensionManagerServiceProvider.php +++ b/extensions/package-manager/src/ExtensionManagerServiceProvider.php @@ -15,8 +15,11 @@ use Flarum\Extension\ExtensionManager; use Flarum\ExtensionManager\Composer\ComposerAdapter; use Flarum\ExtensionManager\Event\FlarumUpdated; +use Flarum\ExtensionManager\Extension\Event\Installed; +use Flarum\ExtensionManager\Extension\Event\Removed; use Flarum\ExtensionManager\Extension\Event\Updated; use Flarum\ExtensionManager\Listener\ClearCacheAfterUpdate; +use Flarum\ExtensionManager\Listener\ClearOPCacheAfterUpdate; use Flarum\ExtensionManager\Listener\ReCheckForUpdates; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\Paths; @@ -100,5 +103,6 @@ function (Updated $event) use ($container) { $events->listen(FlarumUpdated::class, ClearCacheAfterUpdate::class); $events->listen([FlarumUpdated::class, Updated::class], ReCheckForUpdates::class); + $events->listen([Installed::class, Updated::class, Removed::class, FlarumUpdated::class], ClearOPCacheAfterUpdate::class); } } diff --git a/extensions/package-manager/src/Listener/ClearOPCacheAfterUpdate.php b/extensions/package-manager/src/Listener/ClearOPCacheAfterUpdate.php new file mode 100644 index 0000000000..d2551ebc22 --- /dev/null +++ b/extensions/package-manager/src/Listener/ClearOPCacheAfterUpdate.php @@ -0,0 +1,26 @@ +paths->storage . ClearOPCache::PATH; + + if (file_exists($path) || ! function_exists('opcache_reset')) { + return; + } + + @file_put_contents($path, (string) time()); + } +} diff --git a/framework/core/composer.json b/framework/core/composer.json index a2b5bfdb78..9ada459b04 100644 --- a/framework/core/composer.json +++ b/framework/core/composer.json @@ -42,6 +42,7 @@ "doctrine/dbal": "^3.6", "dragonmantank/cron-expression": "*", "fakerphp/faker": "^1.9.1", + "flarum/composer-plugin": "2.x-dev", "franzl/whoops-middleware": "2.0", "guzzlehttp/guzzle": "^7.7", "illuminate/bus": "^13.0", diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 963b28012e..592273fbc8 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -61,6 +61,7 @@ public function register(): void $this->container->singleton('flarum.forum.middleware', function () { return [ + HttpMiddleware\ClearOPCache::class, HttpMiddleware\InjectActorReference::class, 'flarum.forum.error_handler', HttpMiddleware\ParseJsonBody::class, diff --git a/framework/core/src/Http/Middleware/ClearOPCache.php b/framework/core/src/Http/Middleware/ClearOPCache.php new file mode 100644 index 0000000000..190063f000 --- /dev/null +++ b/framework/core/src/Http/Middleware/ClearOPCache.php @@ -0,0 +1,37 @@ +path()) || ! function_exists('opcache_reset')) { + return $handler->handle($request); + } + + opcache_reset(); + + @unlink($this->path()); + + return $handler->handle($request); + } + + public function path(): string + { + return $this->paths->storage . static::PATH; + } +} diff --git a/php-packages/composer-plugin/composer.json b/php-packages/composer-plugin/composer.json new file mode 100644 index 0000000000..231f70611a --- /dev/null +++ b/php-packages/composer-plugin/composer.json @@ -0,0 +1,23 @@ +{ + "name": "flarum/composer-plugin", + "description": "Assists Flarum with Composer", + "type": "composer-plugin", + "license": "MIT", + "require": { + "composer-plugin-api": "^2.0" + }, + "require-dev": { + "composer/composer": "^2.0" + }, + "autoload": { + "psr-4": { + "Flarum\\Composer\\Plugin\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + }, + "class": "Flarum\\Composer\\Plugin\\ComposerPlugin" + } +} diff --git a/php-packages/composer-plugin/src/ComposerPlugin.php b/php-packages/composer-plugin/src/ComposerPlugin.php new file mode 100644 index 0000000000..4a90558c47 --- /dev/null +++ b/php-packages/composer-plugin/src/ComposerPlugin.php @@ -0,0 +1,44 @@ +composer = $composer; + } + + public function deactivate(Composer $composer, IOInterface $io): void {} + public function uninstall(Composer $composer, IOInterface $io): void {} + + + public static function getSubscribedEvents(): array + { + return [ + 'post-autoload-dump' => 'flag', + ]; + } + + public function flag(): void + { + $path = class_exists(ClearOPCache::class) ? ClearOPCache::PATH : '/storage/cache/clear-opcache'; + + $vendorDir = $this->composer->getConfig()->get('vendor-dir'); + $flagFile = dirname($vendorDir) . $path; + + if (is_dir(dirname($flagFile)) && ! file_exists($flagFile)) { + @file_put_contents($flagFile, (string) time()); + } + } +} From 786f843ef0e37fc97d58faa06aa374059ae8db96 Mon Sep 17 00:00:00 2001 From: Daniel Klabbers Date: Tue, 14 Apr 2026 20:45:24 +0200 Subject: [PATCH 2/3] feat: support opcache clears in installation --- .../core/src/Http/Middleware/ClearOPCache.php | 19 +++++++++++++++++-- framework/core/src/Install/Installer.php | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/framework/core/src/Http/Middleware/ClearOPCache.php b/framework/core/src/Http/Middleware/ClearOPCache.php index 190063f000..d1dfd7e755 100644 --- a/framework/core/src/Http/Middleware/ClearOPCache.php +++ b/framework/core/src/Http/Middleware/ClearOPCache.php @@ -5,6 +5,7 @@ namespace Flarum\Http\Middleware; use Flarum\Foundation\Paths; +use Illuminate\Contracts\Container\Container; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface as Middleware; @@ -14,7 +15,7 @@ class ClearOPCache implements Middleware { const PATH = '/cache/clear-opcache'; - public function __construct(private readonly Paths $paths) + public function __construct(private readonly Container $container) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -32,6 +33,20 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface public function path(): string { - return $this->paths->storage . static::PATH; + if ($this->container->bound(Paths::class)) { + return $this->container->make(Paths::class)->storage . static::PATH; + } + + // Fallback when Paths is not available during installation. + $path = dirname(__DIR__, 3); + + while(true) { + if ($path === '.') throw new \Exception('Could not find storage directory'); + + if (is_dir("$path/storage")) break; + $path = dirname($path); + } + + return "$path/storage" . static::PATH; } } diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php index 192e499cd5..9cce790bf6 100644 --- a/framework/core/src/Install/Installer.php +++ b/framework/core/src/Install/Installer.php @@ -34,6 +34,7 @@ public function getContainer(): Container public function getRequestHandler(): RequestHandlerInterface { $pipe = new MiddlewarePipe; + $pipe->pipe(new HttpMiddleware\ClearOPCache($this->getContainer())); $pipe->pipe(new HttpMiddleware\HandleErrors( $this->container->make(Registry::class), $this->container->make(WhoopsFormatter::class), From 7813f2e089f38a3d5ee0bee3abb2388048f57b59 Mon Sep 17 00:00:00 2001 From: Daniel Klabbers Date: Wed, 15 Apr 2026 14:41:29 +0200 Subject: [PATCH 3/3] chore: implemented feedback --- framework/core/src/Admin/AdminServiceProvider.php | 1 + framework/core/src/Api/ApiServiceProvider.php | 1 + framework/core/src/Http/Middleware/ClearOPCache.php | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index f8bc098eef..631d09188b 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -50,6 +50,7 @@ public function register(): void $this->container->singleton('flarum.admin.middleware', function () { return [ + HttpMiddleware\ClearOPCache::class, HttpMiddleware\InjectActorReference::class, 'flarum.admin.error_handler', HttpMiddleware\ParseJsonBody::class, diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index 3ea3cbbb9e..9d9b10d8cc 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -86,6 +86,7 @@ public function register(): void $this->container->singleton('flarum.api.middleware', function () { return [ + HttpMiddleware\ClearOPCache::class, HttpMiddleware\InjectActorReference::class, 'flarum.api.error_handler', HttpMiddleware\ParseJsonBody::class, diff --git a/framework/core/src/Http/Middleware/ClearOPCache.php b/framework/core/src/Http/Middleware/ClearOPCache.php index d1dfd7e755..2f4d88fd3c 100644 --- a/framework/core/src/Http/Middleware/ClearOPCache.php +++ b/framework/core/src/Http/Middleware/ClearOPCache.php @@ -4,6 +4,7 @@ namespace Flarum\Http\Middleware; +use Exception; use Flarum\Foundation\Paths; use Illuminate\Contracts\Container\Container; use Psr\Http\Message\ResponseInterface; @@ -20,7 +21,7 @@ public function __construct(private readonly Container $container) public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if (! file_exists($this->path()) || ! function_exists('opcache_reset')) { + if (! function_exists('opcache_reset') || ! file_exists($this->path())) { return $handler->handle($request); } @@ -39,9 +40,11 @@ public function path(): string // Fallback when Paths is not available during installation. $path = dirname(__DIR__, 3); + $upTo = dirname(__DIR__, 6); while(true) { - if ($path === '.') throw new \Exception('Could not find storage directory'); + if ($path === $upTo) throw new Exception('Could not find storage directory'); + if ($path === '.') throw new Exception('Could not find storage directory'); if (is_dir("$path/storage")) break; $path = dirname($path);