Skip to content

Commit

Permalink
feature #3621 [Console] Command as service (gnugat)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.4 branch.

Discussion
----------

[Console] Command as service

| Q             | A
| ------------- | ---
| Doc fix?      | yes
| New docs?     | no
| Applies to    | 2.4+
| Fixed tickets | N/A

Command as a service can be useful to give access to services and
configuration parameters in the `configure` method.

A simple use case: you want to allow the user to set an option's default
value in the `app/config/parameters.yml` file. Or the default value
needs to be computed by  a service (database retrieval for instance).

With a `ContainerAwareCommand`, this wouldn't be possible because the
`configure` method is called from the constructor, so the container
isn't set yet.

Original PR: #3620

Commits
-------

c8fe610 Moved back and listened to @weaverryan
e8b3320 Took into account @cordoval's feedback
11bfe50 Added warning about performances
6a7a25f Fixed @wouterj's feedback
e137951 Created a new article
cdd534a Added @lsmith77 and @dbu use cases
a7b916e Fixed typos spotted by @cordoval
a055140 Took @wouterj's advices into account
947ad92 [Console] Adding use cases to command as service
  • Loading branch information
weaverryan committed Mar 24, 2014
2 parents d8182fc + c8fe610 commit c75b1a7
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 48 deletions.
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

0 comments on commit c75b1a7

Please sign in to comment.