How to Make Commands Lazily Loaded
Note
If you are using the Symfony full-stack framework, you are probably looking for details about creating lazy commands
The traditional way of adding commands to your application is to use
add(), which expects a
Command
instance as an argument.
This approach can have downsides as some commands might be expensive to
instantiate in which case you may want to lazy-load them. Note however that lazy-loading
is not absolute. Indeed a few commands such as list
, help
or _complete
can
require to instantiate other commands although they are lazy. For example list
needs
to get the name and description of all commands, which might require the command to be
instantiated to get.
In order to lazy-load commands, you need to register an intermediate loader
which will be responsible for returning Command
instances:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use App\Command\HeavyCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
$commandLoader = new FactoryCommandLoader([
// Note that the `list` command will still instantiate that command
// in this example.
'app:heavy' => static fn(): Command => new HeavyCommand(),
]);
$application = new Application();
$application->setCommandLoader($commandLoader);
$application->run();
This way, the HeavyCommand
instance will be created only when the app:heavy
command is actually called.
This example makes use of the built-in FactoryCommandLoader class, but the setCommandLoader() method accepts any CommandLoaderInterface instance so you can use your own implementation.
Another way to do so is to take advantage of Symfony
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use App\Command\HeavyCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
// In this case although the command is instantiated, the underlying command factory
// will not be executed unless the command is actually executed or one tries to access
// its input definition to know its argument or option inputs.
$lazyCommand = new LazyCommand(
'app:heavy',
[],
'This is another more complete form of lazy command.',
false,
static fn (): Command => new HeavyCommand(),
);
$application = new Application();
$application->add($lazyCommand);
$application->run();
Built-in Command Loaders
FactoryCommandLoader
The FactoryCommandLoader
class provides a way of getting commands lazily loaded as it takes an
array of Command
factories as its only constructor argument:
1 2 3 4 5 6 7
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
$commandLoader = new FactoryCommandLoader([
'app:foo' => function (): Command { return new FooCommand(); },
'app:bar' => [BarCommand::class, 'create'],
]);
Factories can be any PHP callable and will be executed each time get() is called.
ContainerCommandLoader
The ContainerCommandLoader
class can be used to load commands from a PSR-11 container. As such, its
constructor takes a PSR-11 ContainerInterface
implementation as its first
argument and a command map as its last argument. The command map must be an array
with command names as keys and service identifiers as values:
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->register(FooCommand::class, FooCommand::class);
$container->compile();
$commandLoader = new ContainerCommandLoader($container, [
'app:foo' => FooCommand::class,
]);
Like this, executing the app:foo
command will load the FooCommand
service
by calling $container->get(FooCommand::class)
.