开发者

How can i inject dependencies to Symfony Console commands?

开发者 https://www.devze.com 2023-04-09 23:57 出处:网络
I\'m writing an open source application uses some Symfony components, and using Symfony Console component for interacting with shell.

I'm writing an open source application uses some Symfony components, and using Symfony Console component for interacting with shell.

But, i need to inject dependencies (used in all commands) something like Logger, Config object, Yaml parsers.. I solved this problem with extendi开发者_JS百科ng Symfony\Component\Console\Command\Command class. But this makes unit testing harder and not looks correct way.

How can i solve this ?


Since Symfony 4.2 the ContainerAwareCommand is deprecated. Use the DI instead.

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Doctrine\ORM\EntityManagerInterface;

final class YourCommand extends Command
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;

        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // YOUR CODE
        $this->entityManager->persist($object1);    
    }
}


It is best not to inject the container itself but to inject services from the container into your object. If you're using Symfony2's container, then you can do something like this:

MyBundle/Resources/config/services (or wherever you decide to put this file):

...
    <services>
        <service id="mybundle.command.somecommand" class="MyBundle\Command\SomeCommand">
        <call method="setSomeService">
             <argument type="service" id="some_service_id" />
        </call>
        </service>
    </services>
...

Then your command class should look like this:

<?php
namespace MyBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use The\Class\Of\The\Service\I\Wanted\Injected;

class SomeCommand extends Command
{
   protected $someService;
   public function setSomeService(Injected $someService)
   {
       $this->someService = $someService;
   }
...

I know you said you're not using the dependency injection container, but in order to implement the above answer from @ramon, you have to use it. At least this way your command can be properly unit tested.


use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

Extends your Command class from ContainerAwareCommand and get the service with $this->getContainer()->get('my_service_id');


You can use ContainerCommandLoader in order to provide a PSR-11 container as follow:

require 'vendor/autoload.php';

$application = new Application('my-app', '1.0');

$container = require 'config/container.php';

// Lazy load command with container
$commandLoader = new ContainerCommandLoader($container, [
    'app:change-mode' => ChangeMode::class,
    'app:generate-logs' => GenerateLogos::class,
]);

$application->setCommandLoader($commandLoader);

$application->run();

ChangeMode class could be defined as follow:

class ChangeMode extends Command
{

    protected static $defaultName = 'app:change-mode';

    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        parent::__construct(static::$defaultName);
    }
...

NB.: ChangeMode should be provided in the Container configuration.


I'm speaking for symfony2.8. You cannot add a constructor to the class that extends the ContainerAwareCommand because the extended class has a $this->getContainer() which got you covered in getting your services instead of injecting them via the constructor.

You can do $this->getContainer()->get('service-name');


Go to services.yaml

Add This to the file(I used 2 existing services as an example):

App\Command\MyCommand:
        arguments: [
            '@request_stack',
            '@doctrine.orm.entity_manager'
        ]

To see a list of all services type in terminal at the root project folder:

php bin/console debug:autowiring --all

You will get a long list of services you can use, an example of one line would look like this:

 Stores CSRF tokens.
 Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface (security.csrf.token_storage)

So if CSRF token services is what you are looking for(for example) you will use as a service the part in the parenthesis: (security.csrf.token_storage)

So your services.yaml will look somewhat like this:

parameters:

services:
    _defaults:
        autowire: true      
        autoconfigure: true 

# Here might be some other services...

App\Command\MyCommand:
        arguments: [
            '@security.csrf.token_storage'
        ]

Then in your command class use the service in the constructor:

class MyCommand extends Command
{
    private $csrfToken;

    public function __construct(CsrfToken $csrfToken)
    {
        parent::__construct();
        $this->csrfToken = $csrfToken;
    }
}


In Symfony 3.4, if autowire is configured correctly, services can be injected into the constructor of the command.

public function __construct(
    \AppBundle\Handler\Service\AwsS3Handler $s3Handler
) {
    parent::__construct();

    $this->s3Handler = $s3Handler;
}


php 8.1 Symfony 6.1

public function __construct(private EntityManagerInterface $em, string $name = null)
{
    parent::__construct($name);
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号