diff --git a/composer.json b/composer.json index 865614fd931f..08a640451fe1 100644 --- a/composer.json +++ b/composer.json @@ -103,7 +103,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.0", + "orchestra/testbench-core": "^8.1", "pda/pheanstalk": "^4.0", "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 2668e33eb499..98536ce41eec 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -4,24 +4,18 @@ use Closure; use Illuminate\Console\Events\ArtisanStarting; -use Illuminate\Console\Events\CommandFinished; -use Illuminate\Console\Events\CommandStarting; use Illuminate\Contracts\Console\Application as ApplicationContract; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\ProcessUtils; use Symfony\Component\Console\Application as SymfonyApplication; use Symfony\Component\Console\Command\Command as SymfonyCommand; -use Symfony\Component\Console\ConsoleEvents; -use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Process\PhpExecutableFinder; class Application extends SymfonyApplication implements ApplicationContract @@ -80,42 +74,9 @@ public function __construct(Container $laravel, Dispatcher $events, $version) $this->events->dispatch(new ArtisanStarting($this)); - $this->rerouteSymfonyCommandEvents(); - $this->bootstrap(); } - /** - * Re-route the Symfony command events to their Laravel counterparts. - * - * @return void - */ - protected function rerouteSymfonyCommandEvents() - { - $this->setDispatcher($dispatcher = new EventDispatcher); - - $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { - $this->events->dispatch( - new CommandStarting( - $event->getCommand()->getName(), - $event->getInput(), - $event->getOutput(), - ) - ); - }); - - $dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) { - $this->events->dispatch( - new CommandFinished( - $event->getCommand()->getName(), - $event->getInput(), - $event->getOutput(), - $event->getExitCode(), - ) - ); - }); - } - /** * Determine the proper PHP executable. * diff --git a/src/Illuminate/Foundation/Console/Kernel.php b/src/Illuminate/Foundation/Console/Kernel.php index e9c12616fe60..22662325b9fe 100644 --- a/src/Illuminate/Foundation/Console/Kernel.php +++ b/src/Illuminate/Foundation/Console/Kernel.php @@ -7,6 +7,8 @@ use DateTimeInterface; use Illuminate\Console\Application as Artisan; use Illuminate\Console\Command; +use Illuminate\Console\Events\CommandFinished; +use Illuminate\Console\Events\CommandStarting; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Contracts\Console\Kernel as KernelContract; use Illuminate\Contracts\Debug\ExceptionHandler; @@ -18,6 +20,10 @@ use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; use ReflectionClass; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Finder\Finder; use Throwable; @@ -39,6 +45,13 @@ class Kernel implements KernelContract */ protected $events; + /** + * The Symfony event dispatcher implementation. + * + * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface|null + */ + protected $symfonyDispatcher; + /** * The Artisan application instance. * @@ -105,11 +118,43 @@ public function __construct(Application $app, Dispatcher $events) $this->app = $app; $this->events = $events; + if (! $this->app->runningUnitTests()) { + $this->rerouteSymfonyCommandEvents(); + } + $this->app->booted(function () { $this->defineConsoleSchedule(); }); } + /** + * Re-route the Symfony command events to their Laravel counterparts. + * + * @internal + * + * @return $this + */ + public function rerouteSymfonyCommandEvents() + { + if (is_null($this->symfonyDispatcher)) { + $this->symfonyDispatcher = new EventDispatcher; + + $this->symfonyDispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { + $this->events->dispatch( + new CommandStarting($event->getCommand()->getName(), $event->getInput(), $event->getOutput()) + ); + }); + + $this->symfonyDispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) { + $this->events->dispatch( + new CommandFinished($event->getCommand()->getName(), $event->getInput(), $event->getOutput(), $event->getExitCode()) + ); + }); + } + + return $this; + } + /** * Define the application's command schedule. * @@ -417,6 +462,10 @@ protected function getArtisan() $this->artisan = (new Artisan($this->app, $this->events, $this->app->version())) ->resolveCommands($this->commands) ->setContainerCommandLoader(); + + if ($this->symfonyDispatcher instanceof EventDispatcher) { + $this->artisan->setDispatcher($this->symfonyDispatcher); + } } return $this->artisan; diff --git a/tests/Integration/Console/CommandEventsTest.php b/tests/Integration/Console/CommandEventsTest.php index ad755d5a1a00..ca90408df1f4 100644 --- a/tests/Integration/Console/CommandEventsTest.php +++ b/tests/Integration/Console/CommandEventsTest.php @@ -4,11 +4,14 @@ use Illuminate\Console\Events\CommandFinished; use Illuminate\Console\Events\CommandStarting; +use Illuminate\Contracts\Console\Kernel as ConsoleKernel; use Illuminate\Events\Dispatcher; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Process; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; +use Symfony\Component\Process\PhpExecutableFinder; class CommandEventsTest extends TestCase { @@ -65,14 +68,27 @@ protected function tearDown(): void parent::tearDown(); } + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function defineEnvironment($app) + { + $app->make(ConsoleKernel::class)->rerouteSymfonyCommandEvents(); + } + /** * @dataProvider commandEventsProvider */ public function testCommandEventsReceiveParsedInput($processType, $argumentType) { + $phpBinary = (new PhpExecutableFinder)->find(); + switch ($processType) { case 'foreground': - $this->app[\Illuminate\Contracts\Console\Kernel::class]->registerCommand(new CommandEventsTestCommand); + $this->app[ConsoleKernel::class]->registerCommand(new CommandEventsTestCommand); $this->app[Dispatcher::class]->listen(function (CommandStarting $event) { array_map(fn ($e) => $this->fs->append($this->logfile, $e."\n"), [ 'CommandStarting', @@ -106,13 +122,8 @@ public function testCommandEventsReceiveParsedInput($processType, $argumentType) case 'background': // Initialize empty logfile. $this->fs->append($this->logfile, ''); - exec('php '.base_path('artisan').' command-events-test-command-'.$this->id.' taylor otwell --occupation=coding'); - // Since our command is running in a separate process, we need to wait - // until it has finished executing before running our assertions. - $this->waitForLogMessages( - 'CommandStarting', 'taylor', 'otwell', 'coding', - 'CommandFinished', 'taylor', 'otwell', 'coding', - ); + + Process::run($phpBinary.' '.base_path('artisan').' command-events-test-command-'.$this->id.' taylor otwell --occupation=coding'); break; } @@ -185,6 +196,7 @@ protected function writeArtisanScript() \$app = require_once __DIR__.'/bootstrap/app.php'; \$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class); +\$kernel->rerouteSymfonyCommandEvents(); class CommandEventsTestCommand extends Illuminate\Console\Command {