Как сделать команды ленивой загрузки

Дата обновления перевода 2024-07-17

Как сделать команды ленивой загрузки

Note

Если вы используете полностековый фреймворк Symfony, то вы скорее всего ищете подробности о создании команд ленивой загрузки

Традиционный способ добавления команд в ваше приложение - это использование add(), который ожидает экземпляр Command в качестве аргумента.

Этот подход может иметь и отрицательные стороны, поскольку некоторые команды могут быть объемными для инстанциирования, и в этом случае вы можете захотеть загрузить их в ленивом режиме. Однако обратите внимание, что ленивая загрузка не является абсолютной. Действительно, некоторые команды, такие как list, help или _complete могут потребовать инициализации других команд, хотя они и являются ленивыми. Например, list требует получить название и описание всех команд, что может потребовать инстанциирования команды для получения информации.

Для того, чтобы лениво загружать команды, вам нужно зарегистрировать срединный загрузчик, который будет отвечать за возвращение экземпляров Command:

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([
    // Отметьте, что команда `list` все равно будет инстанциировать эту команду
    // в данном примере.
    'app:heavy' => static fn(): Command => new HeavyCommand(),
]);

$application = new Application();
$application->setCommandLoader($commandLoader);
$application->run();

Таким образом, экземпляр HeavyCommand будет создан только тогда, когда команда app:heavy будет действительно вызвана.

Этот пример использует встроенный класс FactoryCommandLoader, но метод setCommandLoader() принимает любой экземпляр CommandLoaderInterface, так что вы можете использовать собственные реализации.

Другой способ сделать это - воспользоваться преимуществами Symfony\Component\Console\Command\LazyCommand:

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;

// В этом случае, хотя команда инстанциируется, низлежащая фабрика команд
// не будет выполнена, если только команда не выполняется по-настоящему, или если кто-то не пытается
// получить доступ к ее определению ввода, чтобы узнать ее вводы аргумента или опции.
$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();

Встроенные загрузчики команд

FactoryCommandLoader

Класс FactoryCommandLoader предоставляет простой способ получения команд ленивой загрузки, так как он берёт массив фабрик Command в качестве единственного аргумента конструктора:

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'],
]);

Фабрики могут быть любым PHP вызываемым и будут выполнены каждый раз, когда вызывается get().

ContainerCommandLoader

Класс ContainerCommandLoader может быть использован для загрузки команды из контейнера PSR-11. Таким образом, её конструктор берёт реализацию PSR-11 ContainerInterface в качестве своего первого аргумента, а карту команды - в качестве последнего. Карта команды должна быть массивом с именами команд в качестве ключей и идентификаторами сервисов в качестве значений:

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,
]);

Таким образом, выполнение команды app:foo загрузит сервис FooCommand, вызвав $container->get(FooCommand::class).