简体   繁体   中英

How to use DependencyInjection from symfony in stand alone application with commands?

I have been using symfony/console for making commands and registering them like that, everything works fine:

bin/console:

#!/usr/bin/env php
<?php
require_once __DIR__ . '/../vendor/autoload.php';

use App\Commands\LocalitiesCommand;
use Symfony\Component\Console\Application;

$app = new Application();
$app->add(new LocalitiesCommand(new LocalitiesGenerator()));
$app->run();

src/Commands/LocalitiesCommand.php:

<?php

declare(strict_types=1);

namespace App\Commands;

use App\LocalitiesGenerator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;


final class LocalitiesCommand extends Command
{
    protected static $defaultName = 'app:generate-localities';

    public function __construct(private LocalitiesGenerator $localitiesGenerator)
    {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setDescription('Generate localities.json file')
            ->setHelp('No arguments needed.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->localitiesGenerator->generateJsonLocalities();
        $output->writeln("File localities.json generated!");
        return Command::SUCCESS;
    }
}

Now I want to autoinject the service with symfony/dependency-injection, I was reading the documentation and did some changes:

new bin/console:

#!/usr/bin/env php
<?php
require_once __DIR__ . '/../vendor/autoload.php';

use App\Commands\LocalitiesCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/src/config'));
$loader->load('services.yaml');
$container->compile();


$app = new Application();
$app->add(new LocalitiesCommand());
$app->run();

config/services.yaml:

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

But still asks me to add my service in the constructor when I instantiate my command. Why is it not working?

First, let's clear up a misconception:

But still asks me to add my service in the constructor when I instantiate my command. Why is it not working?

If you call new Foo() , then you no longer are getting autowired DI benefits. If you want to use autowire and automatic dependency injection, you need to let Symfony work for you. When you call new , you are instantiating the object manually, and you need to take care of DI on your own.

With that out of the way, how would you get to do this?


First, composer.json with the basic dependencies and autoloader declaration:

The full directory structure will end up being like this:

<project_dir>
├── composer.json 
├── app 
├── src/
│    ├── ConsoleCommand/
│    │       └── FooCommand.php
│    └── Text/
│          └── Reverser.php
├── config/
│    ├── services.yaml

Now, each of the parts:

The composer.json file with all the dependencies and autoloader:

{
    "require": {
        "symfony/dependency-injection": "^5.3",
        "symfony/console": "^5.3",
        "symfony/config": "^5.3",
        "symfony/yaml": "^5.3"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src"
        }
    }
}

The front-controller script, the file running the application ( app , in my case):

#!/usr/bin/env php
<?php declare(strict_types=1);

use Symfony\Component;

require __DIR__ . '/vendor/autoload.php';

class App extends Component\Console\Application
{

    public function __construct(iterable $commands)
    {
        $commands = $commands instanceof Traversable ? iterator_to_array($commands) : $commands;

        foreach ($commands as $command) {
            $this->add($command);
        }

        parent::__construct();
    }
}

$container = new Component\DependencyInjection\ContainerBuilder();
$loader    = new Component\DependencyInjection\Loader\YamlFileLoader($container, new Component\Config\FileLocator(__DIR__ . '/config'));

$loader->load('services.yaml');
$container->compile();

$app = $container->get(App::class);
$app->run();

The service container configuration for the project:

# config/services.yaml
services:
  _defaults:
    autowire: true

  _instanceof:
    Symfony\Component\Console\Command\Command:
      tags: [ 'app.command' ]

  App\:
    resource: '../src/*'

  App:
    class: \App
    public: true
    arguments:
      - !tagged_iterator app.command

One FooCommand class:

<?php declare(strict_types=1);

// src/ConsoleCommand/FooCommand.php

namespace App\ConsoleCommand;

use App\Text\Reverser;
use Symfony\Component\Console;

class FooCommand extends Console\Command\Command
{

    protected static $defaultName = 'foo';

    public function __construct(private Reverser $reverser)
    {
        parent::__construct(self::$defaultName);
    }

    protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
    {
        $output->writeln('Foo was invoked');
        $output->writeln($this->reverser->exec('the lazy fox'));

        return self::SUCCESS;
    }
}

The above depends on the App\Text\Reverser service, which will be injected automatically for us by the DI component:

<?php declare(strict_types=1);

namespace App\Text;

class Reverser
{

    public function exec(string $in): string
    {
        return \strrev($in);
    }
}

After installing and dumping the autoloader, by executing php app (1) I get that the foo command is available (2): 在此处输入图像描述

I can execute php app foo , and the command is executed correctly, using its injected dependencies:

在此处输入图像描述

A self-contained Symfony Console application, with minimal dependencies and automatic dependency injection.

(All the code for a very similar example, here ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM