.. index::
single: Controlador
Controlador
===========
Um controlador é uma função PHP que você cria e que pega informações da
requisição HTTP para criar e retornar uma resposta HTTP (como um objeto
``Response`` do Symfony2). A resposta pode ser uma página HTML, um documento
XML, um array JSON serializado, uma imagem, um redirecionamento, um erro 404
ou qualquer coisa que você imaginar. O controlador contém toda e qualquer lógica
arbritária que *sua aplicação* precisa para renderizar o conteúdo de uma
página.
Para ver quão simples é isso, vamos ver um controlador do Symfony2 em ação.
O seguinte controlador deve renderizar uma página que mostra apenas
``Hello world!``:
use Symfony\Component\HttpFoundation\Response;
public function helloAction()
{
return new Response('Hello world!');
}
O objetivo de um controlador é sempre o mesmo: criar e retornar um objeto
``Response``. Ao longo do caminho, ele pode ler informações da requisição,
carregar um recurso do banco de dados, mandar um e-mail ou gravar informações
na sessão do usuário. Mas em todos os casos, o controlador acabará retornando
o objeto ``Response`` que será mandado de volta para o cliente.
Não há nenhuma mágica e nenhum outro requisito para se preocupar! Aqui temos
alguns exemplos comuns:
* O *Controlador A* prepara um objeto ``Response`` representando o conteúdo
da página inicial do site.
* O *Controlador B* lê o parâmetro ``slug`` da requisição para carregar uma
entrada do blog no banco de dados e cria um um objeto ``Response`` mostrando
o blog. Se o ``slug`` não for encontrado no banco de dados, ele cria e
retorna um objeto ``Response`` com um código de status 404.
* O *Controlador C* trata a o envio de um formulário de contato. Ele lê a
informação do formulário a partir da requisição, salva a informação de
contato no banco de dados e envia por e-mail a informação de contato
para o webmaster. Finalmente, ele cria um objeto ``Response`` que
redireciona o navegador do cliente para a página "thank you" do formulário
de contato.
.. index::
single: Controlador; O ciclo de vida requisição-controlador-resposta
O Ciclo de Vida da Requisição, Controlador e Resposta
-----------------------------------------------------
Toda requisição tratada por um projeto com Symfony 2 passa pelo mesmo ciclo de
vida simples. O framework cuida das tarefas repetitivas e por fim executa um
controlador onde reside o código personalizado da sua aplicação:
#. Toda requisição é tratada por um único arquivo front controlador (por exemplo,
``app.php`` ou ``app_dev.php``) que inicializa a aplicação;
#. O ``Router`` lê a informação da requisição (por exemplo, a URI), encontra uma rota
que casa com aquela informação e lê o parâmetro ``_controller`` da rota;
#. O controlador que casou com a rota é executado e o código dentro do
controlador cria e retorna um objeto ``Response``;
#. Os cabeçalhos HTTP e o conteúdo do objeto ``Response`` são enviados de
volta para o cliente.
Criar uma página é tão fácil quanto criar um controlador (#3) e fazer uma rota
que mapeie uma URL para aquele controlador (#2).
.. note::
Embora tenha um nome similar, um "front controller" é diferente dos
"controladores" dos quais vamos falar nesse capítulo. Um front controller é
um pequeno arquivo PHP que fica no seu diretório web e através do qual
todas as requisições são direcionadas. Uma aplicação típica terá um front
controller de produção (por exemplo, ``app.php``) e um front controller de
desenvolvimento (por exemplo, ``app_dev.php``). Provavelmente você nunca precisará
editar, visualizar ou se preocupar com os front controllers da sua
aplicação.
.. index::
single: Controlador; Exemplo simples
Um Controlador Simples
----------------------
Embora um controlador possa ser qualquer código PHP que possa ser chamado (uma
função, um método em um objeto ou uma ``Closure``), no Symfony2 um controlador
geralmente é um único método dentro de um objeto controlador. Os controladores
também são chamados de *ações*:
.. code-block:: php
:linenos:
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return new Response('
Hello '.$name.'!');
}
}
.. tip::
Note que o *controlador* é o método ``indexAction``, que fica dentro de
uma *classe controladora* (``HelloController``). Não se confunda com a
nomenclatura: uma *classe controladora* é apenas um forma conveniente de
agrupar vários controladores/ações juntos. Geralmente a classe controladora
irá agrupar vários controladores/ações (por exemplo, ``updateAction``,
``deleteAction`` etc).
Esse controlador é bem simples, mas vamos explicá-lo:
* *linha 3*: O Symfony2 se beneficia da funcionalidade de namespace do PHP 5.3
colocando a classe controladora inteira dentro de um namespace. A palavra chave
``use`` importa a classe ``Response`` que nosso controlador tem que retornar.
* *linha 6*: O nome da classe é a concatenação de um nome para a classe
controlador (ou seja, ``Hello``) com a palavra ``Controller``. Essa é uma
convenção que fornece consistência aos controladores e permite que eles sejam
referenciados usando apenas a primeira parte do nome (ou seja, ``Hello``) na
configuração de roteamento.
* *linha 8*: Toda ação em uma classe controladora é sufixada com ``Action`` e
é referenciada na configuração de roteamento pelo nome da ação (``index``).
Na próxima seção, você criará uma rota que mapeia uma URI para essa action.
Você aprenderá como os marcadores de posição das rotas (``{name}``) tornam-se
argumentos no método da action (``$name``).
* *linha 10*: O controlador cria e retorna um objeto ``Response``.
.. index::
single: Controlador; Rotas e controladores
Mapeando uma URL para um Controlador
------------------------------------
O novo controlador retorna uma página HTML simples. Para ver realmente essa
página no seu navegador você precisa criar uma rota que mapeia um padrão
específico de URL para o controlador:
.. configuration-block::
.. code-block:: yaml
# app/config/routing.yml
hello:
pattern: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }
.. code-block:: xml
AcmeHelloBundle:Hello:index
.. code-block:: php
// app/config/routing.php
$collection->add('hello', new Route('/hello/{name}', array(
'_controller' => 'AcmeHelloBundle:Hello:index',
)));
Agora, acessar ``/hello/ryan`` executa o controlador
``HelloController::indexAction()`` e passa ``ryan`` para a variável ``$name``.
A criação de uma "página" significa simplesmente criar um método controlador e
associar uma rota.
Note a sintaxe usada para referenciar o controlador:
``AcmeHelloBundle:Hello:index``. O Symfony2 usa uma notação flexível de string
para referenciar diferentes controladores. Essa é a sintaxe mais comum e diz ao
Symfony2 para buscar por uma classe controladora chamada ``helloController``
dentro de um bundle chamado ``AcmeHelloBundle``. Então o método
``indexAction()`` é executado.
Para mais detalhes sobre o formato de string usado para referenciar diferentes
controladores, veja :ref:`controller-string-syntax`.
.. note::
Esse exemplo coloca a configuração de roteamento diretamente no diretório
``app/config/``. Uma forma melhor de organizar suas rotas é colocar cada
uma das rotas no bundle a qual elas pertencem. Para mais informações, veja
:ref:`routing-include-external-resources`.
.. tip::
Você pode aprender muito mais sobre o sistema de roteamento no
:doc:`capítulo Roteamento`.
.. index::
single: Controlador; Argumentos do Controlador
.. _route-parameters-controller-arguments:
Parâmetros de Rota como Argumentos do Controlador
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Você já sabe que o parâmetro ``_controller`` em ``AcmeHelloBundle:Hello:index``
se refere ao método ``HelloController::indexAction()`` que está dentro do
bundle ``AcmeHelloBundle``. O que é mais interessante são os argumentos que
são passados para o método:
.. code-block:: php
AcmeHelloBundle:Hello:index
green
.. code-block:: php
// app/config/routing.php
$collection->add('hello', new Route('/hello/{first_name}/{last_name}', array(
'_controller' => 'AcmeHelloBundle:Hello:index',
'color' => 'green',
)));
O controlador dessa rota pode receber vários argumentos:
public function indexAction($first_name, $last_name, $color)
{
// ...
}
Observe que tanto as variáveis de marcadores de posição (``{first_name}``,
``{last_name}``) quanto a váriavel padrão ``color`` estão disponíveis como
argumentos no controlador. Quando uma rota é casada, as variáveis marcadoras
de posição são mescladas com as variáveis ``default`` criando um array
que fica disponível para o seu controlador.
O mapeamento de parâmetros de rota com argumentos do controlador é fácil e
flexível. Tenha em mente as seguintes orientações enquanto estiver
desenvolvendo.
* **A ordem dos argumentos do controlador não importa**
O Symfony é capaz de casar os nomes dos parâmetros da rota com os nomes
das variáveis na assinatura do método do controlador. Em outras palavras,
ele sabe que o parâmetro ``{last_name}`` casa com o argumento
``$last_name``. Os argumentos do controlador podem ser totalmente
reordenados e continuam funcionando perfeitamente:
public function indexAction($last_name, $color, $first_name)
{
// ..
}
* **Todo argumento obrigatório do controlador tem que corresponder a um parâmetro de roteamento**
O seguinte deveria lançar uma ``RuntimeException`` porque não existe nenhum
parâmetro ``foo`` definido na rota:
public function indexAction($first_name, $last_name, $color, $foo)
{
// ..
}
Deixando o argumento opcional, no entanto, tudo corre bem. O
seguinte exemplo não lança uma exceção:
public function indexAction($first_name, $last_name, $color, $foo = 'bar')
{
// ..
}
* **Nem todos os parâmetros de roteamento precisam ser argumentos no seu controlador**
Se, por exemplo, ``last_name`` não for importante para o seu controlador,
você pode omitir inteiramente ele:
public function indexAction($first_name, $color)
{
// ..
}
.. tip::
Cada uma das rotas tem um parâmetro ``_route`` especial, que é igual ao
nome da rota que foi casada (por exemplo, ``hello``). Embora não seja útil
geralmente, ele também fica disponível como um argumento do controlador.
.. _book-controller-request-argument:
O ``Request`` como um Argumento do Controlador
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Por conveniência, você também pode fazer com que o Symfony passe o objeto
``Request`` como um argumento para seu controlador. Isso é conveniente
especialmente quando você estiver trabalhando com formulários, por exemplo:
use Symfony\Component\HttpFoundation\Request;
public function updateAction(Request $request)
{
$form = $this->createForm(...);
$form->bind($request);
// ...
}
.. index::
single: Controlador; Classe base do controlador
A Classe Base do Controlador
----------------------------
Por conveniência, o Symfony2 vem com uma classe ``Controller`` base que ajuda
com algumas das tarefas mais comuns dos controladores e fornece às suas classes
controladoras acesso à qualquer recurso que elas possam precisar. Estendendo essa
classe ``Controller``, você se beneficia com vários métodos helper.
Adicione a instrução ``use`` no topo da sua classe ``Controller`` e então
modifique o ``HelloController`` para estendê-lo:
.. code-block:: php
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController extends Controller
{
public function indexAction($name)
{
return new Response('Hello '.$name.'!');
}
}
Isso não muda realmente nada o jeito que seu controlador trabalha. Na próxima
seção você aprenderá sobre os métodos helper que a classe controladora base
disponibiliza. Esses métodos são apenas atalhos para usar funcionalidades do
núcleo do Symfony2 que estão disponíveis para você usando ou não a classe base
``Controller``. Uma boa maneira de ver a funcionalidade do núcleo em ação
é olhar a própria classe
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller`.
.. tip::
Estender a classe base é *opcional* no Symfony; ela contém atalhos úteis
mas nada que seja mandatório. Você também pode estender
``Symfony\Component\DependencyInjection\ContainerAware``. O objeto
contêiner de serviços então será acessível por meio da propriedade
``container``.
.. note::
Você também pode definir seus :doc:`Controllers como Serviços
`.
.. index::
single: Controlador; Tarefas Comuns
Tarefas Comuns dos Controladores
--------------------------------
Embora virtualmente um controlador possa fazer qualquer coisa, a maioria dos
controladores irão realizar as mesmas tarefas básicas repetidas vezes. Essas
tarefas, como redirecionamentos, direcionamentos, renderização de templates e
acesso a serviços nucleares são muitos fáceis de gerenciar no Symfony2.
.. index::
single: Controlador; Redirecionado
Redirecionando
~~~~~~~~~~~~~~
Se você quiser redirecionar o usuário para outra página, use o método
``redirect()``::
public function indexAction()
{
return $this->redirect($this->generateUrl('homepage'));
}
O método ``generateUrl()`` é apenas uma função helper que gera a URL de uma
determinada rota. Para mais informações, veja o capítulo
:doc:`Roteamento `.
Por padrão, o método ``redirect()`` efetua um redirecionamento 302
(temporário). Para realizar um redirecionamento 301 (permanente), modifique o
segundo argumento::
public function indexAction()
{
return $this->redirect($this->generateUrl('homepage'), 301);
}
.. tip::
O método ``redirect()`` é simplesmente um atalho que cria um objeto
``Response`` especializado em redirecionar o usuário. Ele é equivalente a:
.. code-block:: php
use Symfony\Component\HttpFoundation\RedirectResponse;
return new RedirectResponse($this->generateUrl('homepage'));
.. index::
single: Controlador; Direcionando
Direcionando
~~~~~~~~~~~~
Você também pode facilmente direcionar internamente para outro controlador com o
método ``forward()``. Em vez de redirecionar o navegador do usuário, ele faz
uma sub-requisição interna e chama o controlador especificado. O método
``forward()`` retorna o objeto ``Response`` que é retornado pelo controlador::
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green'
));
// pode modificar a resposta ou retorná-la diretamente
return $response;
}
Note que o método `forward()` usa a mesma representação em string do
controlador que foi usada na configuração de roteamento. Nesse caso, a classe
controlador alvo será ``HelloController`` dentro de ``AcmeHelloBundle``.
O array passado para o método se torna os argumentos no controlador
resultante. Essa mesma interface é usada quando se embutem controladores em
templates (veja :ref:`templating-embedding-controller`). O método controlador
alvo deve se parecer com o seguinte::
public function fancyAction($name, $color)
{
// ... cria e retorna um objeto Response
}
E da mesma forma, quando criamos um controlador para uma rota, a ordem dos
argumentos para ``fancyAction`` não importa. O Symfony2 combina os nomes
das chaves dos índices (por exemplo, ``name``) com os nomes dos argumentos do
método (por exemplo, ``$name``). Se você mudar a ordem dos argumentos, o Symfony2
continuará passando os valores corretos para cada variável.
.. tip::
Assim como em outros métodos do ``Controller`` base, o método ``forward`` é
apenas um atalho para uma funcionalidade nuclear do Symfony2. Um
direcionamento pode ser realizado diretamente por meio do serviço
``http_kernel``. Um direcionamento retorna um objeto ``Response``::
$httpKernel = $this->container->get('http_kernel');
$response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
.. index::
single: Controlador; Renderizando templates
.. _controller-rendering-templates:
Renderizando Templates
~~~~~~~~~~~~~~~~~~~~~~
Apesar de não ser um requisito, a maioria dos controladores irá, no fim das contas,
renderizar um template que é responsável por gerar o HTML (ou outro formato)
para o controlador. O método ``renderView()`` renderiza um template e retorna
seu conteúdo. O conteúdo do template pode ser usado para criar um objeto
``Response``::
$content = $this->renderView('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
return new Response($content);
Isso pode ser feito até em um único passo usando o método ``render()``, que
retorna um objeto ``Response`` com o conteúdo do template::
return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
Em ambos os casos, o template ``Resources/views/Hello/index.html.twig`` dentro
do ``AcmeHelloBundle`` será renderizado.
O sistema de template do Symfony é explicado com mais detalhes no capítulo
:doc:`Templating `.
.. tip::
O método ``renderView`` é um atalho para usar diretamente o serviço
``templating``. O serviço ``templating`` também pode ser usado
diretamente::
$templating = $this->get('templating');
$content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
.. index::
single: Controlador; Acessando Serviços
Acessando outros Serviços
~~~~~~~~~~~~~~~~~~~~~~~~~
Quando se estende a classe controladora base, você pode acessar qualquer um dos
serviços Symfony2 através do método ``get()``. Aqui estão alguns dos serviços
mais comuns que você pode precisar::
$request = $this->getRequest();
$templating = $this->get('templating');
$router = $this->get('router');
$mailer = $this->get('mailer');
Existem outros inúmeros serviços disponíveis e você é encorajado a definir os
seus próprios. Para listar todos os serviços disponíveis, use o comando do
console ``container:debug``:
.. code-block:: bash
php app/console container:debug
Para mais informações, veja o capítulo :doc:`/book/service_container`.
.. index::
single: Controlador; Gerenciando Erros
single: Controlador; Páginas 404
Gerenciando Erros e Páginas 404
-------------------------------
Quando algo não for encontrado, você deve usar de forma correta o protocolo
HTTP e retornar uma resposta 404. Para isso, você lançará um tipo especial de
exceção. Se estiver estendendo a classe controlador base, faça o seguinte::
public function indexAction()
{
$product = // retrieve the object from database
if (!$product) {
throw $this->createNotFoundException('The product does not exist');
}
return $this->render(...);
}
O método ``createNotFoundException()`` cria um objeto especial
``NotFoundHttpException``, que no fim dispara uma resposta HTTP 404 de dentro
do Symfony.
É lógico que você é livre para lançar qualquer classe ``Exception`` no seu
controlador - o Symfony irá retornar automaticamente uma resposta HTTP código
500.
.. code-block:: php
throw new \Exception('Something went wrong!');
Em todo caso, uma página de erro estilizada é mostrada para o usuário final e
uma página de erro com informações de debug completa é mostrada para o
desenvolvedor (no caso de visualizar a página no modo debug). Ambas as páginas
podem ser personalizadas. Para detalhes, leia a receita
":doc:`/cookbook/controller/error_pages`" no cookbook.
.. index::
single: Controlador; A Sessão
single: Sessão
Gerenciando a Sessão
--------------------
O Symfony2 fornece um objeto de sessão muito bom que você pode usar para
guardar informações sobre o usuário (seja ele uma pessoa real usando um
navegador, um robô ou um web service) entre requisições. Por padrão, o
Symfony2 guarda os atributos em um cookie usando as sessões nativas do PHP.
O armazenamento e a recuperação de informações da sessão são feitos
facilmente de qualquer controlador::
$session = $this->getRequest()->getSession();
// store an attribute for reuse during a later user request
$session->set('foo', 'bar');
// in another controller for another request
$foo = $session->get('foo');
// use a default value if the key doesn't exist
$filters = $session->get('filters', array());
Esses atributos permanecerão no usuário até o fim da sessão.
.. index::
single Session; Flash messages
Mensagens Flash
~~~~~~~~~~~~~~~
Você também guardar pequenas mensagens que serão armazenadas na sessão do
usuário apenas por uma requisição. Isso é útil no processamento de formulários:
você pode redirecionar o usuário e mostrar uma mensagem especial na requisição
*seguinte*. Esses tipos de mensagens são chamadas de mensagens "flash".
Por exemplo, imagine que você esteja processando a submissão de um formulário::
public function updateAction()
{
$form = $this->createForm(...);
$form->bind($this->getRequest());
if ($form->isValid()) {
// do some sort of processing
$this->get('session')->getFlashBag()->add('notice', 'Your changes were saved!');
return $this->redirect($this->generateUrl(...));
}
return $this->render(...);
}
Depois do processamento da requisição, o controlador define uma mensagem flash
`notice` e então faz o redirecionamento. O nome (``notice``) não é
importante - é apenas o que você usa para identificar o tipo da mensagem.
No template da próxima action, o código a seguir poderia ser usado para
renderizar a mensagem ``notice``:
.. configuration-block::
.. code-block:: html+jinja
{% for flashMessage in app.session.flashbag.get('notice') %}
{{ flashMessage }}
{% endfor %}
.. code-block:: php
getFlashBag()->get('notice') as $message): ?>
$message
" ?>
Por definição, as mensagens flash são feitas para existirem por exatamente uma
requisição (elas "se vão num instante" - "gone in a flash"). Elas foram
projetadas para serem usadas entre redirecionamentos exatamente como você fez
nesse exemplo.
.. index::
single: Controlador; Objeto Response
O Objeto Response
-----------------
O único requisito de um controlador é retornar um objeto ``Response``. A classe
:class:`Symfony\\Component\\HttpFoundation\\Response` é uma abstração PHP em
volta da resposta HTTP - a mensagem em texto cheia de cabeçalhos HTTP e
conteúdo que é mandado de volta para o cliente::
// create a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, 200);
// create a JSON-response with a 200 status code
$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');
.. tip::
A propriedade ``headers`` é a classe
:class:`Symfony\\Component\\HttpFoundation\\HeaderBag` com vários métodos
úteis para ler e modificar os cabeçalhos do ``Response``. Os nomes dos
cabeçalhos são normalizados de forma que usar ``Content-Type`` seja
equivalente a ``content-type`` ou mesmo ``content_type``.
.. index::
single: Controlador; Objeto Request
O Objeto Request
----------------
Além dos valores nos marcadores de roteamento, o controlador também tem acesso
ao objeto ``Request`` quando está estendendo a classe ``Controller`` base::
$request = $this->getRequest();
$request->isXmlHttpRequest(); // is it an Ajax request?
$request->getPreferredLanguage(array('en', 'fr'));
$request->query->get('page'); // get a $_GET parameter
$request->request->get('page'); // get a $_POST parameter
Assim como com o objeto ``Response``, os cabeçalhos da requisição são guardados
em um objeto ``HeaderBag`` e são facilmente acessados.
Considerações Finais
--------------------
Sempre que criar uma página, no final você precisará escrever algum código que
contenha a lógica dessa página. No Symfony, isso é chamado de controlador,
e ele é uma função PHP que faz tudo que for necessário para no fim retornar
o objeto ``Response`` final que será retornado ao usuário.
Para facilitar a vida, você pode escolher estender uma classe ``Controller``
base, que contém métodos que são atalhos para muitas tarefas comuns dos
controladores. Por exemplo, uma vez que você não queira colocar código HTML
no seu controlador, você pode usar o método ``render()`` para renderizar e
retornar o conteúdo de um template.
Em outros capítulos, você verá como o controlador pode ser usado para persistir
e buscar objetos em um banco de dados, processar submissões de formulários,
gerenciar cache e muito mais.
Saiba mais no Cookbook
----------------------
* :doc:`/cookbook/controller/error_pages`
* :doc:`/cookbook/controller/service`