.. index::
single: Service Container
single: Container de Injeção de Dependência
Container de Serviço
====================
Uma aplicação PHP moderna é cheia de objetos. Um objeto pode facilitar a entrega
de mensagens de e-mail enquanto outro pode permitir persistir as informações
em um banco de dados. Em sua aplicação, você pode criar um objeto que gerencia
seu estoque de produtos, ou outro objeto que processa dados de uma API de
terceiros. O ponto é que uma aplicação moderna faz muitas coisas e é organizada
em muitos objetos que lidam com cada tarefa.
Neste capítulo, vamos falar sobre um objeto PHP especial no Symfony2 que ajuda
você a instanciar, organizar e recuperar os muitos objetos da sua aplicação.
Esse objeto, chamado de container de serviço, lhe permitirá padronizar e
centralizar a forma como os objetos são construídos em sua aplicação. O container
facilita a sua vida, é super rápido e enfatiza uma arquitetura que promove
código reutilizável e desacoplado. E, como todas as classes principais do Symfony2
usam o container, você vai aprender como estender, configurar e usar qualquer objeto
no Symfony2. Em grande parte, o container de serviço é o maior contribuinte à
velocidade e extensibilidade do Symfony2.
Finalmente, configurar e usar o container de serviço é fácil. Ao final deste
capítulo, você estará confortável criando os seus próprios objetos através do container
e personalizando objetos a partir de qualquer bundle de terceiros. Você vai começar
a escrever código que é mais reutilizável, testável e desacoplado, simplesmente porque
o container de serviço torna fácil escrever bom código.
.. index::
single: Container de Serviço; O que é um Serviço?
O que é um Serviço?
-------------------
Simplificando, um :term:`Serviço` é qualquer objeto PHP que realiza algum tipo de
tarefa "global". É um nome genérico proposital, usado em ciência da computação,
para descrever um objeto que é criado para uma finalidade específica (por exemplo,
entregar e-mails). Cada serviço é usado em qualquer parte da sua aplicação sempre que
precisar da funcionalidade específica que ele fornece. Você não precisa fazer nada de
especial para construir um serviço: basta escrever uma classe PHP com algum código que
realiza uma tarefa específica. Parabéns, você acabou de criar um serviço!
.. note::
Como regra geral, um objeto PHP é um serviço se ele é usado globalmente em sua
aplicação. Um único serviço ``Mailer`` é utilizado globalmente para enviar
mensagens de e-mail, enquanto os muitos objetos ``Message`` que ele entrega
*não* são serviços. Do mesmo modo, um objeto ``Product`` não é um serviço, mas
um objeto que persiste os objetos ``Product`` para um banco de dados *é* um serviço.
Então, porque ele é especial? A vantagem de pensar em "serviços" é que você começa
a pensar em separar cada pedaço de funcionalidade de sua aplicação em uma série de
serviços. Uma vez que cada serviço realiza apenas um trabalho, você pode facilmente acessar
cada serviço e usar a sua funcionalidade, sempre que você precisar. Cada serviço pode
também ser mais facilmente testado e configurado já que ele está separado das outras
funcionalidades em sua aplicação. Esta idéia é chamada de `arquitetura orientada à
serviços`_ e não é exclusiva do Symfony2 ou até mesmo do PHP. Estruturar a sua aplicação
em torno de um conjunto de classes de serviços independentes é uma das melhores práticas
de orientação à objeto bem conhecida e confiável. Essas habilidades são a chave para
ser um bom desenvolvedor em praticamente qualquer linguagem.
.. index::
single: Container de Serviço; O que é?
O que é um Service Container?
----------------------------
Um :term:`Container de Serviço` (ou *container de injeção de dependência*) é
simplesmente um objeto PHP que gerencia a instanciação de serviços (ex. objetos).
Por exemplo, imagine que temos uma classe PHP simples que envia mensagens de e-mail.
Sem um container de serviço, precisamos criar manualmente o objeto sempre que
precisarmos dele:
.. code-block:: php
use Acme\HelloBundle\Mailer;
$mailer = new Mailer('sendmail');
$mailer->send('ryan@foobar.net', ... );
Isso é bastante fácil. A classe imaginária ``Mailer`` nos permite configurar o método
utilizado para entregar as mensagens de e-mail (por exemplo: ``sendmail``, ``smtp``, etc).
Mas, e se quiséssemos usar o serviço de mailer em outro lugar? Nós certamente não
desejamos repetir a configuração do mailer *sempre* que nós precisamos usar o objeto
``Mailer``. E se precisarmos mudar o ``transport`` de ``sendmail`` para ``smtp`` em
toda a aplicação? Nós precisaremos localizar cada lugar em que adicionamos um serviço
``Mailer`` e alterá-lo.
.. index::
single: Container de Serviço; Configurando serviços
Criando/Configurando Serviços no Container
------------------------------------------
Uma resposta melhor é deixar o container de serviço criar o objeto ``Mailer``
para você. Para que isso funcione, é preciso *ensinar* o container como
criar o serviço ``Mailer``. Isto é feito através de configuração, que pode
ser especificada em YAML, XML ou PHP:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
services:
my_mailer:
class: Acme\HelloBundle\Mailer
arguments: [sendmail]
.. code-block:: xml
sendmail
.. code-block:: php
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$container->setDefinition('my_mailer', new Definition(
'Acme\HelloBundle\Mailer',
array('sendmail')
));
.. note::
Quando o Symfony2 é inicializado, ele constrói o container de serviço usando a
configuração da aplicação (por padrão (``app/config/config.yml``). O arquivo
exato que é carregado é ditado pelo método ``AppKernel::registerContainerConfiguration()``
, que carrega um arquivo de configuração do ambiente específico (por exemplo
``config_dev.yml`` para o ambiente ``dev`` ou ``config_prod.yml``
para o ``prod``).
Uma instância do objeto ``Acme\HelloBundle\Mailer`` está agora disponível através do
container de serviço. O container está disponível em qualquer controlador tradicional do
Symfony2 onde você pode acessar os serviços do container através do método de atalho
``get()``::
class HelloController extends Controller
{
// ...
public function sendEmailAction()
{
// ...
$mailer = $this->get('my_mailer');
$mailer->send('ryan@foobar.net', ... );
}
}
Quando requisitamos o serviço ``my_mailer`` a partir do container, o container
constrói o objeto e o retorna. Esta é outra vantagem importante do uso do container
de serviço. Ou seja, um serviço *nunca* é construído até que ele seja necessário.
Se você definir um serviço e nunca usá-lo em um pedido, o serviço nunca será criado.
Isso economiza memória e aumenta a velocidade de sua aplicação. Isto também significa
que não há nenhuma perda ou apenas uma perda insignificante de desempenho ao definir
muitos serviços. Serviços que nunca são usados, nunca são construídos.
Como um bônus adicional, o serviço ``Mailer`` é criado apenas uma vez e a mesma
instância é retornada cada vez que você requisitar o serviço. Isso é quase sempre
o comportamento que você precisa (é mais flexível e poderoso), mas vamos aprender,
mais tarde, como você pode configurar um serviço que possui várias instâncias.
.. _book-service-container-parameters:
Parâmetros do Serviço
---------------------
A criação de novos serviços (objetos, por exemplo) através do container é bastante
simples. Os parâmetros tornam a definição dos serviços mais organizada e flexível:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
parameters:
my_mailer.class: Acme\HelloBundle\Mailer
my_mailer.transport: sendmail
services:
my_mailer:
class: %my_mailer.class%
arguments: [%my_mailer.transport%]
.. code-block:: xml
Acme\HelloBundle\Mailer
sendmail
%my_mailer.transport%
.. code-block:: php
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
$container->setParameter('my_mailer.transport', 'sendmail');
$container->setDefinition('my_mailer', new Definition(
'%my_mailer.class%',
array('%my_mailer.transport%')
));
O resultado final é exatamente o mesmo de antes - a diferença está apenas em *como*
definimos o serviço. Quando as strings ``my_mailer.class`` e ``my_mailer.transport``
estão envolvidas por sinais de porcentagem (``%``), o container sabe que deve procurar
por parâmetros com esses nomes. Quando o container é construído, ele procura o valor de
cada parâmetro e utiliza-o na definição do serviço.
.. note::
O sinal de porcentagem dentro de um parâmetro ou argumento, como parte da string, deve
ser escapado com um outro sinal de porcentagem:
.. code-block:: xml
http://symfony.com/?foo=%%s&bar=%%d
A finalidade dos parâmetros é alimentar informação para os serviços. Naturalmente,
não há nada de errado em definir o serviço sem o uso de quaisquer parâmetros.
Os parâmetros, no entanto, possuem várias vantagens:
* separação e organização de todas as "opções" dos serviços sob uma única
chave ``parameters``;
* os valores do parâmetro podem ser usados em múltiplas definições de serviços;
* ao criar um serviço em um bundle (vamos mostrar isso em breve), o uso de parâmetros
permite que o serviço seja facilmente customizado em sua aplicação.
A escolha de usar ou não os parâmetros é com você. Bundles de terceiros de alta qualidade
*sempre* utilizarão parâmetros, pois, eles tornam mais configurável o serviço armazenado no
container. Para os serviços em sua aplicação, entretanto, você pode não precisar da
flexibilidade dos parâmetros.
Parâmetros Array
~~~~~~~~~~~~~~~~
Os parâmetros não precisam ser strings, eles também podem ser arrays. Para o formato XML
, você precisará usar o atributo type="collection" em todos os parâmetros que são
arrays.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
parameters:
my_mailer.gateways:
- mail1
- mail2
- mail3
my_multilang.language_fallback:
en:
- en
- fr
fr:
- fr
- en
.. code-block:: xml
mail1
mail2
mail3
en
fr
fr
en
.. code-block:: php
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$container->setParameter('my_mailer.gateways', array('mail1', 'mail2', 'mail3'));
$container->setParameter('my_multilang.language_fallback',
array('en' => array('en', 'fr'),
'fr' => array('fr', 'en'),
));
Importando outros Recursos de Configuração do Container
-------------------------------------------------------
.. tip::
Nesta seção, vamos referenciar os arquivos de configuração de serviço como *recursos*.
Isto é para destacar o fato de que, enquanto a maioria dos recursos de configuração
será em arquivos (por exemplo, YAML, XML, PHP), o Symfony2 é tão flexível que a configuração
pode ser carregada de qualquer lugar (por exemplo, de um banco de dados ou até mesmo através
de um web service externo).
O service container é construído usando um único recurso de configuração
(por padrão ``app/config/config.yml``). Todas as outras configurações de serviço
(incluindo o núcleo do Symfony2 e configurações de bundles de terceiros) devem
ser importadas dentro desse arquivo de uma forma ou de outra. Isso lhe dá absoluta
flexibilidade sobre os serviços em sua aplicação.
Configurações de serviços externos podem ser importadas de duas maneiras distintas.
Primeiro, vamos falar sobre o método que você vai usar mais frequentemente na sua
aplicação: a diretiva ``imports``. Na seção seguinte, vamos falar sobre o segundo método,
que é mais flexível e preferido para a importação de configuração de serviços dos bundles
de terceiros.
.. index::
single: Container de Serviço; imports
.. _service-container-imports-directive:
Importando configuração com ``imports``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Até agora, adicionamos a nossa definição do container de serviço ``my_mailer``
diretamente no arquivo de configuração da aplicação (por exemplo: ``app/config/config.yml``).
Naturalmente, já que a própria classe ``Mailer`` reside dentro do ``AcmeHelloBundle``,
faz mais sentido colocar a definição do container ``my_mailer`` dentro do
bundle também.
Primeiro, mova a definição do container ``my_mailer`` dentro de um novo arquivo container de recurso
dentro do ``AcmeHelloBundle``. Se os diretórios ``Resources`` ou ``Resources/config``
não existirem, adicione eles.
.. configuration-block::
.. code-block:: yaml
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
my_mailer.class: Acme\HelloBundle\Mailer
my_mailer.transport: sendmail
services:
my_mailer:
class: %my_mailer.class%
arguments: [%my_mailer.transport%]
.. code-block:: xml
Acme\HelloBundle\Mailer
sendmail
%my_mailer.transport%
.. code-block:: php
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
$container->setParameter('my_mailer.transport', 'sendmail');
$container->setDefinition('my_mailer', new Definition(
'%my_mailer.class%',
array('%my_mailer.transport%')
));
A definição em si não mudou, apenas a sua localização. Claro que o container de
serviço não sabe sobre o novo arquivo de recurso. Felizmente, nós podemos facilmente
importar o arquivo de recurso usando a chave ``imports`` na configuração da
aplicação.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
imports:
- { resource: @AcmeHelloBundle/Resources/config/services.yml }
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$this->import('@AcmeHelloBundle/Resources/config/services.php');
A diretiva ``imports`` permite à sua aplicação incluir os recursos de configuração
do container de serviço de qualquer outra localização (mais comumente, de bundles).
A localização do ``resource``, para arquivos, é o caminho absoluto para o arquivo de
recurso. A sintaxe especial ``@AcmeHello`` resolve o caminho do diretório do
bundle ``AcmeHelloBundle``. Isso ajuda você a especificar o caminho para o recurso
sem preocupar-se mais tarde caso mover o ``AcmeHelloBundle`` para um diretório
diferente.
.. index::
single: Container de Serviço; Configuração de extensão
.. _service-container-extension-configuration:
Importando Configuração através de Extensões do Container
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ao desenvolver no Symfony2, você vai mais comumente usar a diretiva ``imports``
para importar a configuração do container dos bundles que você criou especificamente
para a sua aplicação. As configurações de container de bundles de terceiros, incluindo
os serviços do núcleo do Symfony2, são geralmente carregadas usando um outro método
que é mais flexível e fácil de configurar em sua aplicação.
Veja como ele funciona. Internamente, cada bundle define os seus serviços de forma
semelhante a que vimos até agora. Ou seja, um bundle utiliza um ou mais arquivos de
configurações de recurso (normalmente XML) para especificar os parâmetros e serviços
para aquele bundle. No entanto, em vez de importar cada um desses recursos diretamente a partir
da sua configuração da aplicação usando a diretiva ``imports``, você pode simplesmente
invocar uma *extensão do container de serviço* dentro do bundle que faz o trabalho para
você. Uma extensão do container de serviço é uma classe PHP criada pelo autor do bundle
para realizar duas coisas:
* importar todos os recursos de container de serviço necessários para configurar
os serviços para o bundle;
* fornecer configuração simples e semântica para que o bundle possa
ser configurado sem interagir com os parâmetros da configuração
do container de serviço.
Em outras palavras, uma extensão do container de serviço configura os serviços
para um bundle em seu nome. E, como veremos em breve, a extensão fornece
uma interface sensível e de alto nível para configurar o bundle.
Considere o ``FrameworkBundle`` - o bundle do núcleo do framework Symfony2 -
como um exemplo. A presença do código a seguir na configuração da sua aplicação
invoca a extensão do container de serviço dentro do ``FrameworkBundle``:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
framework:
secret: xxxxxxxxxx
form: true
csrf_protection: true
router: { resource: "%kernel.root_dir%/config/routing.yml" }
# ...
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('framework', array(
'secret' => 'xxxxxxxxxx',
'form' => array(),
'csrf-protection' => array(),
'router' => array('resource' => '%kernel.root_dir%/config/routing.php'),
// ...
));
Quando é realizado o parse da configuração, o container procura uma extensão que
pode lidar com a diretiva de configuração ``framework``. A extensão em questão,
que reside no ``FrameworkBundle``, é chamada e a configuração do serviço para o
``FrameworkBundle`` é carregada. Se você remover totalmente a chave ``framework``
de seu arquivo de configuração da aplicação, os serviços do núcleo do Symfony2
não serão carregados. O ponto é que você está no controle: o framework Symfony2
não contém qualquer tipo de magia ou realiza quaisquer ações que você não tenha
controle.
Claro que você pode fazer muito mais do que simplesmente "ativar" a extensão do
container de serviço do ``FrameworkBundle``. Cada extensão permite que você facilmente
personalize o bundle, sem se preocupar com a forma como os serviços internos são
definidos.
Neste caso, a extensão permite que você personalize as configurações ``error_handler``,
``csrf_protection``, ``router`` e muito mais. Internamente,
o ``FrameworkBundle`` usa as opções especificadas aqui para definir e configurar
os serviços específicos para ele. O bundle se encarrega de criar todos os ``parameters``
e ``services`` necessários para o container de serviço, permitindo ainda que grande parte
da configuração seja facilmente personalizada. Como um bônus adicional, a maioria das
extensões do container de serviço também são inteligentes o suficiente para executar a validação -
notificando as opções que estão faltando ou que estão com o tipo de dados incorreto.
Ao instalar ou configurar um bundle, consulte a documentação do bundle para
verificar como os serviços para o bundle devem ser instalados e configurados. As opções
disponíveis para os bundles do núcleo podem ser encontradas no :doc:`Reference Guide`.
.. note::
Nativamente, o container de serviço somente reconhece as diretivas
``parameters``, ``services`` e ``imports``. Quaisquer outras diretivas
são manipuladas pela extensão do container de serviço.
Se você deseja expor configuração amigável em seus próprios bundles, leia o
":doc:`/cookbook/bundles/extension`" do cookbook.
.. index::
single: Container de Serviço; Referenciando serviços
Referenciando (Injetando) Serviços
----------------------------------
Até agora, nosso serviço original ``my_mailer`` é simples: ele recebe apenas um argumento
em seu construtor, que é facilmente configurável. Como você verá, o poder real
do container é percebido quando você precisa criar um serviço que depende de um ou
mais outros serviços no container.
Vamos começar com um exemplo. Suponha que temos um novo serviço, ``NewsletterManager``,
que ajuda a gerenciar a preparação e entrega de uma mensagem de e-mail para
uma coleção de endereços. Claro que o serviço ``my_mailer`` já está muito bom
ao entregar mensagens de e-mail, por isso vamos usá-lo dentro do ``NewsletterManager``
para lidar com a entrega das mensagens. Esta simulação de classe seria parecida
com::
namespace Acme\HelloBundle\Newsletter;
use Acme\HelloBundle\Mailer;
class NewsletterManager
{
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
Sem usar o container de serviço, podemos criar um novo ``NewsletterManager``
facilmente dentro de um controlador::
public function sendNewsletterAction()
{
$mailer = $this->get('my_mailer');
$newsletter = new Acme\HelloBundle\Newsletter\NewsletterManager($mailer);
// ...
}
Esta abordagem é boa, mas, e se decidirmos mais tarde que a classe ``NewsletterManager``
precisa de um segundo ou terceiro argumento? E se decidirmos refatorar nosso
código e renomear a classe? Em ambos os casos, você precisa encontrar todos os
locais onde o ``NewsletterManager`` é instanciado e modificá-lo. Certamente,
o container de serviço nos dá uma opção muito mais atraente:
.. configuration-block::
.. code-block:: yaml
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
services:
my_mailer:
# ...
newsletter_manager:
class: %newsletter_manager.class%
arguments: [@my_mailer]
.. code-block:: xml
Acme\HelloBundle\Newsletter\NewsletterManager
.. code-block:: php
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');
$container->setDefinition('my_mailer', ... );
$container->setDefinition('newsletter_manager', new Definition(
'%newsletter_manager.class%',
array(new Reference('my_mailer'))
));
Em YAML, a sintaxe especial ``@my_mailer`` diz ao container para procurar
por um serviço chamado ``my_mailer`` e passar esse objeto para o construtor
do ``NewsletterManager``. Neste caso, entretanto, o serviço especificado ``my_mailer``
deve existir. Caso ele não existir, será gerada uma exceção. Você pode marcar suas
dependências como opcionais - o que será discutido na próxima seção.
O uso de referências é uma ferramenta muito poderosa que lhe permite criar classes de
serviços independentes com dependências bem definidas. Neste exemplo, o serviço
``newsletter_manager`` precisa do serviço ``my_mailer`` para funcionar. Quando você
define essa dependência no container de serviço, o container cuida de todo o
trabalho de instanciar os objetos.
Dependências Opcionais: Injeção do Setter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Injetando dependências no construtor dessa maneira é uma excelente forma de
assegurar que a dependência estará disponível para o uso. No entanto, se você possuir
dependências opcionais para uma classe, a "injeção do setter" pode ser uma opção melhor.
Isto significa injetar a dependência usando uma chamada de método ao invés do
construtor. A classe ficaria assim::
namespace Acme\HelloBundle\Newsletter;
use Acme\HelloBundle\Mailer;
class NewsletterManager
{
protected $mailer;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
Para injetar a dependência pelo método setter somente é necessária uma mudança da sintaxe:
.. configuration-block::
.. code-block:: yaml
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
services:
my_mailer:
# ...
newsletter_manager:
class: %newsletter_manager.class%
calls:
- [ setMailer, [ @my_mailer ] ]
.. code-block:: xml
Acme\HelloBundle\Newsletter\NewsletterManager
.. code-block:: php
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');
$container->setDefinition('my_mailer', ... );
$container->setDefinition('newsletter_manager', new Definition(
'%newsletter_manager.class%'
))->addMethodCall('setMailer', array(
new Reference('my_mailer')
));
.. note::
As abordagens apresentadas nesta seção são chamadas de "injeção de construtor"
e "injeção de setter". O container de serviço do Symfony2 também suporta a
"injeção de propriedade".
Tornando Opcionais as Referências
---------------------------------
Às vezes, um de seus serviços pode ter uma dependência opcional, ou seja,
a dependência não é exigida por seu serviço para funcionar corretamente. No
exemplo acima, o serviço ``my_mailer`` *deve* existir, caso contrário, uma exceção
será gerada. Ao modificar a definição do serviço ``newsletter_manager`` ,
você pode tornar esta referência opcional. O container irá, então, injetá-lo se
ele existir e não irá fazer nada caso contrário:
.. configuration-block::
.. code-block:: yaml
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
services:
newsletter_manager:
class: %newsletter_manager.class%
arguments: [@?my_mailer]
.. code-block:: xml
.. code-block:: php
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerInterface;
// ...
$container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');
$container->setDefinition('my_mailer', ... );
$container->setDefinition('newsletter_manager', new Definition(
'%newsletter_manager.class%',
array(new Reference('my_mailer', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))
));
Em YAML, a sintaxe especial ``@?`` diz ao container de serviço que a dependência
é opcional. Naturalmente, o ``NewsletterManager`` também deve ser escrito para
permitir uma dependência opcional:
.. code-block:: php
public function __construct(Mailer $mailer = null)
{
// ...
}
Serviços do Núcleo do Symfony e de Bundles de Terceiros
-------------------------------------------------------
Desde que o Symfony2 e todos os bundles de terceiros configuram e recuperam os seus serviços
através do container, você pode acessá-los facilmente ou até mesmo usá-los em seus próprios
serviços. Para manter tudo simples, o Symfony2, por padrão, não exige que controladores sejam
definidos como serviços. Além disso, o Symfony2 injeta o container de serviço inteiro em seu
controlador. Por exemplo, para gerenciar o armazenamento de informações em uma sessão do
usuário, o Symfony2 fornece um serviço ``session``, que você pode acessar dentro de um
controlador padrão da seguinte forma::
public function indexAction($bar)
{
$session = $this->get('session');
$session->set('foo', $bar);
// ...
}
No Symfony2, você vai usar constantemente os serviços fornecidos pelo núcleo do Symfony ou
outros bundles de terceiros para executar tarefas como renderização de templates (``templating``),
envio de e-mails (``mailer``), ou acessar informações sobre o pedido(``request``).
Podemos levar isto um passo adiante, usando esses serviços dentro dos serviços que
você criou para a sua aplicação. Vamos modificar o ``NewsletterManager``
para utilizar o serviço de ``mailer`` real do Symfony2 (no lugar do ``my_mailer``).
Vamos também passar o serviço de templating engine para o ``NewsletterManager``
então ele poderá gerar o conteúdo de e-mail através de um template::
namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\Templating\EngineInterface;
class NewsletterManager
{
protected $mailer;
protected $templating;
public function __construct(\Swift_Mailer $mailer, EngineInterface $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
// ...
}
A configuração do service container é fácil:
.. configuration-block::
.. code-block:: yaml
services:
newsletter_manager:
class: %newsletter_manager.class%
arguments: [@mailer, @templating]
.. code-block:: xml
.. code-block:: php
$container->setDefinition('newsletter_manager', new Definition(
'%newsletter_manager.class%',
array(
new Reference('mailer'),
new Reference('templating')
)
));
O serviço ``newsletter_manager`` agora tem acesso aos serviços do núcleo
``mailer`` and ``templating``. Esta é uma forma comum de criar serviços
específicos para sua aplicação que aproveitam o poder de diferentes serviços
dentro do framework.
.. tip::
Certifique-se de que a entrada ``swiftmailer`` aparece na configuração da
sua aplicação. Como mencionamos em :ref:`service-container-extension-configuration`,
a chave ``swiftmailer`` invoca a extensão de serviço do
``SwiftmailerBundle``, que registra o serviço ``mailer``.
.. index::
single: Container de Serviço; Configuração avançada
Configuração Avançada do Container
----------------------------------
Como vimos, a definição de serviços no interior do container é fácil, geralmente
envolvendo uma chave de configuração ``service`` e alguns parâmetros. No entanto,
o container possui várias outras ferramentas disponíveis que ajudam a adicionar uma *tag*
para uma funcionalidade especial nos serviços, criar serviços mais complexos e executar
operações após o container estar construído.
Marcando Serviços como público / privado
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ao definir os serviços, normalmente você vai desejar poder acessar essas definições
dentro do código da sua aplicação. Esses serviços são chamados ``publicos``. Por exemplo,
o serviço ``doctrine`` registrado com o container quando se utiliza o DoctrineBundle
é um serviço público, já que você pode acessá-lo via::
$doctrine = $container->get('doctrine');
No entanto, existem casos de uso em que você não deseja que um serviço seja público.
Isto é comum quando um serviço é definido apenas porque poderia ser usado como um
argumento para um outro serviço.
.. note::
Se você usar um serviço privado como um argumento para mais de um serviço,
isto irá resultar em duas instâncias diferentes sendo usadas, já que, a instanciação
do serviço privado é realizada inline (por exemplo: ``new PrivateFooBar()``).
Simplesmente falando: Um serviço será privado quando você não quiser acessá-lo
diretamente em seu código.
Aqui está um exemplo:
.. configuration-block::
.. code-block:: yaml
services:
foo:
class: Acme\HelloBundle\Foo
public: false
.. code-block:: xml
.. code-block:: php
$definition = new Definition('Acme\HelloBundle\Foo');
$definition->setPublic(false);
$container->setDefinition('foo', $definition);
Agora que o serviço é privado, você *não* pode chamar::
$container->get('foo');
No entanto, se o serviço foi marcado como privado, você ainda pode adicionar um alias
para ele (veja abaixo) para acessar este serviço (através do alias).
.. note::
Os serviços são públicos por padrão.
Definindo Alias
~~~~~~~~~~~~~~~
Ao usar bundles do núcleo ou de terceiros dentro de sua aplicação, você pode desejar
usar atalhos para acessar alguns serviços. Isto é possível adicionando alias à eles
e, além disso, você pode até mesmo adicionar alias para serviços que não são públicos.
.. configuration-block::
.. code-block:: yaml
services:
foo:
class: Acme\HelloBundle\Foo
bar:
alias: foo
.. code-block:: xml
.. code-block:: php
$definition = new Definition('Acme\HelloBundle\Foo');
$container->setDefinition('foo', $definition);
$containerBuilder->setAlias('bar', 'foo');
Isto significa que, ao usar o container diretamente, você pode acessar o serviço
``foo`` pedindo pelo serviço ``bar`` como segue::
$container->get('bar'); // retorna o serviço foo
Incluindo Arquivos
~~~~~~~~~~~~~~~~~~
Podem haver casos de uso quando é necessário incluir outro arquivo antes do
serviço em si ser carregado. Para fazer isso, você pode usar a diretiva ``file``.
.. configuration-block::
.. code-block:: yaml
services:
foo:
class: Acme\HelloBundle\Foo\Bar
file: %kernel.root_dir%/src/path/to/file/foo.php
.. code-block:: xml
%kernel.root_dir%/src/path/to/file/foo.php
.. code-block:: php
$definition = new Definition('Acme\HelloBundle\Foo\Bar');
$definition->setFile('%kernel.root_dir%/src/path/to/file/foo.php');
$container->setDefinition('foo', $definition);
Observe que o symfony irá, internamente, chamar a função PHP require_once
o que significa que seu arquivo será incluído apenas uma vez por pedido.
.. _book-service-container-tags:
Tags (``tags``)
~~~~~~~~~~~~~~~
Da mesma forma que podem ser definidas tags para um post de um blog na web com palavras
como "Symfony" ou "PHP", também podem ser definidas tags para os serviços configurados
em seu container. No container de serviço, a presença de uma tag significa que o serviço
destina-se ao uso para um fim específico. Veja o seguinte exemplo:
.. configuration-block::
.. code-block:: yaml
services:
foo.twig.extension:
class: Acme\HelloBundle\Extension\FooExtension
tags:
- { name: twig.extension }
.. code-block:: xml
.. code-block:: php
$definition = new Definition('Acme\HelloBundle\Extension\FooExtension');
$definition->addTag('twig.extension');
$container->setDefinition('foo.twig.extension', $definition);
A tag ``twig.extension`` é uma tag especial que o ``TwigBundle`` usa durante a
configuração. Ao definir a tag ``twig.extension`` para este serviço, o bundle
saberá que o serviço ``foo.twig.extension`` deve ser registrado como uma extensão
Twig com o Twig. Em outras palavras, o Twig encontra todos os serviços com a tag
``twig.extension`` e automaticamente registra-os como extensões.
Tags, então, são uma forma de dizer ao Symfony2 ou outros bundles de terceiros que
o seu serviço deve ser registrado ou usado de alguma forma especial pelo bundle.
Segue abaixo uma lista das tags disponíveis com os bundles do núcleo do Symfony2.
Cada uma delas tem um efeito diferente no seu serviço e muitas tags requerem
argumentos adicionais (além do parâmetro ``name``).
* assetic.filter
* assetic.templating.php
* data_collector
* form.field_factory.guesser
* kernel.cache_warmer
* kernel.event_listener
* monolog.logger
* routing.loader
* security.listener.factory
* security.voter
* templating.helper
* twig.extension
* translation.loader
* validator.constraint_validator
Saiba mais
----------
* :doc:`/components/dependency_injection/factories`
* :doc:`/components/dependency_injection/parentservices`
* :doc:`/cookbook/controller/service`
.. _`arquitetura orientada à serviços`: http://wikipedia.org/wiki/Service-oriented_architecture