Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions src/Illuminate/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Illuminate\Support\ProcessUtils;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
Expand Down Expand Up @@ -54,11 +55,18 @@ class Application extends SymfonyApplication implements ApplicationContract
protected static $bootstrappers = [];

/**
* A map of command names to classes.
* Indicates whether the console application has booted.
*
* @var array
* @var bool
*/
protected $booted = false;

/**
* The lazy command loader.
*
* @var \Symfony\Component\Console\CommandLoader\CommandLoaderInterface
*/
protected $commandMap = [];
protected $commandLoader;

/**
* Create a new Artisan console application.
Expand All @@ -79,7 +87,7 @@ public function __construct(Container $laravel, Dispatcher $events, $version)

$this->events->dispatch(new ArtisanStarting($this));

$this->bootstrap();
$this->setContainerCommandLoader();
}

/**
Expand Down Expand Up @@ -151,13 +159,21 @@ public static function starting(Closure $callback)
/**
* Bootstrap the console application.
*
* @return void
* @return $this
*/
protected function bootstrap()
public function bootstrap()
{
if ($this->booted) {
return $this;
}

foreach (static::$bootstrappers as $bootstrapper) {
$bootstrapper($this);
}

$this->booted = true;

return $this;
}

/**
Expand Down Expand Up @@ -265,8 +281,8 @@ protected function addToParent(SymfonyCommand $command)
*/
public function resolve($command)
{
if (class_exists($command) && ($commandName = $command::getDefaultName())) {
$this->commandMap[$commandName] = $command;
if ($this->commandLoader && $this->commandLoader->accepts($command)) {
$this->commandLoader->add($command);

return null;
}
Expand All @@ -291,18 +307,46 @@ public function resolveCommands($commands)
return $this;
}

/**
* Add deferred commands list.
*
* @param array $commands Must be keyed by command name e.g. ['migrate' => MigrateCommand::class]
* @return $this
*/
public function addDeferredCommands(array $commands)
{
$this->commandLoader->merge($commands);

return $this;
}

/**
* Set the container command loader for lazy resolution.
*
* @param \Symfony\Component\Console\CommandLoader\CommandLoaderInterface|null
* @return $this
*/
public function setContainerCommandLoader()
public function setContainerCommandLoader(CommandLoaderInterface $loader = null)
{
$this->setCommandLoader(new ContainerCommandLoader($this->laravel, $this->commandMap));
if (is_null($loader)) {
$loader = new ContainerCommandLoader($this->laravel);
}

$this->setCommandLoader($this->commandLoader = $loader);

return $this;
}

/**
* Get the deferred console commander loader.
*
* @return \Symfony\Component\Console\CommandLoader\CommandLoaderInterface
*/
public function getCommandLoader()
{
return $this->commandLoader;
}

/**
* Get the default input definition for the application.
*
Expand Down
66 changes: 61 additions & 5 deletions src/Illuminate/Console/ContainerCommandLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Illuminate\Console;

use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
Expand All @@ -21,19 +22,64 @@ class ContainerCommandLoader implements CommandLoaderInterface
*
* @var array
*/
protected $commandMap;
protected $commandMap = [];

/**
* Create a new command loader instance.
*
* @param \Psr\Container\ContainerInterface $container
* @param array $commandMap
* @return void
*/
public function __construct(ContainerInterface $container, array $commandMap)
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->commandMap = $commandMap;
}

/**
* Merge the command map list
*
* @param array $commandMap
* @return void
*/
public function merge(array $commandMap)
{
$this->commandMap = array_merge($this->commandMap, $commandMap);
}

/**
* Determine if the command is accepted by the cmand loader.
*
* @param string $name
* @return bool
*/
public function accepts(string $name): bool
{
if (in_array($name, $this->commandMap)) {
return true;
}

return class_exists($name) && ! is_null($name::getDefaultName());
}

/**
* Add class to the loader.
*
* @param string $name
* @return void
*
* @throws \InvalidArgumentException
*/
public function add(string $name)
{
if (in_array($name, $this->commandMap)) {
return;
}

if (! $this->accepts($name)) {
throw new InvalidArgumentException(sprintf('Command "%s" was not accepted by the command loader.', $name));
}

$this->commandMap[$name::getDefaultName()] = $name;
}

/**
Expand Down Expand Up @@ -61,7 +107,7 @@ public function get(string $name): Command
*/
public function has(string $name): bool
{
return $name && isset($this->commandMap[$name]);
return isset($this->commandMap[$name]);
}

/**
Expand All @@ -73,4 +119,14 @@ public function getNames(): array
{
return array_keys($this->commandMap);
}

/**
* Get the full list of commands.
*
* @return array
*/
public function all(): array
{
return $this->commandMap;
}
}
35 changes: 35 additions & 0 deletions src/Illuminate/Foundation/Console/CommandCacheCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Illuminate\Foundation\Console;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;

class CommandCacheCommand extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'command:cache';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a cache file for faster command loading';

/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
Artisan::cacheCommands();

$this->info('Commands cached successfully!');
}
}
58 changes: 56 additions & 2 deletions src/Illuminate/Foundation/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class Kernel implements KernelContract
*/
protected $commands = [];

/**
* The deferred command list.
*
* @var array|null
*/
protected $deferredCommands;

/**
* Indicates if the Closure commands have been loaded.
*
Expand Down Expand Up @@ -199,6 +206,50 @@ public function command($signature, Closure $callback)
return $command;
}

/**
* Cache the list of registered deferred/lazily loaded commands.
*
* @return void
*/
public function cacheCommands()
{
$commands = $this->getArtisan()->getCommandLoader()->all();

file_put_contents($this->getCommandCachePath(), '<?php return ' . var_export($commands, true) . ';');
}

/**
* Get the list of deferred/lazily loaded commands.
*
* @return array
*/
protected function getCachedCommands(): array
{
$path = $this->getCommandCachePath();

return file_exists($path) ? require $this->getCommandCachePath() : [];
}

/**
* Get full path to the command cache file.
*
* @return string
*/
protected function getCommandCachePath(): string
{
return base_path('bootstrap/cache/commands.php');
}

/**
* Load the cached commands, used for deferring/lazy resolving of commands.
*
* @return void
*/
protected function loadCachedCommands()
{
$this->deferredCommands = $this->getCachedCommands();
}

/**
* Register all of the commands in the given directory.
*
Expand Down Expand Up @@ -313,7 +364,9 @@ public function bootstrap()
$this->app->loadDeferredProviders();

if (! $this->commandsLoaded) {
$this->commands();
if (! $this->loadCachedCommands()) {
$this->commands();
}

$this->commandsLoaded = true;
}
Expand All @@ -328,8 +381,9 @@ protected function getArtisan()
{
if (is_null($this->artisan)) {
$this->artisan = (new Artisan($this->app, $this->events, $this->app->version()))
->addDeferredCommands($this->deferredCommands ?? [])
->resolveCommands($this->commands)
->setContainerCommandLoader();
->bootstrap();
}

return $this->artisan;
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Foundation/Console/OptimizeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function handle()
{
$this->call('config:cache');
$this->call('route:cache');
$this->call('command:cache');

$this->info('Files cached successfully!');
}
Expand Down
14 changes: 14 additions & 0 deletions src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Illuminate\Foundation\Console\CastMakeCommand;
use Illuminate\Foundation\Console\ChannelMakeCommand;
use Illuminate\Foundation\Console\ClearCompiledCommand;
use Illuminate\Foundation\Console\CommandCacheCommand;
use Illuminate\Foundation\Console\ComponentMakeCommand;
use Illuminate\Foundation\Console\ConfigCacheCommand;
use Illuminate\Foundation\Console\ConfigClearCommand;
Expand Down Expand Up @@ -95,6 +96,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid
'ClearCompiled' => ClearCompiledCommand::class,
'ClearResets' => ClearResetsCommand::class,
'ConfigCache' => ConfigCacheCommand::class,
'CommandCache' => CommandCacheCommand::class,
'ConfigClear' => ConfigClearCommand::class,
'Db' => DbCommand::class,
'DbPrune' => PruneCommand::class,
Expand Down Expand Up @@ -320,6 +322,18 @@ protected function registerConfigClearCommand()
});
}

/**
* Register the command.
*
* @return void
*/
protected function registerCommandCacheCommand()
{
$this->app->singleton(CommandCacheCommand::class, function ($app) {
return new CommandCacheCommand;
});
}

/**
* Register the command.
*
Expand Down
Loading