From 62b5698d7bc9b0251ef8df1b66a4c93daa907466 Mon Sep 17 00:00:00 2001
From: Erik Gaal <me@erikgaal.nl>
Date: Wed, 25 Sep 2024 15:55:07 +0200
Subject: [PATCH] [11.x] Optimize commands registration

---
 .../Console/OptimizeClearCommand.php          | 25 +++++++++----
 .../Foundation/Console/OptimizeCommand.php    | 21 ++++++++---
 src/Illuminate/Support/ServiceProvider.php    | 25 +++++++++++++
 .../Console/OptimizeClearCommandTest.php      | 37 +++++++++++++++++++
 .../Console/OptimizeCommandTest.php           | 37 +++++++++++++++++++
 5 files changed, 131 insertions(+), 14 deletions(-)
 create mode 100644 tests/Integration/Foundation/Console/OptimizeClearCommandTest.php
 create mode 100644 tests/Integration/Foundation/Console/OptimizeCommandTest.php

diff --git a/src/Illuminate/Foundation/Console/OptimizeClearCommand.php b/src/Illuminate/Foundation/Console/OptimizeClearCommand.php
index 02c9d5ab6cf5..bf4e666d71bc 100644
--- a/src/Illuminate/Foundation/Console/OptimizeClearCommand.php
+++ b/src/Illuminate/Foundation/Console/OptimizeClearCommand.php
@@ -3,6 +3,7 @@
 namespace Illuminate\Foundation\Console;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\ServiceProvider;
 use Symfony\Component\Console\Attribute\AsCommand;
 
 #[AsCommand(name: 'optimize:clear')]
@@ -31,15 +32,23 @@ public function handle()
     {
         $this->components->info('Clearing cached bootstrap files.');
 
-        collect([
-            'cache' => fn () => $this->callSilent('cache:clear') == 0,
-            'compiled' => fn () => $this->callSilent('clear-compiled') == 0,
-            'config' => fn () => $this->callSilent('config:clear') == 0,
-            'events' => fn () => $this->callSilent('event:clear') == 0,
-            'routes' => fn () => $this->callSilent('route:clear') == 0,
-            'views' => fn () => $this->callSilent('view:clear') == 0,
-        ])->each(fn ($task, $description) => $this->components->task($description, $task));
+        foreach ($this->getOptimizeClearTasks() as $description => $command) {
+            $this->components->task($description, fn () => $this->callSilently($command) == 0);
+        }
 
         $this->newLine();
     }
+
+    public function getOptimizeClearTasks(): array
+    {
+        return [
+            'cache' => 'cache:clear',
+            'compiled' => 'clear-compiled',
+            'config' => 'config:clear',
+            'events' => 'event:clear',
+            'routes' => 'route:clear',
+            'views' => 'view:clear',
+            ...ServiceProvider::$optimizeClearingCommands,
+        ];
+    }
 }
diff --git a/src/Illuminate/Foundation/Console/OptimizeCommand.php b/src/Illuminate/Foundation/Console/OptimizeCommand.php
index 0bcf3e97a3a6..14ec87c0fcc8 100644
--- a/src/Illuminate/Foundation/Console/OptimizeCommand.php
+++ b/src/Illuminate/Foundation/Console/OptimizeCommand.php
@@ -3,6 +3,7 @@
 namespace Illuminate\Foundation\Console;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\ServiceProvider;
 use Symfony\Component\Console\Attribute\AsCommand;
 
 #[AsCommand(name: 'optimize')]
@@ -31,13 +32,21 @@ public function handle()
     {
         $this->components->info('Caching framework bootstrap, configuration, and metadata.');
 
-        collect([
-            'config' => fn () => $this->callSilent('config:cache') == 0,
-            'events' => fn () => $this->callSilent('event:cache') == 0,
-            'routes' => fn () => $this->callSilent('route:cache') == 0,
-            'views' => fn () => $this->callSilent('view:cache') == 0,
-        ])->each(fn ($task, $description) => $this->components->task($description, $task));
+        foreach ($this->getOptimizeTasks() as $description => $command) {
+            $this->components->task($description, fn () => $this->callSilently($command) == 0);
+        }
 
         $this->newLine();
     }
+
+    protected function getOptimizeTasks(): array
+    {
+        return [
+            'config' => 'config:cache',
+            'events' => 'event:cache',
+            'routes' => 'route:cache',
+            'views' => 'view:cache',
+            ...ServiceProvider::$optimizingCommands,
+        ];
+    }
 }
diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php
index 607431363e2a..0516bd94f529 100755
--- a/src/Illuminate/Support/ServiceProvider.php
+++ b/src/Illuminate/Support/ServiceProvider.php
@@ -51,6 +51,20 @@ abstract class ServiceProvider
      */
     public static $publishGroups = [];
 
+    /**
+     * Commands that should be run during the optimize command.
+     *
+     * @var array<string, string>
+     */
+    public static array $optimizingCommands = [];
+
+    /**
+     * Commands that should be run during the optimize:clear command.
+     *
+     * @var array<string, string>
+     */
+    public static array $optimizeClearingCommands = [];
+
     /**
      * The migration paths available for publishing.
      *
@@ -537,4 +551,15 @@ public static function addProviderToBootstrapFile(string $provider, ?string $pat
 
         return true;
     }
+
+    protected function registerOptimizeCommands(string $key, string $optimize = null, string $optimizeClear = null): void
+    {
+        if ($optimize) {
+            static::$optimizingCommands[$key] = $optimize;
+        }
+
+        if ($optimizeClear) {
+            static::$optimizeClearingCommands[$key] = $optimizeClear;
+        }
+    }
 }
diff --git a/tests/Integration/Foundation/Console/OptimizeClearCommandTest.php b/tests/Integration/Foundation/Console/OptimizeClearCommandTest.php
new file mode 100644
index 000000000000..a18bd9155ca7
--- /dev/null
+++ b/tests/Integration/Foundation/Console/OptimizeClearCommandTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Illuminate\Tests\Integration\Foundation\Console;
+
+use Illuminate\Foundation\Console\ClosureCommand;
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Tests\Integration\Generators\TestCase;
+
+class OptimizeClearCommandTest extends TestCase
+{
+    protected function getPackageProviders($app): array
+    {
+        return [TestServiceProvider::class];
+    }
+
+    public function testCanListenToOptimizingEvent(): void
+    {
+        $this->artisan('optimize:clear')
+            ->assertSuccessful()
+            ->expectsOutputToContain('my package');
+    }
+}
+
+class TestServiceProvider extends ServiceProvider
+{
+    public function boot(): void
+    {
+        $this->commands([
+            new ClosureCommand('my_package:clear', fn () => 0),
+        ]);
+
+        $this->registerOptimizeCommands(
+            key: 'my package',
+            optimizeClear: 'my_package:clear',
+        );
+    }
+}
diff --git a/tests/Integration/Foundation/Console/OptimizeCommandTest.php b/tests/Integration/Foundation/Console/OptimizeCommandTest.php
new file mode 100644
index 000000000000..cf1b0f20236f
--- /dev/null
+++ b/tests/Integration/Foundation/Console/OptimizeCommandTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Illuminate\Tests\Integration\Foundation\Console;
+
+use Illuminate\Foundation\Console\ClosureCommand;
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Tests\Integration\Generators\TestCase;
+
+class OptimizeCommandTest extends TestCase
+{
+    protected function getPackageProviders($app): array
+    {
+        return [TestServiceProvider::class];
+    }
+
+    public function testCanListenToOptimizingEvent(): void
+    {
+        $this->artisan('optimize')
+            ->assertSuccessful()
+            ->expectsOutputToContain('my package');
+    }
+}
+
+class TestServiceProvider extends ServiceProvider
+{
+    public function boot(): void
+    {
+        $this->commands([
+            new ClosureCommand('my_package:cache', fn () => 0),
+        ]);
+
+        $this->registerOptimizeCommands(
+            key: 'my package',
+            optimize: 'my_package:cache',
+        );
+    }
+}