Skip to content

[Console] Command as service #3621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 24, 2014
Merged
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
122 changes: 122 additions & 0 deletions cookbook/console/commands_as_services.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
.. index::
single: Console; Commands as Services

How to Define Commands as Services
==================================

.. versionadded:: 2.4
Support for registering commands in the service container was introduced in
version 2.4.

By default, Symfony will take a look in the ``Command`` directory of each
bundle and automatically register your commands. If a command extends the
:class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`,
Symfony will even inject the container.
While making life easier, this has some limitations:

* Your command must live in the ``Command`` directory;
* There's no way to conditionally register your service based on the environment
or availability of some dependencies;
* You can't access the container in the ``configure()`` method (because
``setContainer`` hasn't been called yet);
* You can't use the same class to create many commands (i.e. each with
different configuration).

To solve these problems, you can register your command as a service and tag it
with ``console.command``:

.. configuration-block::

.. code-block:: yaml

# app/config/config.yml
services:
acme_hello.command.my_command:
class: Acme\HelloBundle\Command\MyCommand
tags:
- { name: console.command }

.. code-block:: xml

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="acme_hello.command.my_command"
class="Acme\HelloBundle\Command\MyCommand">
<tag name="console.command" />
</service>
</services>
</container>

.. code-block:: php

// app/config/config.php
$container
->register('acme_hello.command.my_command', 'Acme\HelloBundle\Command\MyCommand')
->addTag('console.command')
;

Using Dependencies and Parameters to Set Default Values for Options
-------------------------------------------------------------------

Imagine you want to provide a default value for the ``name`` option. You could
pass one of the following as the 5th argument of ``addOption()``:

* a hardcoded string;
* a container parameter (e.g. something from parameters.yml);
* a value computed by a service (e.g. a repository).

By extending ``ContainerAwareCommand``, only the first is possible, because you
can't access the container inside the ``configure()`` method. Instead, inject
any parameter or service you need into the constructor. For example, suppose you
have some ``NameRepository`` service that you'll use to get your default value::

// src/Acme/DemoBundle/Command/GreetCommand.php
namespace Acme\DemoBundle\Command;

use Acme\DemoBundle\Entity\NameRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
{
protected $nameRepository;

public function __construct(NameRepository $nameRepository)
{
$this->nameRepository = $nameRepository;
}

protected function configure()
{
$defaultName = $this->nameRepository->findLastOne();

$this
->setName('demo:greet')
->setDescription('Greet someone')
->addOption('name', '-n', InputOption::VALUE_REQUIRED, 'Who do you want to greet?', $defaultName)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getOption('name');

$output->writeln($name);
}
}

Now, just update the arguments of your service configuration like normal to
inject the ``NameRepository``. Great, you now have a dynamic default value!

.. caution::

Be careful not to actually do any work in ``configure`` (e.g. make database
queries), as your code will be run, even if you're using the console to
execute a different command.
52 changes: 4 additions & 48 deletions cookbook/console/console_command.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,55 +65,11 @@ This command will now automatically be available to run:
.. _cookbook-console-dic:

Register Commands in the Service Container
------------------------------------------

.. versionadded:: 2.4
Support for registering commands in the service container was added in
version 2.4.

Instead of putting your command in the ``Command`` directory and having Symfony
auto-discover it for you, you can register commands in the service container
using the ``console.command`` tag:

.. configuration-block::

.. code-block:: yaml

# app/config/config.yml
services:
acme_hello.command.my_command:
class: Acme\HelloBundle\Command\MyCommand
tags:
- { name: console.command }

.. code-block:: xml

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<service id="acme_hello.command.my_command"
class="Acme\HelloBundle\Command\MyCommand">
<tag name="console.command" />
</service>
</container>

.. code-block:: php

// app/config/config.php

$container
->register('acme_hello.command.my_command', 'Acme\HelloBundle\Command\MyCommand')
->addTag('console.command')
;

.. tip::
-------------------------------------------

Registering your command as a service gives you more control over its
location and the services that are injected into it. But, there are no
functional advantages, so you don't need to register your command as a service.
Just like controllers, commands can be declared as services. See the
:doc:`dedicated cookbook entry </cookbook/console/commands_as_services>`
for details.

Getting Services from the Service Container
-------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions cookbook/console/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Console
usage
sending_emails
logging
commands_as_services