Beruflich Dokumente
Kultur Dokumente
Verso 2.4
symfony
16/09/2014
Sumrio
1
1
Livro
2.1 Livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
25
Cookbook
239
3.1 Cookbook . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Componentes
433
4.1 Os Componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 433
Documentos de Referncia
441
5.1 Documentos de Referncia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Bundles
445
6.1 Bundles da Edio Standard do Symfony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445
Contribuindo
447
7.1 Contribuindo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447
ii
CAPTULO 1
Baixando o Symfony2
Primeiro, verifique se voc tem um servidor web instalado e configurado (como o Apache) com a verso mais recente
possvel do PHP ( recomendado o PHP 5.3.8 ou mais recente).
Pronto? Comece fazendo o download da Edio Standard do Symfony2, uma distribuio do Symfony que prconfigurada para os casos de uso mais comuns e contm tambm um cdigo que demonstra como usar Symfony2
(obtenha o arquivo com os vendors includos para comear ainda mais rpido).
Aps descompactar o arquivo no seu diretrio raiz do servidor web, voc deve ter um diretrio Symfony/ parecido
com o seguinte:
www/ <- your web root directory
Symfony/ <- the unpacked archive
app/
cache/
config/
logs/
Resources/
bin/
src/
Acme/
DemoBundle/
Controller/
Resources/
...
vendor/
symfony/
doctrine/
...
web/
app.php
...
Nota: Se voc est familiarizado com o Composer, poder executar o seguinte comando em vez de baixar o arquivo:
$ composer.phar create-project symfony/framework-standard-edition path/to/install 2.1.x-dev
# remove the Git history
$ rm -rf .git
Para uma verso exata, substitua 2.1.x-dev com a verso mais recente do Symfony
(Ex. 2.1.1). Para detalhes, veja a Pgina de Instalao do Symfony_
Dica: Se voc tem o PHP 5.4, possvel usar o servidor web integrado:
# check your PHP CLI configuration
$ php ./app/check.php
# run the built-in web server
$ php ./app/console server:run
Verificando a configurao
O Symfony2 vem com uma interface visual para teste da configurao do servidor que ajuda a evitar algumas dores
de cabea que originam da m configurao do servidor Web ou do PHP. Use a seguinte URL para ver o diagnstico
para a sua mquina:
http://localhost/config.php
Nota: Todos as URLs de exemplo assumem que voc baixou e descompactou o Symfony diretamente no diretrio
raiz web do seu servidor web. Se voc seguiu as instrues acima e descompactou o diretrio Symfony em seu raiz
web, ento, adicione /Symfony/web aps localhost em todas as URLs:
http://localhost/Symfony/web/config.php
Se houver quaisquer questes pendentes informadas, corrija-as. Voc tambm pode ajustar a sua configurao, seguindo todas as recomendaes. Quando tudo estiver certo, clique em Bypass configuration and go to the Welcome
page para solicitar a sua primeira pgina real do Symfony2:
http://localhost/app_dev.php/
Compreendendo os Fundamentos
Um dos objetivos principais de um framework garantir a Separao de Responsabilidades. Isso mantm o seu cdigo
organizado e permite que a sua aplicao evolua facilmente ao longo do tempo, evitando a mistura de chamadas ao
banco de dados, de tags HTML e de lgica de negcios no mesmo script. Para atingir este objetivo com o Symfony,
primeiro voc precisa aprender alguns conceitos e termos fundamentais.
Dica: Quer uma prova de que o uso de um framework melhor do que misturar tudo no mesmo script? Leia o
captulo Symfony2 versus o PHP puro do livro.
A distribuio vem com um cdigo de exemplo que voc pode usar para aprender mais sobre os principais conceitos
do Symfony2. V para a seguinte URL para ser cumprimentado pelo Symfony2 (substitua Fabien pelo seu primeiro
nome):
http://localhost/app_dev.php/demo/hello/Fabien
O Symfony2 encaminha a solicitao para o cdigo que lida com ela, tentando corresponder a URL solicitada contra alguns padres configurados. Por predefinio, esses padres (chamados de rotas) so definidos no arquivo de
configurao app/config/routing.yml. Quando voc est no ambiente dev - indicado pelo front controller
app_**dev**.php - o arquivo de configurao app/config/routing_dev.yml tambm carregado. Na Edio
Standard, as rotas para estas pginas demo so colocadas no arquivo:
# app/config/routing_dev.yml
_welcome:
pattern: /
defaults: { _controller: AcmeDemoBundle:Welcome:index }
_demo:
resource: "@AcmeDemoBundle/Controller/DemoController.php"
type:
annotation
prefix:
/demo
# ...
As trs primeiras linhas (aps o comentrio) definem o cdigo que executado quando o usurio solicita o recurso / (ou seja, a pgina de boas-vindas que voc viu anterioremente). Quando solicitado, o controlador
AcmeDemoBundle:Welcome:index ser executado. Na prxima seo, voc vai aprender exatamente o que
isso significa.
Dica: A Edio Standard do Symfony2 usa YAML para seus arquivos de configurao, mas o Symfony2 tambm
suporta XML, PHP e anotaes nativamente. Os diferentes formatos so compatveis e podem ser utilizados alternadamente dentro de uma aplicao. Alm disso, o desempenho de sua aplicao no depende do formato de configurao
que voc escolher, pois tudo armazenado em cache na primeira solicitao.
Controladores
Controlador um nome fantasia para uma funo ou mtodo PHP que manipula as solicitaes de entrada e retorna
respostas (cdigo HTML, na maioria das vezes). Em vez de usar as variveis globais e funes do PHP (como
$_GET ou header()) para gerenciar essas mensagens HTTP, o Symfony usa objetos: Request e Response. O
controlador mais simples possvel pode criar a resposta manualmente, com base na solicitao:
use Symfony\Component\HttpFoundation\Response;
$name = $request->query->get(name);
return new Response(Hello .$name, 200, array(Content-Type => text/plain));
Nota: O Symfony2 engloba a Especificao HTTP, que so as regras que regem toda a comunicao na Web. Leia o
captulo Fundamentos de Symfony e HTTP do livro para aprender mais sobre ela e o poder que isso acrescenta.
O Symfony2 escolhe o controlador com base no valor _controller da configurao de roteamento:
AcmeDemoBundle:Welcome:index. Esta string o nome lgico do controlador, e ela referencia o mtodo
indexAction da classe Acme\DemoBundle\Controller\WelcomeController:
// src/Acme/DemoBundle/Controller/WelcomeController.php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class WelcomeController extends Controller
{
public function indexAction()
{
return $this->render(AcmeDemoBundle:Welcome:index.html.twig);
}
}
Dica:
Voc poderia ter usado o nome completo da classe e do mtodo Acme\DemoBundle\Controller\WelcomeController::indexAction - para o valor _controller.
Mas, se voc seguir algumas convenes simples, o nome lgico menor e permite mais flexibilidade.
A classe WelcomeController estende a classe nativa Controller, que fornece mtodos de atalho teis, tal como o mtodo render() que carrega e renderiza um template
(AcmeDemoBundle:Welcome:index.html.twig). O valor retornado um objeto Response populado
com o contedo processado. Assim, se as necessidades surgirem, o Response pode ser ajustado antes de ser enviado
ao navegador:
public function indexAction()
{
$response = $this->render(AcmeDemoBundle:Welcome:index.txt.twig);
$response->headers->set(Content-Type, text/plain);
return $response;
}
No importa como voc faz isso, o objetivo final do seu controlador sempre ser retornar o objeto Response que
deve ser devolvido ao usurio. Este objeto Response pode ser populado com cdigo HTML, representar um redirecionamento do cliente, ou mesmo retornar o contedo de uma imagem JPG com um cabealho Content-Type de
image/jpg.
Dica: Estender a classe base Controller opcional. De fato um controlador pode ser uma funo PHP simples
ou at mesmo uma closure PHP. O captulo O Controlador do livro lhe ensina tudo sobre os controladores do
Symfony2.
O nome do template, AcmeDemoBundle:Welcome:index.html.twig, o nome lgico do template e faz
referncia ao arquivo Resources/views/Welcome/index.html.twig dentro do AcmeDemoBundle (localizado em src/Acme/DemoBundle). A seo bundles abaixo ir explicar porque isso til.
Agora, d uma olhada novamente na configurao de roteamento e encontre a chave _demo.
# app/config/routing_dev.yml
_demo:
resource: "@AcmeDemoBundle/Controller/DemoController.php"
type:
annotation
prefix:
/demo
@AcmeDemoBundle/Controller/DemoController.php
e
refere-se
ao
arquivo
src/Acme/DemoBundle/Controller/DemoController.php . Neste arquivo, as rotas so definidas
como anotaes nos mtodos da ao:
// src/Acme/DemoBundle/Controller/DemoController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class DemoController extends Controller
{
/**
* @Route("/hello/{name}", name="_demo_hello")
* @Template()
*/
public function helloAction($name)
{
return array(name => $name);
}
// ...
}
A anotao @Route() define uma nova rota com um padro /hello/{name} que executa o mtodo
helloAction quando corresponder. A string entre chaves como {name} chamada de placeholder. Como voc
pode ver, o seu valor pode ser obtido atravs do argumento do mtodo $name.
Nota: Mesmo as anotaes no sendo suportadas nativamente pelo PHP, voc as usar extensivamente no Symfony2
como uma forma conveniente de configurar o comportamento do framework e manter a configurao prxima ao
cdigo.
Se voc verificar o cdigo do controlador, poder ver que em vez de renderizar um template e retornar um objeto
Response como antes, ele apenas retorna um array de parmetros. A anotao @Template() diz ao Symfony para
renderizar o template para voc, passando cada varivel do array ao template. O nome do template que renderizado
segue o nome do controlador. Assim, neste exemplo, o template AcmeDemoBundle:Demo:hello.html.twig
renderizado (localizado em src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig).
Dica: As anotaes @Route() e @Template() so mais poderosas do que os exemplos simples mostrados neste
tutorial. Saiba mais sobre anotaes em controladores_ na documentao oficial.
Templates
Por padro, o Symfony2 usa o Twig como seu template engine, mas voc tambm pode usar templates tradicionais
PHP se voc escolher. No prximo captulo apresentaremos como os templates funcionam no Symfony2.
Bundles
Voc pode ter se perguntado por que a palavra bundle usada em muitos nomes que vimos at agora. Todo o cdigo
que voc escreve para a sua aplicao est organizado em bundles. Na forma de falar do Symfony2, um bundle um
conjunto estruturado de arquivos (arquivos PHP, folhas de estilo, JavaScripts, imagens, ...) que implementam uma funcionalidade nica (um blog, um frum, ...) e que podem ser facilmente compartilhados com outros desenvolvedores.
At agora, manipulamos um bundle, AcmeDemoBundle. Voc vai aprender mais sobre bundles no ltimo captulo
deste tutorial.
Trabalhando com Ambientes
Agora que voc tem uma compreenso melhor de como funciona o Symfony2, verifique a parte inferior de qualquer
pgina renderizada com o Symfony2. Voc deve observar uma pequena barra com o logotipo do Symfony2. Isso
chamado de Barra de ferramentas para Debug Web e a melhor amiga do desenvolvedor.
Mas, o que voc v inicialmente apenas a ponta do iceberg; clique sobre o estranho nmero hexadecimal para revelar
mais uma ferramenta de depurao muito til do Symfony2: o profiler.
claro, voc no vai querer mostrar essas ferramentas quando implantar a sua aplicao em produo. por isso
que voc vai encontrar um outro front controller no diretrio web/ (app.php), que otimizado para o ambiente de
produo:
http://localhost/app.php/demo/hello/Fabien
E, se voc usar o Apache com o mod_rewrite habilitado, poder at omitir a parte app.php da URL:
http://localhost/demo/hello/Fabien
Por ltimo, mas no menos importante, nos servidores de produo, voc deve apontar seu diretrio raiz web para o
diretrio web/ para proteger sua instalao e ter uma URL ainda melhor:
http://localhost/demo/hello/Fabien
Nota: Note que as trs URLs acima so fornecidas aqui apenas como exemplos de como uma URL parece quando
o front controller de produo usado (com ou sem mod_rewrite). Se voc realmente experiment-los em uma
instalao do Symfony Standard Edition voc receber um erro 404 pois o AcmeDemoBundle est habilitado somente
no ambiente dev e suas rotas importam o app/config/routing_dev.yml.
Para fazer a sua aplicao responder mais rpido, o Symfony2 mantm um cache sob o diretrio app/cache/. No
ambiente de desenvolvimento (app_dev.php), esse cache liberado automaticamente sempre que fizer alteraes
em qualquer cdigo ou configurao. Mas esse no o caso do ambiente de produo (app.php) onde o desempenho
fundamental. por isso que voc deve sempre usar o ambiente de desenvolvimento ao desenvolver a sua aplicao.
Diferentes ambientes de uma dada aplicao diferem apenas na sua configurao. Na verdade, uma configurao pode
herdar de outra:
# app/config/config_dev.yml
imports:
- { resource: config.yml }
web_profiler:
toolbar: true
intercept_redirects: false
O ambiente dev (que carrega o arquivo de configurao config_dev.yml) importa o arquivo global
config.yml e, em seguida, modifica-o, neste exemplo, habilitando a barra de ferramentas para debug web.
Consideraes Finais
Parabns! Voc j teve a sua primeira amostra de cdigo do Symfony2. Isso no foi to difcil, foi? H muito mais para
explorar, mas, voc j deve ter notado como o Symfony2 torna muito fcil implementar web sites de forma melhor e
mais rpida. Se voc est ansioso para aprender mais sobre o Symfony2, mergulhe na prxima seo: A Viso.
1.1.2 A View
Depois de ler a primeira parte desse tutorial, voc decidiu que o Symfony2 vale pelo menos mais 10 minutos? Boa
escolha! Nessa segunda parte, voc vai aprender sobre o sistema de template do Symfony2, o Twig. Ele um sistema
de templates para PHP flexvel, rpido e seguro. Ele faz com que seus templates sejam mais legveis e concisos e
tambm os torna mais amigveis para os web designers.
Nota: Em vez do Twig, voc tambm pode usar PHP para os seus templates. Ambos so suportados pelo Symfony2.
{% ...
%}: Controla a lgica do template; usado para executar loops for e instrues if, por exemplo.
Abaixo temos um template mnimo que ilustra alguns comandos bsicos usando as duas vriaveis, page_title e
navigation, que so passadas para o template:
<!DOCTYPE html>
<html>
<head>
<title>My Webpage</title>
</head>
<body>
<h1>{{ page_title }}</h1>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>
</body>
</html>
Dica: Podem ser includos comentrios nos templates usando o delimitador {# ...
#}.
Para renderizar um template no Symfony, use o mtodo render a partir do controller, e passe para ele todas as
varivels necessrias ao template:
$this->render(AcmeDemoBundle:Demo:hello.html.twig, array(
name => $name,
));
As variveis passadas para o template podem ser strings, arrays ou at objetos. O Twig abstrai a diferena entre eles e
deixa acessar os atributos de uma varivel usando dot notation (.):
{# array(name => Fabien) #}
{{ name }}
{# array(user => array(name => Fabien)) #}
{{ user.name }}
{# force array lookup #}
{{ user[name] }}
{# array(user => new User(Fabien)) #}
{{ user.name }}
{{ user.getName }}
{# force method name lookup #}
{{ user.name() }}
{{ user.getName() }}
{# pass arguments to a method #}
{{ user.date(Y-m-d) }}
Nota: importante saber que as chaves no fazem parte da varivel mas sim do comando de impresso. Se voc
acessar variveis em tags no coloque as chaves em volta delas.
10
Decorando os Templates
frequente em um projeto que os templates compartilhem elementos comuns, como os bem-conhecidos cabealho e
rodap. No Symfony2, gostamos de enxergar essa situao de uma forma diferente: um template pode ser decorado
por outro. Funciona exatamente do mesmo jeito que nas classes PHP: a herana de templates permite que se construa
o template base layout, que contm todos os elementos comuns do seu site, e define blocos que os templates filhos
podem sobrescrever.
O template hello.html.twig herda do layout.html.twig, graas a tag extends:
{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}
{% extends "AcmeDemoBundle::layout.html.twig" %}
{% block title "Hello " ~ name %}
{% block content %}
<h1>Hello {{ name }}!</h1>
{% endblock %}
As tags {% block %} definem blocos que os templates filhos podem preencher. Tudo o que essas tags fazem dizer
ao sistema de template que um filho pode sobrescrever aquelas partes de seu template pai.
Nesse exemplo, o template hello.html.twig sobrescreve o bloco content, que significa que o
texto Hello Fabien renderizado dentro do elemento div.symfony-content
Usando Tags, Filtros e Funes
Uma das melhores funcionalidades do Twig sua extensibilidade por meio de tags, filtros e funes. O Symfony2 j
vem com muitos desses embutidos facilitando o trabalho do designer de templates.
Incluindo outros Templates
A melhor forma de compartilhar um trecho de cdigo entre vrios templates distintos criar um novo desses que possa
ser includo nos outros.
Crie um template embedded.html.twig:
{# src/Acme/DemoBundle/Resources/views/Demo/embedded.html.twig #}
Hello {{ name }}
11
{% block content %}
{% include "AcmeDemoBundle:Demo:embedded.html.twig" %}
{% endblock %}
E o que fazer se voc quiser incorporar o resultado de um outro controller em um template? Isso muito til quando
estiver trabalhado com Ajax, ou quando o template incorporado precisa de alguma varivel que no est disponvel no
template principal.
Suponha que voc tenha criado uma action fancy, e quer inclu-la dentro do template index. Para fazer isso, use a
tag render:
{# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #}
{% render "AcmeDemoBundle:Demo:fancy" with { name: name, color: green } %}
Quando estamos falando de aplicaes web, a criao de links entre pginas uma obrigao. Em vez de fazer
hardcode das URLS nos templates, usamos a funo path que sabe como gerar URLs baseando-se na configurao
das rotas. Dessa forma, todas as URLs podem ser atualizadas facilmente apenas mudando essa configurao:
<a href="{{ path(_demo_hello, { name: Thomas }) }}">Greet Thomas!</a>
A funo path pega o nome da rota e um array de parmetros como argumentos. O nome da rota a chave principal
sob a qual as rotas so referenciadas e os parmetros so os valores dos marcadores definidos no padro da rota:
// src/Acme/DemoBundle/Controller/DemoController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* @Route("/hello/{name}", name="_demo_hello")
* @Template()
*/
public function helloAction($name)
{
12
Thomas }) }}.
O que seria da Internet sem as imagens, os JavaScripts e as folhas de estilo? O Symfony2 fornece a funo asset
para lidar com eles de forma fcil:
<link href="{{ asset(css/blog.css) }}" rel="stylesheet" type="text/css" />
<img src="{{ asset(images/logo.png) }}" />
O objetivo principal da funo asset deixar sua aplicao mais porttil. Graas a ela, voc pode mover o diretrio
raiz da aplicao para qualquer lugar no diretrio web root sem mudar nem uma linha no cdigo de seus templates.
Escapando Variveis
O Twig configurado por padro para escapar automaticamente toda a sada de dados. Leia a documentao do Twig
para aprender mais sobre como escapar a sada de dados e sobre a extenso Escaper.
Consideraes Finais
O Twig simples mas poderoso. Graas a incluso de layouts, blocos, templates e actions, muito fcil organizar seus
templates de uma maneira lgica e extensvel. No entanto se voc no estiver confortvel com o Twig sempre poder
usar templates PHP no Symfony sem problemas.
Voc est trabalhando com o Symfony2 h apenas 20 minutos, mas j pode fazer coisas incrveis com ele. Esse o
poder do Symfony2. Aprender a base fcil, e logo voc aprender que essa simplicidade est escondida debaixo de
uma arquitetura muito flexvel.
Mas eu j estou me adiantando. Primeiro, voc precisa aprender mais sobre o controller e esse exatamente o assunto
da prxima parte do tutorial. Pronto para mais 10 minutos de Symfony2?
1.1.3 O Controller
Ainda est com a gente depois das primeiras duas partes? Ento voc j est se tornando um viciado no Symfony2!
Sem mais delongas, vamos descobrir o que os controllers podem fazer por voc.
Usando Formatos
Atualmente, uma aplicao web deve ser capaz de entregar mais do que apenas pginas HTML. Desde XML para
feeds RSS ou Web Services, at JSON para requisies Ajax, existem muitos formatos diferentes para escolher. Dar
suporte para esses formatos no Symfony2 simples. s ajustar a rota, como aqui que acrescentando um valor padro
xml para a varivel _format:
// src/Acme/DemoBundle/Controller/DemoController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
13
/**
* @Route("/hello/{name}", defaults={"_format"="xml"}, name="_demo_hello")
* @Template()
*/
public function helloAction($name)
{
return array(name => $name);
}
Usando o formato de requisio (como definido pelo valor _format), o Symfony2 automaticamente seleciona o
template correto, nesse caso o hello.xml.twig:
<!-- src/Acme/DemoBundle/Resources/views/Demo/hello.xml.twig -->
<hello>
<name>{{ name }}</name>
</hello>
Isso tudo. Para os formatos padro, o Symfony2 tambm ir escolher automaticamente o melhor cabealho
Content-Type para a resposta. Se voc quiser dar suporte para diferentes formatos numa nica action, em vez
disso use o marcador {_format} no padro da rota:
// src/Acme/DemoBundle/Controller/DemoController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, requirements={"_format"="html|xml|j
* @Template()
*/
public function helloAction($name)
{
return array(name => $name);
}
por
URLs
parecidas
com
/demo/hello/Fabien.xml
ou
A entrada requirements define expresses regulares que os marcadores precisam casar. Nesse exemplo, se voc
tentar requisitar /demo/hello/Fabien.js ir receber um erro HTTP 404 pois a requisio no casa com o
requisito _format.
Redirecionamento e Encaminhamento
Se voc quiser redirecionar o usurio para outra pgina, use o mtodo redirect():
return $this->redirect($this->generateUrl(_demo_hello, array(name => Lucas)));
O mtodo generateUrl() o mesmo que a funo path() que usamos nos templates. Ele pega o nome da rota
e um array de parmetros como argumentos e retorna a URL amigvel associada.
Voc tambm pode facilmente encaminhar a action para uma outra com o mtodo forward(). Internamente, o
Symfony faz uma sub-requisio, e retorna o objeto Response daquela sub-requisio:
14
Em um template, voc tambm pode acessar o objeto Request via a varivel app.request:
{{ app.request.query.get(page) }}
{{ app.request.parameter(page) }}
Voc pode guardar pequenas mensagens que ficaro disponveis apenas para a prxima requisio:
// guarda uma mensagem para a prxima requisio somente (em um controller)
$session->getFlashBag()->add(notice, Congratulations, your action succeeded!);
// exibe quaisquer mensagens no prximo pedido (no template)
{% for flashMessage in app.session.flashbag.get(notice) %}
<div>{{ flashMessage }}</div>
{% endfor %}
Isso til quando voc precisa definir uma mensagem de sucesso antes de redirecionar o usurio para outra pgina
(que ento mostrar a mensagem). Por favor note que quando voc usa has() ao invs de get(), a mensagem flash no
ser apagada e, assim, permanece disponvel durante os pedidos seguintes.
15
Protegendo Recursos
A verso Standard Edition do Symfony vem com uma configurao de segurana simples que atende as necessidades
mais comuns:
# app/config/security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN:
ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory:
memory:
users:
user: { password: userpass, roles: [ ROLE_USER ] }
admin: { password: adminpass, roles: [ ROLE_ADMIN ] }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/demo/secured/login$
security: false
secured_area:
pattern:
^/demo/secured/
form_login:
check_path: /demo/secured/login_check
login_path: /demo/secured/login
logout:
path:
/demo/secured/logout
target: /demo/
Essa configurao requer que os usurios se autentiquem para acessar qualquer URL comeada por
/demo/secured/ e define dois usurios vlidos: user e admin. Alm disso o usurio admin tem uma permisso ROLE_ADMIN, que tambm inclui a permisso ROLE_USER (veja a configurao role_hierarchy).
Dica: Para melhorar a legibilidade, nessa nossa configurao simplificada as senhas so guardadas em texto puro,
mas voc pode usar algum algoritmo de hash ajustando a seo encoders.
Indo para a URL http://localhost/app_dev.php/demo/secured/hello voc ser automaticamente
redirecionado para o formulrio de login pois o recurso protegido por um firewall.
Voc tambm pode forar a action para requisitar uma permisso especial usando a annotation @Secure no controller:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use JMS\SecurityExtraBundle\Annotation\Secure;
/**
* @Route("/hello/admin/{name}", name="_demo_secured_hello_admin")
* @Secure(roles="ROLE_ADMIN")
16
* @Template()
*/
public function helloAdminAction($name)
{
return array(name => $name);
}
Agora, se autentique como user (que no tem a permisso ROLE_ADMIN) e, a partir da pgina protegida hello,
clique no link Hello resource secured. O Symfony2 deve retornar um erro HTTP 403, indicando que o usurio est
proibido de acessar o recurso.
Nota: A camada de segurana do Symfony2 bem flexvel e vem com muitos servios de usurios (como no Doctrine
ORM) e autenticao (como HTTP bsico, HTTP digest ou certificados X509). Leia o captulo Segurana do livro
para mais informao de como us-los e configur-los.
Nesse exemplo, o recurso ficar em cache por um dia. Mas voc tambm pode usar validaes em vez de expirao,
ou uma combinao de ambos, se isso se encaixar melhor nas suas necessidades.
O cache de recursos gerenciado pelo proxy reverso embutido no Symfony2. Mas como o cache gerenciado usando
cabealhos de cache HTTP normais, voc pode substituir o proxy reverso com o Varnish ou o Squid e estender a sua
aplicao de forma fcil.
Nota: Mas como se virar se voc no puder fazer cache de pginas inteiras? O Symfony2 continua tendo a soluo,
via Edge Side Includes (ESI), que so suportados nativamente. Aprenda mais sobre isso lendo o captulo HTTP
Cache do livro.
Consideraes Finais
Isso foi tudo, e acho que no gastamos nem 10 minutos. Fizemos uma breve introduo aos bundles na primeira parte
e todas as funcionalidades sobre as quais aprendemos at agora so parte do bundle ncleo do framework. Graas aos
bundles, tudo no Symfony2 pode ser estendido ou substitudo. Esse o tema da prxima parte do tutorial.
17
1.1.4 A Arquitetura
Voc meu heri! Quem imaginaria que voc ainda estaria aqui aps as trs primeiras partes? Seus esforos sero
bem recompensados em breve. As trs primeiras partes no contemplaram profundamente a arquitetura do framework.
Porque ela faz o Symfony2 destacar-se na multido de frameworks, vamos mergulhar na arquitetura agora.
Compreendendo a estrutura de diretrio
A estrutura de diretrio de uma aplicao do Symfony2 bastante flexvel, mas a estrutura do diretrio da distribuio
Standard Edition reflete a estrutura tpica e recomendada de uma aplicao Symfony2:
app/: A configurao da aplicao;
src/: O cdigo PHP do projeto;
vendor/: As dependncias de terceiros;
web/: O diretrio raiz web.
O Diretrio web/
O diretrio raiz web o local de todos os arquivos pblicos e estticos, como imagens, folhas de estilo e arquivos
JavaScript. tambm o local onde cada front controller reside:
// web/app.php
require_once __DIR__./../app/bootstrap.php.cache;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(prod, false);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();
O kernel primeiro solicita o arquivo bootstrap.php.cache, que inicializa a estrutura e regista o autoloader (veja
abaixo).
Como qualquer front controller, o app.php usa uma classe Kernel, AppKernel, para a inicializao da aplicao.
O Diretrio app/
A classe AppKernel o principal ponto de entrada da configurao da aplicao e, como tal, ele armazenado no
diretrio app/.
Essa classe deve implementar dois mtodos:
registerBundles() que deve retornar um array de todos os bundles necessrios para executar a aplicao.
registerContainerConfiguration() que carrega a configurao da aplicao (veremos mais sobre
isso depois).
O autoloading do PHP pode ser configurado via app/autoload.php:
// app/autoload.php
use Symfony\Component\ClassLoader\UniversalClassLoader;
$loader = new UniversalClassLoader();
$loader->registerNamespaces(array(
18
Symfony
Sensio
JMS
Doctrine\\Common
Doctrine\\DBAL
Doctrine
Monolog
Assetic
Metadata
=>
=>
=>
=>
=>
=>
=>
=>
=>
array(__DIR__./../vendor/symfony/symfony/src, __DIR__./../vendor/bundles
__DIR__./../vendor/bundles,
__DIR__./../vendor/jms/,
__DIR__./../vendor/doctrine/common/lib,
__DIR__./../vendor/doctrine/dbal/lib,
__DIR__./../vendor/doctrine/orm/lib,
__DIR__./../vendor/monolog/monolog/src,
__DIR__./../vendor/kriswallsmith/assetic/src,
__DIR__./../vendor/jms/metadata/src,
));
$loader->registerPrefixes(array(
Twig_Extensions_ => __DIR__./../vendor/twig/extensions/lib,
Twig_
=> __DIR__./../vendor/twig/twig/lib,
));
// ...
$loader->registerNamespaceFallbacks(array(
__DIR__./../src,
));
$loader->register();
O UniversalClassLoader usado para fazer o autoload dos arquivos que respeitam as normas tcnicas de
interoperabilidade para namespaces do PHP 5.3 ou a conveno de nomenclatura para classes do PEAR. Como voc
pode ver aqui, todas as dependncias so armazenadas sob o diretrio vendor/, mas isso apenas uma conveno.
Voc pode armazen-las onde quiser, globalmente em seu servidor ou localmente em seus projetos.
Nota: Se voc quiser saber mais sobre a flexibilidade do autoloader do Symfony2, leia o captulo O Componente
ClassLoader.
Uma aplicao composta de bundles, que foram definidos no mtodo registerBundles() da classe
AppKernel. Cada bundle um diretrio que contm uma nica classe Bundle que descreve ele:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
19
new
new
new
new
Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
Symfony\Bundle\AsseticBundle\AsseticBundle(),
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
);
if (in_array($this->getEnvironment(), array(dev, test))) {
$bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
}
return $bundles;
}
Alm do AcmeDemoBundle que ns j falamos, observe que o kernel tambm habilita outros bundles, como o
FrameworkBundle, DoctrineBundle, SwiftmailerBundle e o AsseticBundle. Todos eles fazem
parte do framework.
Configurando um Bundle
Cada bundle pode ser personalizado atravs dos arquivos de configurao escritos em YAML, XML ou PHP. Esta a
configurao padro:
# app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
framework:
#esi:
#translator:
secret:
router:
form:
csrf_protection:
validation:
templating:
default_locale:
session:
auto_start:
~
{ fallback: "%locale%" }
"%secret%"
{ resource: "%kernel.root_dir%/config/routing.yml" }
true
true
{ enable_annotations: true }
{ engines: [twig] } #assets_version: SomeVersionScheme
"%locale%"
true
# Twig Configuration
twig:
debug:
"%kernel.debug%"
strict_variables: "%kernel.debug%"
# Assetic Configuration
assetic:
debug:
"%kernel.debug%"
use_controller: false
bundles:
[ ]
# java: /usr/bin/java
filters:
cssrewrite: ~
# closure:
20
#
jar: "%kernel.root_dir%/java/compiler.jar"
# yui_css:
#
jar: "%kernel.root_dir%/java/yuicompressor-2.4.2.jar"
# Doctrine Configuration
doctrine:
dbal:
driver:
"%database_driver%"
host:
"%database_host%"
port:
"%database_port%"
dbname:
"%database_name%"
user:
"%database_user%"
password: "%database_password%"
charset: UTF8
orm:
auto_generate_proxy_classes: "%kernel.debug%"
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host:
"%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
jms_security_extra:
secure_controllers: true
secure_all_services: false
Cada entrada como framework define a configurao para um bundle especfico. Por exemplo, framework configura o FrameworkBundle enquanto swiftmailer configura o SwiftmailerBundle.
Cada ambiente pode substituir a configurao padro, ao fornecer um arquivo de configurao especfico. Por exemplo,
o ambiente dev carrega o arquivo config_dev.yml, que carrega a configurao principal (ou seja, config.yml)
e, ento, modifica ela para adicionar algumas ferramentas de depurao:
# app/config/config_dev.yml
imports:
- { resource: config.yml }
framework:
router:
{ resource: "%kernel.root_dir%/config/routing_dev.yml" }
profiler: { only_exceptions: false }
web_profiler:
toolbar: true
intercept_redirects: false
monolog:
handlers:
main:
type:
path:
level:
firephp:
type:
level:
stream
"%kernel.logs_dir%/%kernel.environment%.log"
debug
firephp
info
21
assetic:
use_controller: true
Estendendo um Bundle
Alm de ser uma boa forma de organizar e configurar seu cdigo, um bundle pode estender um outro bundle. A herana do bundle permite substituir qualquer bundle existente a fim de personalizar seus controladores, templates ou qualquer um de seus arquivos. Aqui o onde os nomes lgicos (por exemplo,
@AcmeDemoBundle/Controller/SecuredController.php) so teis: eles abstraem onde o recurso
realmente armazenado.
Nomes Lgicos de Arquivos Quando voc quer fazer referncia um arquivo de um bundle, use esta notao:
@BUNDLE_NAME/path/to/file; o Symfony2 ir resolver @BUNDLE_NAME para o caminho real do bundle. Por
exemplo, o caminho lgico @AcmeDemoBundle/Controller/DemoController.php seria convertido para
src/Acme/DemoBundle/Controller/DemoController.php, pois o Symfony conhece a localizao do
AcmeDemoBundle.
Nomes Lgicos de Controladores Para os controladores, voc precisa referenciar os
de mtodos usando o formato BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME. Por
plo,
AcmeDemoBundle:Welcome:index mapeia para o mtodo indexAction da
Acme\DemoBundle\Controller\WelcomeController.
nomes
exemclasse
inteligente o suficiente para liberar o cache quando voc altera um arquivo. Mas, no ambiente de produo, sua a
responsabilidade de limpar o cache quando voc atualizar o seu cdigo ou alterar sua configurao.
Ao desenvolver uma aplicao web, as coisas podem dar errado em muitos aspectos. Os arquivos de log no diretrio
app/logs/ dizem tudo sobre os pedidos e ajudam a resolver os problemas rapidamente.
Utilizando a Interface da Linha de Comando
Cada aplicao vem com uma ferramenta de interface de linha de comando (app/console) que ajuda na manuteno da sua aplicao. Ela fornece comandos que aumentam a sua produtividade ao automatizar tarefas tediosas e
repetitivas.
Execute-a sem argumentos para saber mais sobre suas capacidades:
$ php app/console
Consideraes finais
Me chame de louco, mas, depois de ler esta parte, voc deve estar confortvel em mover as coisas e fazer o Symfony2
trabalhar para voc. Tudo no Symfony2 projetado para sair do seu caminho. Portanto, sinta-se livre para renomear e
mover os diretrios como voc desejar.
E isso tudo para o incio rpido. Desde testes at o envio de e-mails, voc ainda precisa aprender muito para se tornar
um mestre no Symfony2. Pronto para aprofundar nestes tpicos agora? No procure mais - v para o Livro oficial e
escolha qualquer tema que voc desejar.
Panorama Geral >
A View >
O Controller >
A Arquitetura
23
24
CAPTULO 2
Livro
2.1 Livro
2.1.1 Fundamentos de Symfony e HTTP
Parabns! Aprendendo sobre o Symfony2, voc est no caminho certo para ser um desenvolvedor web mais produtivo,
bem preparado e popular (na verdade, este ltimo por sua prpria conta). O Symfony2 foi criado para voltar ao
bsico: desenvolver ferramentas para ajuda-lo a criar aplicaes mais robustas de uma maneira mais rpida sem ficar
no seu caminho. Ele foi construdo baseando-se nas melhores ideias de diversas tecnologias: as ferramentas e conceitos
que voc est prestes a aprender representam o esforo de milhares de pessoas, realizado durante muitos anos. Em
outras palavras, voc no est apenas aprendendo o Symfony, voc est aprendendo os fundamentos da web, boas
prticas de desenvolvimento e como usar diversas biblotecas PHP impressionantes, dentro e fora do Symfony2. Ento,
prepare-se.
Seguindo a filosofia do Symfony2, este captulo comea explicando o conceito fundamental para o desenvolvimento
web: o HTTP. Independente do seu conhecimento anterior ou linguagem de programao preferida, esse captulo
uma leitura obrigatria para todos.
HTTP simples
HTTP (Hypertext Transfer Protocol, para os geeks) uma linguagem textual que permite que duas mquinas se
comuniquem entre si. s isso! Por exemplo, quando voc vai ler a ltima tirinha do xkcd, acontece mais ou menos
a seguinte conversa:
25
Apesar da linguagem real ser um pouco mais formal, ainda assim ela bastante simples. HTTP o termo usado para
descrever essa linguagem simples baseada em texto. No importa como voc desenvolva para a web, o objetivo do seu
servidor sempre ser entender simples requisies de texto e enviar simples respostas de texto.
O Symfony2 foi criado fundamentado nessa realidade. Voc pode at no perceber, mas o HTTP algo que voc
utilizada todos os dias. Com o Symfony2 voc ir aprender a domina-lo.
Primeiro Passo: O Cliente envia uma Requisio
Toda comunicao na web comea com uma requisio. Ela uma mensagem de texto criada por um cliente (por
exemplo, um navegador, um app para iPhone etc) em um formato especial conhecido como HTTP. O cliente envia
essa requisio para um servidor e, ento, espera pela resposta.
Veja a primeira parte da interao (a requisio) entre um navegador e o servidor web do xkcd:
26
Captulo 2. Livro
Essa simples mensagem comunica tudo o que necessrio sobre o recurso exato que o cliente est requisitando. A
primeira linha de uma requisio HTTP a mais importante e contm duas coisas: a URI e o mtodo HTTP.
A URI (por exemplo, /, /contact etc) um endereo nico ou localizao que identifica o recurso que o cliente
quer. O mtodo HTTP (por exemplo, GET) define o que voc quer fazer com o recurso. Os mtodos HTTP so os
verbos da requisio e definem algumas maneiras comuns de agir em relao ao recurso:
GET
POST
PUT
DELETE
Tendo isso em mente, voc pode imaginar como seria uma requisio HTTP para excluir uma postagem especfica de
um blog, por exemplo:
DELETE /blog/15 HTTP/1.1
Nota: Existem na verdade nove mtodos definidos pela especificao HTTP, mas a maioria deles no so muito
utilizados ou suportados. Na realidade, muitos dos navegadores modernos no suportam os mtodos PUT e DELETE.
Alm da primeira linha, uma requisio HTTP invariavelmente contm outras linhas de informao chamadas de
cabealhos da requisio. Os cabealhos podem fornecer uma vasta quantidade de informaes, tais como o Host
que foi requisitado, os formatos de resposta que o cliente aceita (Accept) e a aplicao que o cliente est utilizando
para enviar a requisio (User-Agent). Muitos outros cabealhos existem e podem ser encontrados na Wikipedia,
no artigo List of HTTP header fields
Segundo Passo: O Servidor envia uma resposta
Uma vez que o servidor recebeu uma requisio, ele sabe exatamente qual recurso o cliente precisa (atravs do URI)
e o que o cliente quer fazer com ele (atravs do mtodo). Por exemplo, no caso de uma requisio GET, o servidor
prepara o o recurso e o retorna em uma resposta HTTP. Considere a resposta do servidor web do xkcd:
Traduzindo para HTTP, a resposta enviada para o navegador ser algo como:
HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Server: lighttpd/1.4.19
Content-Type: text/html
2.1. Livro
27
<html>
<!-- HTML for the xkcd comic -->
</html>
A resposta HTTP contm o recurso requisitado (nesse caso, o contedo HTML), bem como outras informaes. A
primeira linha especialmente importante e contm o cdigo de status da resposta HTTP (nesse caso, 200). Esse
cdigo de status uma representao geral da resposta enviada requisio do cliente. A requisio foi bem sucedida? Ocorreu algum erro? Existem diferentes cdigos de status para indentificar sucesso, um erro, ou que o cliente
precisa fazer alguma coisa (por exemplo, redirecionar para outra pgina). Uma lista completa pode ser encontrada na
Wikipedia, no artigo List of HTTP status codes.
Assim como uma requisio, uma resposta HTTP tambm contm informaes adicionais conhecidas como cabealhos HTTP. Por exemplo, um cabealho importante nas respostas HTTP o Content-Type. O contedo de um
mesmo recurso pode ser retornado em vrios formatos diferentes, incluindo HTML, XML ou JSON, s para citar
alguns. O cabealho Content-Type diz ao cliente qual o formato que est sendo retornado.
Existem diversos outros cabealhos, alguns deles bastante poderosos. Certos cabealhos, por exemplo, podem ser
utilizados para criar um poderoso sistema de cache.
Requisies, Respostas e o Desenvolvimento Web
Essa conversao de requisio-resposta o processo fundamental que dirige toda a comunicao na web. Apesar de
to importante e poderoso esse processo, ainda assim, inevitavelmente simples.
O fato mais importante : independente da linguagem que voc utiliza, o tipo de aplicao que voc desenvolva (web,
mobile, API em JSON) ou a filosofia de desenvolvimento que voc segue, o objetivo final da aplicao sempre ser
entender cada requisio e criar e enviar uma resposta apropriada.
O Symfony foi arquitetado para atender essa realidade.
Dica: Para aprender mais sobre a especificao HTTP, leia o original HTTP 1.1 RFC ou HTTP Bis, que trata-se de
um esforo para facilitar o entendimento da especificao original. Para verificar as requisies e respostas enviadas
enquanto navega em um site, voc pode utilizar a extenso do Firefox chamada Live HTTP Headers.
Por mais estranho que possa parecer, essa pequena aplicao, de fato, l informaes da requisio HTTP e a est
utilizando para criar um resposta HTTP. Em vez de interpretar a requisio pura, o PHP prepara algumas variveis
superglobais , tais como $_SERVER e $_GET, que contm toda a informao da requisio. Da mesma forma, em vez
de retornar o texto da resposta no formato do HTTP, voc pode utilizar a funo header() para criar os cabealhos e
simplesmente imprimir o que ser o contedo da mensagem da reposta. O PHP ir criar uma reposta HTTP verdadeira
que ser retornada para o cliente.
28
Captulo 2. Livro
HTTP/1.1 200 OK
Date: Sat, 03 Apr 2011 02:14:33 GMT
Server: Apache/2.2.17 (Unix)
Content-Type: text/html
The URI requested is: /testing?foo=symfony
The value of the "foo" parameter is: symfony
Como um bnus, a classe Request faz um monte de trabalho com o qual voc nunca precisar se preocupar. Por
exemplo, o mtodo isSecure() verifica os trs valores diferentes que o PHP utiliza para indicar ser o usurio est
utilizando uma conexo segura (https, por exemplo).
O Symfony tambm fornece a classe Response: uma simples representao em PHP de uma resposta HTTP. Assim
possvel que sua aplicao utilize uma interface orientada a objetos para construir a resposta que precisa ser enviada
ao cliente:
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->setContent(<html><body><h1>Hello world!</h1></body></html>);
$response->setStatusCode(200);
$response->headers->set(Content-Type, text/html);
// prints the HTTP headers followed by the content
$response->send();
Com tudo isso, mesmo que o Symfony no oferecesse mais nada, voc j teria um kit de ferramentas para facilmente
acessar informaes sobre a requisio e uma interface orientada a objetos para criar a resposta. Mesmo depois de
aprender muitos dos poderosos recursos do Symfony, tenha em mente que o objetivo da sua aplicao sempre ser
interpretar uma requisio e criar a resposta apropriada baseada na lgica da sua aplicao.
Dica:
2.1. Livro
29
HttpFoundation. Esse componente pode ser utilizado de forma independente ao framework e tambm possui
classes para tratar sesses e upload de arquivos.
Tradicionalmente, aplicaes so construdas para que cada pgina do site seja um arquivo fsico:
index.php
contact.php
blog.php
Existem diversos problemas para essa abordagem, incluindo a falta de flexibilidade das URLs (e se voc quiser mudar
o arquivo blog.php para news.php sem quebrar todos os seus links?) e o fato de que cada arquivo deve ser
alterado manualmente para incluir um certo conjunto de arquivos essenciais de forma que a segurana, conexes com
banco de dados e a aparncia do site continue consistente.
Uma soluo muito melhor utilizar um front controller: um nico arquivo PHP que trata todas as requisies enviadas
para a sua aplicao. Por exemplo:
/index.php
/index.php/contact
/index.php/blog
executa index.php
executa index.php
executa index.php
Dica: Utilizando o mod_rewrite do Apache (ou o equivalente em outros servidores web), as URLs podem ser
simplificadas facilmente para ser somente /, /contact e /blog.
Agora, cada requisio tratada exatamente do mesmo jeito. Em vez de arquivos PHP individuais para executar cada
URL, o front controller sempre ser executado, e o roteamento de cada URL para diferentes partes da sua aplicao
feito internamente. Assim resolve-se os dois problemas da abordagem original. Quase todas as aplicaes modernas
fazem isso - incluindo apps como o Wordpress.
Mantenha-se Organizado
Dentro do front controller, como voc sabe qual pgina deve ser renderizada e como renderiza-las de uma maneira
sensata? De um jeito ou de outro, voc precisar verificar a URI requisitada e executar partes diferentes do seu cdigo
dependendo do seu valor. Isso pode acabar ficando feio bem rpido:
// index.php
$request = Request::createFromGlobals();
$path = $request->getPathInfo(); // the URL being requested
30
Captulo 2. Livro
Resolver esse problema pode ser difcil. Felizmente exatamente o que o Symfony foi projetado para fazer.
O Fluxo de uma Aplicao Symfony
Quando voc deixa que o Symfony cuide de cada requisio, sua vida fica muito mais fcil. O framework segue um
simples padro para toda requisio:
Figura 2.1: As requisies recebidas so interpretadas pelo roteamento e passadas para as funes controller que
retornam objetos do tipo Response.
Cada pgina do seu site definida no arquivo de configurao de roteamento que mapeia diferentes URLs para
diferentes funes PHP. O trabalho de cada funo, chamadas de controller, usar a informao da requisio - junto
com diversas outras ferramentas disponveis no Symfony - para criar e retornar um objeto Response. Em outras
palavras, o seu cdigo deve estar nas funes controller: l onde voc interpreta a requisio e cria uma resposta.
fcil! Vamos fazer uma reviso:
Cada requisio executa um arquivo front controller;
O sistema de roteamento determina qual funo PHP deve ser executada, baseado na informao da requisio
e na configurao de roteamento que voc criou;
A funo PHP correta executada, onde o seu cdigo cria e retorna o objeto Response apropriado.
Uma Requisio Symfony em Ao
Sem entrar em muitos detalhes, vamos ver esse processo em ao. Suponha que voc quer adicionar a pgina
/contact na sua aplicao Symfony. Primeiro, adicione uma entrada para /contact no seu arquivo de configurao de roteamento:
2.1. Livro
31
contact:
pattern: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
Nota: Esse exemplo utiliza YAML para definir a configurao de roteamento. Essa configurao tambm pode ser
escrita em outros formatos, tais como XML ou PHP.
Quando algum visitar a pgina /contact, essa rota ser encontrada e o controller especfico ser executado. Como
voc ir aprender no captulo sobre roteamento, a string AcmeDemoBundle:Main:contact uma sintaxe encurtada para apontar para o mtodo contactAction dentro de uma classe chamada MainController:
class MainController
{
public function contactAction()
{
return new Response(<h1>Contact us!</h1>);
}
}
Nesse exemplo extremamente simples, o controller simplesmente cria um objeto Response com o HTML
<h1>Contact us!</h1>. No captulo sobre controller, voc ir aprender como um controller pode renderizar templates, fazendo com que o seu cdigo de apresentao (por exemplo, qualquer coisa que gere HTML) fique em um
arquivo de template separado. Assim deixamos o controller livre para se preocupar apenas com a parte complicada:
interagir com o banco de dados, tratar os dados enviados ou enviar emails.
Symfony2: Construa sua aplicao, no suas Ferramentas
Agora voc sabe que o objetivo de qualquer aplicao interpretar cada requisio recebida e criar uma resposta
apropriada. Conforme uma aplicao cresce, torna-se mais difcil de manter o seu cdigo organizado e de fcil
manuteno. Invariavelmente, as mesmas tarefas complexas continuam a aparecer: persistir dados no banco, renderizar
e reutilizar templates, tratar envios de formulrios, enviar emails, validar entradas dos usurios e cuidar da segurana.
A boa notcia que nenhum desses problemas nico. O Symfony um framework cheio de ferramentas para voc
construir a sua aplicao e no as suas ferramentas. Com o Symfony2, nada imposto: voc livre para utilizar o
framework completo ou apenas uma parte dele.
Ferramentas Independentes: Os Componentes do Symfony2
Ento, o que o Symfony2? Primeiramente, trata-se de uma coleo de vinte bibliotecas independentes que podem
ser utilizadas dentro de qualquer projeto PHP. Essas bibliotecas, chamadas de Components do Symfony2, contm
coisas teis para praticamente qualquer situao, independente de como o seu projeto desenvolvido. Alguns desses
componentes so:
HttpFoundation - Contm as classes Request e Response, bem como outras classes para tratar de sesses e
upload de arquivos;
Routing - Um poderoso e rpido sistema de roteamento que permite mapear uma URI especfica (por exemplo,
/contact) para uma informao sobre como a requisio deve ser tratada (por exemplo, executar o mtodo
contactAction());
Form - Um framework completo e flexvel para criar formulrios e tratar os dados enviados por eles;
Validator Um sistema para criar regras sobre dados e validar se os dados enviados pelos usurios seguem ou no
essas regras;
32
Captulo 2. Livro
ClassLoader Uma biblioteca de autoloading que faz com que classes PHP possam ser utilizadas sem precisar
adicionar manualmente um require para cada arquivo que as contm;
Templating Um conjunto de ferramentas para renderizar templates, tratar da herana de templates (por exemplo,
um template decorado com um layout) e executar outras tarefas comuns relacionadas a templates;
Security - Uma biblioteca poderosa para tratar qualquer tipo de segurana dentro de sua aplicao;
Translation Um framework para traduzir strings na sua aplicao.
Cada um desses componentes funcionam de forma independente e podem ser utilizados em qualquer projeto PHP, no
importa se voc utiliza o Symfony2 ou no. Cada parte foi feita para ser utilizada e substituda quando for necessrio.
A soluo completa: O framework Symfony2
Ento, o que o framework Symfony2? Ele uma biblioteca PHP que realiza duas tarefas distintas:
1. Fornecer uma seleo de componentes (os componentes do Symfony2, por exemplo) e bibliotecas de terceiros
(por exemplo, a Swiftmailer, utilizada para enviar emails);
2. Fornecer as configuraes necessrias e uma cola para manter todas as peas juntas.
O objetivo do framework integrar vrias ferramentas independentes para criar uma experincia consistente para
o desenvolvedor. At prprio prprio framework um pacote Symfony2 (um plugin, por exemplo) que pode ser
configurado ou completamente substitudo.
O Symfony2 fornece um poderoso conjunto de ferramentas para desenvolver aplicaes web rapidamente sem impor
nada. Usurios normais podem iniciar o desenvolvimento rapidamente utilizando uma distribuio do Symfony2, que
contm o esqueleto de um projeto com as princpais itens padro. Para os usurios mais avanados, o cu o limite.
2.1. Livro
33
<html>
<head>
<title>List of Posts</title>
</head>
<body>
<h1>List of Posts</h1>
<ul>
<?php while ($row = mysql_fetch_assoc($result)): ?>
<li>
<a href="/show.php?id=<?php echo $row[id] ?>">
<?php echo $row[title] ?>
</a>
</li>
<?php endwhile; ?>
</ul>
</body>
</html>
<?php
mysql_close($link);
Simples de escrever, rpido de executar e, conforme sua aplicao crescer, impossvel de manter. Existem diversos
problemas que precisam ser tratados:
Sem verificaes de erros: E se a conexo com o banco de dados falhar?
Organizao pobre: Se a aplicao crescer, esse arquivo tambm ir crescer e ficar impossvel de dar manuteno. Onde voc deve colocar o cdigo que cuida de tratar os envios de formulrios? Como voc valida os
dados? Onde voc deve colocar o cdigo que envia emails?
Dificuldade para reutilizar cdigo: Uma vez que tudo est em um nico arquivo, no h como reutilizar
qualquer parte dele em outras pginas do blog.
Nota: Um outro problema no mencionado aqui o fato do banco de dados estar amarrado ao MySQL. Apesar de
no ser tratado aqui, o Symfony2 integra-se totalmente com o Doctrine, uma biblioteca dedicada a abstrao de banco
de dados e mapeamento.
Vamos ao trabalho de resolver esses problemas e mais ainda.
Isolando a Apresentao
O cdigo pode ter ganhos imediatos ao separar a lgica da aplicao do cdigo que prepara o HTML para apresentao:
<?php
// index.php
$link = mysql_connect(localhost, myuser, mypassword);
mysql_select_db(blog_db, $link);
$result = mysql_query(SELECT id, title FROM post, $link);
$posts = array();
while ($row = mysql_fetch_assoc($result)) {
$posts[] = $row;
}
34
Captulo 2. Livro
mysql_close($link);
// include the HTML presentation code
require templates/list.php;
Agora o cdigo HTML est armazenado em um arquivo separado (templates/list.php), que um arquivo
HTML que utiliza um sintaxe PHP parecida com a de templates:
<html>
<head>
<title>List of Posts</title>
</head>
<body>
<h1>List of Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<a href="/read?id=<?php echo $post[id] ?>">
<?php echo $post[title] ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</body>
</html>
Por conveno, o arquivo que contm toda a lgica da aplicao - index.php - conhecido como controller. O
termo controller uma palavra que voc vai escutar bastante, independente da linguagem ou framework voc utilize.
Ela refere-se a rea do seu cdigo que processa as entradas do usurio e prepara uma resposta.
Nesse caso, nosso controller prepara os dados do banco de dados e ento inclui um template para apresenta-los. Com
o controller isolado, voc pode facilmente mudar apenas o arquivo de template caso precise renderizar os posts de
blog em algum outro formato (por exemplo, list.json.php para o formato JSON).
Isolando a Lgica (Domnio) da Aplicaco
Por enquanto a aplicao tem apenas uma pgina. Mas e se uma segunda pgina precisar utilizar a mesma conexo
com o banco de dados, ou at o mesmo array de posts do blog? Refatore o cdigo de forma que o comportamento
principal e as funes de acesso aos dados da aplicao fiquem isolados em um novo arquivo chamado model.php:
<?php
// model.php
function open_database_connection()
{
$link = mysql_connect(localhost, myuser, mypassword);
mysql_select_db(blog_db, $link);
return $link;
}
function close_database_connection($link)
{
mysql_close($link);
}
function get_all_posts()
2.1. Livro
35
{
$link = open_database_connection();
$result = mysql_query(SELECT id, title FROM post, $link);
$posts = array();
while ($row = mysql_fetch_assoc($result)) {
$posts[] = $row;
}
close_database_connection($link);
return $posts;
}
Dica: O nome model.php foi utilizado porque a lgica e o acesso aos dados de uma aplicao so tradicionalmente
conhecidos como a camada de modelo. Em uma aplicao bem organizada, a maioria do cdigo representando
as suas regras de negcio devem estar apenas no model (em vez de estar em um controller). Ao contrrio desse
exemplo, somente uma parte do model (ou nenhuma) est realmente relacionada ao banco de dados.
Agora o controller (index.php) ficou bem simples:
<?php
require_once model.php;
$posts = get_all_posts();
require templates/list.php;
Agora, a nica tarefa do controller recuperar os dados da camada de modelo da sua aplicao (o model) e chamar o
template para renderiza-los. Esse um exemplo bem simples do padro model-view-controller.
Isolando o Layout
At esse ponto a aplicao foi refatorada em trs partes distintas, oferecendo vrias vantagens e a oportunidade de
reutilizar quase qualquer coisa em outras pginas.
A nica parte do cdigo que no pode ser reutilizada o layout da pgina. Conserte isso criando um novo arquivo
chamado layout.php:
<!-- templates/layout.php -->
<html>
<head>
<title><?php echo $title ?></title>
</head>
<body>
<?php echo $content ?>
</body>
</html>
36
Captulo 2. Livro
Agora voc foi apresentado a uma metodologia que permite a reutilizao do layout. Infelizmente, para fazer isso, voc
forado a utilizar no template algumas funes feias do PHP (ob_start(), ob_get_clean()). O Symfony2
utiliza o componente Templating que permite realizar isso de uma maneira limpa e fcil. Logo voc ver esse
componente em ao.
Adicionando a pgina show ao Blog
A pgina list foi refatorada para que o cdigo fique mais organizado e reutilizvel. Para provar isso, adicione ao
blog uma pgina chamada show, que exibe um nico post identificado pelo parmetro id.
Para comear, crie uma nova funo no arquivo model.php que recupera o post com base no id informado:
// model.php
function get_post_by_id($id)
{
$link = open_database_connection();
$id = mysql_real_escape_string($id);
$query = SELECT date, title, body FROM post WHERE id = .$id;
$result = mysql_query($query);
$row = mysql_fetch_assoc($result);
close_database_connection($link);
return $row;
}
Em seguida, crie um novo arquivo chamado show.php - o controller para essa nova pgina:
<?php
require_once model.php;
$post = get_post_by_id($_GET[id]);
require templates/show.php;
Por fim, crie um novo arquivo de template - templates/show.php - para renderizar individualmente o post do
blog:
<?php $title = $post[title] ?>
<?php ob_start() ?>
<h1><?php echo $post[title] ?></h1>
<div class="date"><?php echo $post[date] ?></div>
<div class="body">
<?php echo $post[body] ?>
</div>
2.1. Livro
37
Criar a segunda pgina foi bastante fcil e nenhum cdigo foi duplicado. Ainda assim, essa pgina criou mais alguns problemas persistentes que um framework pode resolver para voc. Por exemplo, se o parmetro id no for
informado, ou for invlido, a pgina ir quebrar. Seria mais interessante exibir uma pgina de erro 404, mas isso
ainda no pode ser feito de uma maneira fcil. Pior ainda, caso voc esquea de tratar o id utilizando a funo
mysql_real_escape_string(), todo o seu banco de dados estar correndo o risco de sofrer ataques de SQL
injection.
Um problema ainda maior que cada controller deve incluir o arquivo model.php individualmente. O que acontece
se cada controller, de repente, precisar incluir um arquivo adicional para executar alguma outra tarefa global (impor segurana, por exemplo)? Da maneira como est agora, esse cdigo teria que ser adicionado em cada arquivo controller.
Se voc esquecer de incluir algo em algum arquivo espero que no seja algo relacionado a segurana...
Um Front Controller para a salvao
A soluo utilizar um front controller: um nico arquivo PHP que ir processar todas as requisies. Com um front
controller, as URIs vo mudar um pouco, mas comeam a ficar mais flexveis:
Without a front controller
/index.php
=> Blog post list page (index.php executed)
/show.php
=> Blog post show page (show.php executed)
With index.php as the front controller
/index.php
=> Blog post list page (index.php executed)
/index.php/show
=> Blog post show page (index.php executed)
Dica: O index.php pode ser removido da URI se voc estiver utilizando regras de rewrite no Apache (ou algo
equivalente). Nesse caso, a URI resultante para a pgina show ser simplesmente /show.
Ao utilizar um front controller, um nico arquivo PHP (nesse caso o index.php) ir renderizar todas as requisies.
Para a pgina show do blog, o endereo /index.php/show ir, na verdade, executar o arquivo index.php, que
agora responsvel por redirecionar as requisies internamente baseado na URI completa. Como voc pode ver, um
front controller uma ferramente bastante poderosa.
Criando o Front Controller
Voc est prestes a dar um grande passo com a sua aplicao. Com um arquivo para gerenciar todas as suas requisies, voc pode centralizar coisas como segurana, configuraes e roteamento. Nessa aplicao, o arquivo
index.php deve ser esperto o suficiente para renderizar a pgina com a lista de posts ou a pgina com um nico
post baseado na URI da requisio:
<?php
// index.php
// load and initialize any global libraries
require_once model.php;
require_once controllers.php;
// route the request internally
$uri = $_SERVER[REQUEST_URI];
if ($uri == /index.php) {
38
Captulo 2. Livro
list_action();
} elseif ($uri == /index.php/show && isset($_GET[id])) {
show_action($_GET[id]);
} else {
header(Status: 404 Not Found);
echo <html><body><h1>Page Not Found</h1></body></html>;
}
Por questo de organizao, ambos os controllers (os antigos arquivos index.php e show.php) agora so funes
e cada uma foi movida para um arquivo separado, chamado controllers.php:
function list_action()
{
$posts = get_all_posts();
require templates/list.php;
}
function show_action($id)
{
$post = get_post_by_id($id);
require templates/show.php;
}
Sendo um front controller, index.php agora tem um papel inteiramente novo, que inclui carregar as bibliotecas
principais e rotear a aplicao de forma que um dos controllers (as funes list_action() e show_action())
seja chamado. Na verdade, o front controller est comeando a ficar bastante parecido com o mecanismo do Symfony2
utilizado para tratar e redirecionar as requisies:
Dica: Uma outra vantagem do front controller ter URLs flexveis. Note que a URL para a pgina que exibe um post
no blog pode mudar de /show para /read alterando o cdigo apenas em um nico lugar. Antes, um arquivo teria
que ser renomeado. No Symfony2 as URLs podem ser ainda mais flexveis.
At agora, a aplicao evoluiu de um nico arquivo PHP para para uma estrutura organizada que permite a reutilizao
de cdigo. Voc deve estar mais feliz, mas longe de estar satisfeito. Por exemplo, o sistema de roteamento ainda
no consistente e no reconhece que a pgina de listagem (index.php) tambm pode ser acessada via / (se as
regras de rewrite foram adicionadas no Apache). Alm disso, em vez de desenvolver o blog, boa parte do tempo foi
gasto trabalhando na arquitetura do cdigo (por exemplo, roteamento, execuo de controllers, templates etc). Mais
tempo ainda ser necessrio para tratar o envio de formulrios, validao das entradas, logs e segurana. Por que voc
tem que reinventar solues para todos esses problemas?
Adicione um toque de Symfony2
Symfony2 para a salvao. Antes de realmente utilizar o Symfony2, voc precisa ter certeza que o PHP sabe onde
encontrar as classes do framework. Isso pode ser feito com o autoloader fornecido pelo Symfony. Um autoloader
uma ferramenta que permite a utilizao de classes PHP sem a necessidade de incluir os seus arquivos explicitamente.
Primeiro, faa o download do symfony e o coloque no diretrio vendor/symfony/symfony/. A seguir, crie um
o arquivo app/bootstrap.php. Utilize-o para dar require dos dois arquivos da aplicao e para configurar o
autoloader:
<?php
// bootstrap.php
require_once model.php;
require_once controllers.php;
require_once vendor/symfony/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php;
2.1. Livro
39
Esse cdigo diz ao autoloader onde esto as classes do Symfony. Com isso, voc pode comear a utilizar as classes
sem precisar de um require para os arquivos que as contm.
Dentro da filosofia do Symfony est a idia de que a principal tarefa de uma aplicao interpretar cada requisio
e retornar uma resposta. Para essa finalidade, o Symfony2 fornece as classes Request e Response. Elas so
representaes orientadas a objetos da requisio HTTP pura sendo processada e da resposta HTTP sendo retornada.
Utilize-as para melhorar o blog:
<?php
// index.php
require_once app/bootstrap.php;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
$uri = $request->getPathInfo();
if ($uri == /) {
$response = list_action();
} elseif ($uri == /show && $request->query->has(id)) {
$response = show_action($request->query->get(id));
} else {
$html = <html><body><h1>Page Not Found</h1></body></html>;
$response = new Response($html, 404);
}
// echo the headers and send the response
$response->send();
Agora os controller so responsveis por retornar um objeto Response. Para tornar isso mais fcil, voc pode
adicionar uma nova funo chamada render_template(), que, a propsito, funciona de forma um pouco parecida
com o mecanismo de template do Symfony2:
// controllers.php
use Symfony\Component\HttpFoundation\Response;
function list_action()
{
$posts = get_all_posts();
$html = render_template(templates/list.php, array(posts => $posts));
return new Response($html);
}
function show_action($id)
{
$post = get_post_by_id($id);
$html = render_template(templates/show.php, array(post => $post));
return new Response($html);
}
40
Captulo 2. Livro
Ao adicionar uma pequena parte do Symfony2, a aplicao ficou mais flexvel e confivel. A classe Request
fornece uma maneira segura para acessar informaes sobre a requisio HTTP. Especificamente, o mtodo
getPathInfo() retorna a URI limpa (sempre retornando /show e nunca /index.php/show). Assim, mesmo
que o usurio utilize /index.php/show, a aplicao inteligente o suficiente para direcionar a requisio para
show_action().
O objeto Response d flexibilidade ao construir a resposta HTTP, permitindo a adio de cabealhos HTTP e contedo atravs de um interface orientada a objetos. Apesar das respostas nessa aplicao ainda serem simples, essa
flexibilidade ser til conforme a aplicao crescer.
A aplicao de exemplo no Symfony2
O blog j passou por um longo caminho, mas ele ainda tem muito cdigo para uma aplicao to simples. Por
esse caminho, ns tambm inventamos um simples sistema de roteamento e um mtodo utilizando ob_start() e
ob_get_clean() para renderiar templates. Se, por alguma razo, voc precisasse continuar a construir esse framework do zero, voc poderia pelo menos utilizar isoladamente os components Routing e Templating do Symfony,
que j resolveriam esses problemas.
Em vez de re-resolver problemas comuns, voc pode deixar que o Symfony2 cuide deles pra voc. Aqui est um
exemplo da mesma aplicao, agora feito com o Symfony2:
<?php
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller
{
public function listAction()
{
$posts = $this->get(doctrine)->getEntityManager()
->createQuery(SELECT p FROM AcmeBlogBundle:Post p)
->execute();
return $this->render(AcmeBlogBundle:Blog:list.html.php, array(posts => $posts));
}
public function showAction($id)
{
$post = $this->get(doctrine)
->getEntityManager()
->getRepository(AcmeBlogBundle:Post)
->find($id);
2.1. Livro
41
if (!$post) {
// cause the 404 page not found to be displayed
throw $this->createNotFoundException();
}
return $this->render(AcmeBlogBundle:Blog:show.html.php, array(post => $post));
}
}
Os dois controller ainda esto bastante leves. Cada um utiliza a biblioteca de ORM Doctrine para recuperar objetos do
banco de dados e o componente Templating para renderizar e retornar um objeto Response. O template list ficou
um pouco mais simples:
<!-- src/Acme/BlogBundle/Resources/views/Blog/list.html.php -->
<?php $view->extend(::layout.html.php) ?>
<?php $view[slots]->set(title, List of Posts) ?>
<h1>List of Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<a href="<?php echo $view[router]->generate(blog_show, array(id => $post->getId())) ?>"
<?php echo $post->getTitle() ?>
</a>
</li>
<?php endforeach; ?>
</ul>
Nota: Vamos deixar o template da pgina show como um exerccio para voc, uma vez que trivial cria-lo com base
no template da pgina list
Quando o mecanismo do Symfony2 (chamado de Kernel) iniciado, ele precisa de um mapa que indique quais
controllers devem ser executados de acordo com a requisio. A configurao de roteamento contm essa informao
em um formato legvel:
# app/config/routing.yml
blog_list:
pattern: /blog
defaults: { _controller: AcmeBlogBundle:Blog:list }
blog_show:
pattern: /blog/show/{id}
defaults: { _controller: AcmeBlogBundle:Blog:show }
Agora que o Symfony2 est cuidando dessas tarefas simples, o front controller ficou extremamente simples. Uma
42
Captulo 2. Livro
vez que ele faz to pouco, voc nunca mais ter que mexer nele depois de criado (e se voc estiver utilizando uma
distribuio do Symfony2, voc nem mesmo precisar cria-lo!):
<?php
// web/app.php
require_once __DIR__./../app/bootstrap.php;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(prod, false);
$kernel->handle(Request::createFromGlobals())->send();
A nica tarefa do front controller iniciar o mecanismo (Kernel) do Symfony2 e passar para ele o objeto Request
que deve ser manuseado. Ento o Symfony utiliza o mapa de rotas para determinar qual controller chamar. Assim
como antes, o mtodo controller responsvel por retornar o objeto Response. No h muito mais que ele precise
fazer.
Para uma representao visual de como o Symfony2 trata cada requisio, veja o diagrama de fluxo da requisio.
Onde vantagem utilizar o Symfony2
Nos prximos captulos voc ir aprender mais sobre cada parte do Symfony funciona e a organizao recomendada
para um projeto. Por enquanto, vamos ver como a migrao do PHP puro para o Symfony2 facilitou a sua vida:
A sua aplicao agora tem um cdigo limpo e organizado de forma consistente apesar do Symfony no
te forar a isso). Isso aumenta a usabilidade e permite que novos desenvolvedores sejam produtivos no seu
projeto de uma maneira mais rpida.
100% do cdigo que voc escreveu para a sua aplicao. Voc no precisa desenvolver ou manter ferramentas de baixo nvel como autoloading, roteamento, ou renderizao nos controllers.
O Symfony2 te d acesso a ferramentas open source como Doctrine e os componentes Templating, Security,
Form, Validation e Translation (s para citar alguns).
A aplicao agora faz uso de URLs totalmente flexveis graas ao componente Routing.
A arquitetura do Symfony2 centrada no HTTP te d acesso a poderosas ferramentas como HTTP caching feito
pelo cache interno de HTTP do Symfony2 ou por ferramentas ainda mais poderosas como o Varnish_. Esse
assunto ser tratado em um prximo captulo sobre caching.
E talvez o melhor de tudo, ao utilizar o Symfony2, voc tem acesso a todo um conjunto de ferramentas open
source de alta qualidade desenvolvidas pela comunidade do Symfony2! Para mais informaes, visite o site Symfony2Bundles.org
Melhores templates
Se voc optar por utiliza-lo, o Symfony2 vem com um sistema de template padro chamado Twig que torna mais fcil
a tarefa de escrever templates e os deixa mais fcil de ler. Isso significa que a aplicao de exemplo pode ter ainda
menos cdigo! Pegue como exemplo o template list escrito com o Twig:
{# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #}
{% extends "::layout.html.twig" %}
{% block title %}List of Posts{% endblock %}
{% block body %}
<h1>List of Posts</h1>
2.1. Livro
43
<ul>
{% for post in posts %}
<li>
<a href="{{ path(blog_show, { id: post.id }) }}">
{{ post.title }}
</a>
</li>
{% endfor %}
</ul>
{% endblock %}
O Twig bem suportado no Symfony2. E, mesmo que os templates em PHP sempre sero suportados pelo framework,
continuaremos a discutir sobre as muitas vantagens do Twig. Para mais informaes, veja o captulo sobre templates.
Aprenda mais no Cookbook
/cookbook/templating/PHP
Como definir Controladores como Servios
44
Captulo 2. Livro
Comece acessando a pgina de download do Symfony2 em http://symfony.com/download. Nessa pgina, voc ver
Symfony Standard Edition, que a principal distribuio do Symfony2. Existem duas formas de iniciar o seu projeto:
Opo 1) Composer
Composer uma biblioteca de gerenciamento de dependncias para PHP, que voc pode usar para baixar a Edio
Standard do Symfony2.
Comece fazendo o download do Composer em qualquer lugar em seu computador local. Se voc tem o curl instalado,
to fcil como:
curl -s https://getcomposer.org/installer | php
Nota: Se o seu computador no est pronto para usar o Composer, voc ver algumas recomendaes ao executar
este comando. Siga as recomendaes para que o Composer funcione corretamente.
O Composer um arquivo executvel PHAR, que voc pode usar para baixar a Distribuio Standard:
Dica: Para uma verso exata, substitua 2.1.x-dev com a verso mais recente do Symfony (por exemplo, 2.1.1). Para
mais detalhes, consulte a Pgina de Instalao do Symfony_
Este comando pode demorar alguns minutos para ser executado pois o Composer baixa a Distribuio Padro, juntamente com todas as bibliotecas vendor de que ela precisa. Quando terminar, voc deve ter um diretrio parecido com
o seguinte:
path/to/webroot/ <- your web root directory
Symfony/ <- the new directory
app/
cache/
config/
logs/
src/
...
vendor/
...
web/
app.php
...
Voc tambm pode fazer download de um arquivo da Edio Standard. Aqui, voc vai precisar fazer duas escolhas:
Faa o download do arquivo tgz ou zip - ambos so equivalentes, faa o download daquele que voc est mais
confortvel em usar;
Faa o download da distribuio com ou sem vendors. Se voc est pensando em usar mais bibliotecas de
terceiros ou bundles e gerenci-los atravs do Composer, voc provavelmente deve baixar sem vendors.
Baixe um dos arquivos em algum lugar sob o diretrio raiz do seu servidor web local e descompacte-o. A partir de
uma linha de comando UNIX, isto pode ser feito com um dos seguintes comandos (substituindo ### com o seu nome
real do arquivo):
2.1. Livro
45
Se voc baixou o arquivo sem vendors, voc definitivamente precisa ler a prxima seo.
Voc pode facilmente substituir a estrutura de diretrios padro. Veja
Padro do Symfony para mais
informaes.
Atualizando os Vendors
Neste ponto, voc baixou um projeto Symfony totalmente funcional em que voc vai comear a desenvolver a sua
prpria aplicao. Um projeto Symfony depende de um nmero de bibliotecas externas. Estas so baixadas no
diretrio vendor/ do seu projeto atravs de uma biblioteca chamada Composer_.
Dependendo de como voc baixou o Symfony, voc pode ou no precisar fazer a atualizao de seus vendors agora.
Mas, a atualizao de seus vendors sempre segura, e garante que voc tem todas as bibliotecas vendor que voc
precisa.
Passo 1: Obter o Composer _ (O excelente novo sistema de pacotes do PHP)
curl -s http://getcomposer.org/installer | php
Certifique-se de que voc baixou o composer.phar no mesmo diretrio onde o arquivo composer.json
encontra-se (por padro, no raiz de seu projeto Symfony).
Passo 2: Instalar os vendors
$ php composer.phar install
Este comando faz o download de todas as bibliotecas vendor necessrias - incluindo o Symfony em si - dentro do
diretrio vendor/.
Nota: Se voc no tem o curl instalado, voc tambm pode apenas baixar o arquivo installer manualmente em
http://getcomposer.org/installer. Coloque este arquivo em seu projeto e execute:
php installer
php composer.phar install
Dica: Ao executar php composer.phar install ou php composer.phar update, o composer vai
executar comandos de ps instalao/atualizao para limpar o cache e instalar os assets. Por padro, os assets sero
copiados para o seu diretrio web. Para criar links simblicos em vez de copiar os assets, voc pode adicionar
uma entrada no n extra do seu arquivo composer.json com a chave symfony-assets-install e o valor
symlink:
"extra": {
"symfony-app-dir": "app",
"symfony-web-dir": "web",
"symfony-assets-install": "symlink"
}
46
Captulo 2. Livro
Configurao e Instalao
Nesse ponto, todas as bibliotecas de terceiros necessrios encontram-se no diretrio vendor/. Voc tambm tem um
instalao padro da aplicao em app/ e alguns cdigos de exemplo no diretrio src/.
O Symfony2 tem um script para testar as configurao do servidor de forma visual, que ajuda garantir que o servidor
web e o PHP esto configurados para o framework. Utilize a seguinte URL para verificar a sua configurao:
http://localhost/config.php
Se algum problema foi encontrado, ele deve ser corrigido agora, antes de prosseguir.
Configurando as Permisses
Um problema comum que os diretrios app/cache e app/logs devem ter permisso de escrita para o
servidor web e para o usurio da linha de comando. Em um sistema UNIX, se o usurio do seu servidor web
for diferente do seu usurio da linha de comando, voc pode executar os seguintes comandos para garantir que
as permisses estejam configuradas corretamente. Mude o www-data para o usurio do servidor web e o
yourname para o usurio da linha de comando:
1. Utilizando ACL em um sistema que suporta chmod +a
Muitos sistemas permitem que voc utilize o comando chmod +a. Tente ele primeiro e se der erro tente o
prximo mtodo:
rm -rf app/cache/*
rm -rf app/logs/*
Note que se voc tem acesso a ACL no seu servidor, esse ser o mtodo recomendado, uma vez que alterar a
umask no uma operao thread-safe.
Quando tudo estiver feito, clique em Go to the Welcome page para acessar a sua primeira webpage Symfony2 real:
http://localhost/app_dev.php/
O Symfony2 dever lhe dar as boas vindas e parabeniza-lo pelo trabalho duro at agora!
2.1. Livro
47
Iniciando o Desenvolvimento
Agora que voc tem uma aplicao Symfony2 totalmente funcional, voc pode comear o desenvolvimento! A sua
distribuio deve conter alguns cdigos de exemplo - verifique o arquivo README.rst includo na distribuio (voc
pode abri-lo como um arquivo de texto) para aprender sobre os exemplos includos e como voc pode remov-los mais
tarde.
Se voc novo no Symfony, junte-se a ns em page_creation, onde voc aprender como criar pginas, mudar
configuraes e tudo mais que precisar para a sua nova aplicao.
Certifique-se tambm verificar o Cookbook, que contm uma grande variedade de artigos sobre a resoluo de problemas especficos com Symfony.
Utilizando Controle de Verso
Se voc est utilizando um sistema de controle de verso como Git ou Subversion, voc pode instala-lo e comear
a realizar os commits do seu projeto normalmente. A edio padro do Symfony o ponto inicial para o seu novo
projeto.
Para instrues especficas sobre a melhor maneira de configurar o seu projeto para ser armazenado no git, veja Como
Criar e Armazenar um Projeto Symfony2 no git.
Ignorando o diretrio vendor/
Se voc baixou o arquivo sem itens de terceiros (without vendors), voc pode ignorar todo o diretrio vendor/
com segurana e no envi-lo para o controle de verso. No Git, isso feito criando e o arquivo .gitignore e
adicionando a seguinte linha:
48
Captulo 2. Livro
/vendor/
Agora, o diretrio vendor no ser enviado para o controle de verso. Isso bom (na verdade, timo!) porque quando
algum clonar ou fizer check out do projeto, ele/ela poder simplesmente executar o script php composer.phar
install para instalar todas as bibliotecas vendor necessrias.
2.1.4 Controlador
Um controlador uma funo PHP que voc cria e que pega informaes da requisio HTTP para criar e retornar uma
resposta HTTP (como um objeto Response do Symfony2). A resposta pode ser uma pgina HTML, um documento
XML, um array JSON serializado, uma imagem, um redirecionamento, um erro 404 ou qualquer coisa que voc
imaginar. O controlador contm toda e qualquer lgica arbritria que sua aplicao precisa para renderizar o contedo
de uma pgina.
Para ver quo simples isso, vamos ver um controlador do Symfony2 em ao. O seguinte controlador deve renderizar
uma pgina que mostra apenas Hello world!:
use SymfonyComponentHttpFoundationResponse;
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 informaes da requisio, carregar um recurso do banco de dados, mandar um e-mail ou gravar informaes
na sesso do usurio. Mas em todos os casos, o controlador acabar retornando o objeto Response que ser mandado
de volta para o cliente.
No h nenhuma mgica e nenhum outro requisito para se preocupar! Aqui temos alguns exemplos comuns:
O Controlador A prepara um objeto Response representando o contedo da pgina inicial do site.
O Controlador B l o parmetro slug da requisio para carregar uma entrada do blog no banco de dados e
cria um um objeto Response mostrando o blog. Se o slug no for encontrado no banco de dados, ele cria e
retorna um objeto Response com um cdigo de status 404.
O Controlador C trata a o envio de um formulrio de contato. Ele l a informao do formulrio a partir da
requisio, salva a informao de contato no banco de dados e envia por e-mail a informao de contato para
o webmaster. Finalmente, ele cria um objeto Response que redireciona o navegador do cliente para a pgina
thank you do formulrio de contato.
O Ciclo de Vida da Requisio, Controlador e Resposta
Toda requisio 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 cdigo personalizado da sua aplicao:
1. Toda requisio tratada por um nico arquivo front controlador (por exemplo, app.php ou app_dev.php)
que inicializa a aplicao;
2. O Router l a informao da requisio (por exemplo, a URI), encontra uma rota que casa com aquela informao e l o parmetro _controller da rota;
3. O controlador que casou com a rota executado e o cdigo dentro do controlador cria e retorna um objeto
Response;
4. Os cabealhos HTTP e o contedo do objeto Response so enviados de volta para o cliente.
2.1. Livro
49
Criar uma pgina to fcil quanto criar um controlador (#3) e fazer uma rota que mapeie uma URL para aquele
controlador (#2).
Nota: Embora tenha um nome similar, um front controller diferente dos controladores dos quais vamos falar
nesse captulo. Um front controller um pequeno arquivo PHP que fica no seu diretrio web e atravs do qual todas as
requisies so direcionadas. Uma aplicao tpica ter um front controller de produo (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 aplicao.
Um Controlador Simples
Embora um controlador possa ser qualquer cdigo PHP que possa ser chamado (uma funo, um mtodo em um objeto
ou uma Closure), no Symfony2 um controlador geralmente um nico mtodo dentro de um objeto controlador.
Os controladores tambm so chamados de aes:
1
// src/Acme/HelloBundle/Controller/HelloController.php
2
3
4
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
5
6
7
8
9
10
11
12
class HelloController
{
public function indexAction($name)
{
return new Response(<html><body>Hello .$name.!</body></html>);
}
}
Dica:
Note que o controlador o mtodo indexAction, que fica dentro de uma classe controladora
(HelloController). No se confunda com a nomenclatura: uma classe controladora apenas um forma conveniente de agrupar vrios controladores/aes juntos. Geralmente a classe controladora ir agrupar vrios controladores/aes (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 concatenao de um nome para a classe controlador (ou seja, Hello) com a
palavra Controller. Essa uma conveno que fornece consistncia aos controladores e permite que eles
sejam referenciados usando apenas a primeira parte do nome (ou seja, Hello) na configurao de roteamento.
linha 8: Toda ao em uma classe controladora sufixada com Action e referenciada na configurao de
roteamento pelo nome da ao (index). Na prxima seo, voc criar uma rota que mapeia uma URI para
essa action. Voc aprender como os marcadores de posio das rotas ({name}) tornam-se argumentos no
mtodo da action ($name).
linha 10: O controlador cria e retorna um objeto Response.
Mapeando uma URL para um Controlador
O novo controlador retorna uma pgina HTML simples. Para ver realmente essa pgina no seu navegador voc precisa
criar uma rota que mapeia um padro especfico de URL para o controlador:
50
Captulo 2. Livro
YAML
# app/config/routing.yml
hello:
pattern:
/hello/{name}
defaults:
{ _controller: AcmeHelloBundle:Hello:index }
XML
<!-- app/config/routing.xml -->
<route id="hello" pattern="/hello/{name}">
<default key="_controller">AcmeHelloBundle:Hello:index</default>
</route>
PHP
// app/config/routing.php
$collection->add(hello, new Route(/hello/{name}, array(
_controller => AcmeHelloBundle:Hello:index,
)));
2.1. Livro
51
}
}
O controlador tem um nico argumento, $name, que corresponde ao parmetro {name} da rota casada (ryan
no nosso exemplo). Na verdade quando executa seu controlador, o Symfony2 casa cada um dos argumentos do
controlador com um parmetro da rota casada. Veja o seguinte exemplo:
YAML
# app/config/routing.yml
hello:
pattern:
/hello/{first_name}/{last_name}
defaults:
{ _controller: AcmeHelloBundle:Hello:index, color: green }
XML
<!-- app/config/routing.xml -->
<route id="hello" pattern="/hello/{first_name}/{last_name}">
<default key="_controller">AcmeHelloBundle:Hello:index</default>
<default key="color">green</default>
</route>
PHP
// app/config/routing.php
$collection->add(hello, new Route(/hello/{first_name}/{last_name}, array(
_controller => AcmeHelloBundle:Hello:index,
color
=> green,
)));
Captulo 2. Livro
// ..
}
Deixando o argumento opcional, no entanto, tudo corre bem. O seguinte exemplo no lana
uma exceo:
public function indexAction($first_name, $last_name, $color, $foo = bar) {
// ..
}
Nem todos os parmetros de roteamento precisam ser argumentos no seu controlador
Se, por exemplo, last_name no for importante para o seu controlador, voc pode omitir inteiramente ele:
public function indexAction($first_name, $color) {
// ..
}
Dica: Cada uma das rotas tem um parmetro _route especial, que igual ao nome da rota que foi casada (por
exemplo, hello). Embora no seja til geralmente, ele tambm fica disponvel como um argumento do controlador.
Por convenincia, voc tambm pode fazer com que o Symfony passe o objeto Request como um argumento para
seu controlador. Isso conveniente especialmente quando voc estiver trabalhando com formulrios, por exemplo:
use SymfonyComponentHttpFoundationRequest;
public function updateAction(Request $request) {
$form = $this->createForm(...);
$form->bind($request); // ...
}
A Classe Base do Controlador
Por convenincia, 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 vrios mtodos helper.
Adicione a instruo use no topo da sua classe Controller e ento modifique o HelloController para
estend-lo:
// 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)
{
2.1. Livro
53
Isso no muda realmente nada o jeito que seu controlador trabalha. Na prxima seo voc aprender sobre os mtodos
helper que a classe controladora base disponibiliza. Esses mtodos so apenas atalhos para usar funcionalidades do
ncleo do Symfony2 que esto disponveis para voc usando ou no a classe base Controller. Uma boa maneira
de ver a funcionalidade do ncleo em ao olhar a prpria classe Controller.
Dica: Estender a classe base opcional no Symfony; ela contm atalhos teis mas nada que seja mandatrio.
Voc tambm pode estender Symfony\Component\DependencyInjection\ContainerAware. O objeto
continer de servios ento ser acessvel por meio da propriedade container.
Nota: Voc tambm pode definir seus Controllers como Servios.
Se voc quiser redirecionar o usurio para outra pgina, use o mtodo redirect():
public function indexAction()
{
return $this->redirect($this->generateUrl(homepage));
}
O mtodo generateUrl() apenas uma funo helper que gera a URL de uma determinada rota. Para mais
informaes, veja o captulo Roteamento.
Por padro, o mtodo redirect() efetua um redirecionamento 302 (temporrio). Para realizar um redirecionamento 301 (permanente), modifique o segundo argumento:
public function indexAction()
{
return $this->redirect($this->generateUrl(homepage), 301);
}
Dica: O mtodo redirect() simplesmente um atalho que cria um objeto Response especializado em redirecionar o usurio. Ele equivalente a:
use Symfony\Component\HttpFoundation\RedirectResponse;
return new RedirectResponse($this->generateUrl(homepage));
Direcionando
Voc tambm pode facilmente direcionar internamente para outro controlador com o mtodo forward(). Em vez
de redirecionar o navegador do usurio, ele faz uma sub-requisio interna e chama o controlador especificado. O
54
Captulo 2. Livro
indexAction($name)
$this->forward(AcmeHelloBundle:Hello:fancy, array(
=> $name,
=> green
Note que o mtodo forward() usa a mesma representao em string do controlador que foi usada na configurao
de roteamento. Nesse caso, a classe controlador alvo ser HelloController dentro de AcmeHelloBundle. O
array passado para o mtodo se torna os argumentos no controlador resultante. Essa mesma interface usada quando se
embutem controladores em templates (veja Incorporao de Controllers). O mtodo 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 no
importa. O Symfony2 combina os nomes das chaves dos ndices (por exemplo, name) com os nomes dos argumentos
do mtodo (por exemplo, $name). Se voc mudar a ordem dos argumentos, o Symfony2 continuar passando os
valores corretos para cada varivel.
Dica: Assim como em outros mtodos do Controller base, o mtodo forward apenas um atalho para
uma funcionalidade nuclear do Symfony2. Um direcionamento pode ser realizado diretamente por meio do servio
http_kernel. Um direcionamento retorna um objeto Response:
$httpKernel
$response =
name
color
));
= $this->container->get(http_kernel);
$httpKernel->forward(AcmeHelloBundle:Hello:fancy, array(
=> $name,
=> green,
Renderizando Templates
Apesar de no ser um requisito, a maioria dos controladores ir, no fim das contas, renderizar um template que
responsvel por gerar o HTML (ou outro formato) para o controlador. O mtodo renderView() renderiza um
template e retorna seu contedo. O contedo 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 mtodo render(), que retorna um objeto Response com o
contedo do template:
return $this->render(AcmeHelloBundle:Hello:index.html.twig, array(name => $name));
Resources/views/Hello/index.html.twig
dentro
do
55
Quando se estende a classe controladora base, voc pode acessar qualquer um dos servios Symfony2 atravs do
mtodo get(). Aqui esto alguns dos servios mais comuns que voc pode precisar:
$request = $this->getRequest();
$templating = $this->get(templating);
$router = $this->get(router);
$mailer = $this->get(mailer);
Existem outros inmeros servios disponveis e voc encorajado a definir os seus prprios. Para listar todos os
servios disponveis, use o comando do console container:debug:
php app/console container:debug
Em todo caso, uma pgina de erro estilizada mostrada para o usurio final e uma pgina de erro com informaes de
debug completa mostrada para o desenvolvedor (no caso de visualizar a pgina no modo debug). Ambas as pginas
podem ser personalizadas. Para detalhes, leia a receita Como personalizar as pginas de erro no cookbook.
56
Captulo 2. Livro
Gerenciando a Sesso
O Symfony2 fornece um objeto de sesso muito bom que voc pode usar para guardar informaes sobre o usurio (seja ele uma pessoa real usando um navegador, um rob ou um web service) entre requisies. Por padro, o
Symfony2 guarda os atributos em um cookie usando as sesses nativas do PHP.
O armazenamento e a recuperao de informaes da sesso so 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 doesnt exist
$filters = $session->get(filters, array());
Voc tambm guardar pequenas mensagens que sero armazenadas na sesso do usurio apenas por uma requisio.
Isso til no processamento de formulrios: voc pode redirecionar o usurio e mostrar uma mensagem especial na
requisio seguinte. Esses tipos de mensagens so chamadas de mensagens flash.
Por exemplo, imagine que voc esteja processando a submisso de um formulrio:
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 requisio, o controlador define uma mensagem flash notice e ento faz o redirecionamento. O nome (notice) no importante - apenas o que voc usa para identificar o tipo da mensagem.
No template da prxima action, o cdigo a seguir poderia ser usado para renderizar a mensagem notice:
Twig
{% for flashMessage in app.session.flashbag.get(notice) %}
<div class="flash-notice">
{{ flashMessage }}
</div>
{% endfor %}
PHP
2.1. Livro
57
Por definio, as mensagens flash so feitas para existirem por exatamente uma requisio (elas se vo num instante
- gone in a flash). Elas foram projetadas para serem usadas entre redirecionamentos exatamente como voc fez nesse
exemplo.
O Objeto Response
O nico requisito de um controlador retornar um objeto Response. A classe Response uma abstrao PHP em
volta da resposta HTTP - a mensagem em texto cheia de cabealhos HTTP e contedo 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);
Dica: A propriedade headers a classe HeaderBag com vrios mtodos teis para ler e modificar os cabealhos
do Response. Os nomes dos cabealhos so normalizados de forma que usar Content-Type seja equivalente a
content-type ou mesmo content_type.
O Objeto Request
Alm dos valores nos marcadores de roteamento, o controlador tambm 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 cabealhos da requisio so guardados em um objeto HeaderBag e so
facilmente acessados.
Consideraes Finais
Sempre que criar uma pgina, no final voc precisar escrever algum cdigo que contenha a lgica dessa pgina. No
Symfony, isso chamado de controlador, e ele uma funo PHP que faz tudo que for necessrio para no fim retornar
o objeto Response final que ser retornado ao usurio.
Para facilitar a vida, voc pode escolher estender uma classe Controller base, que contm mtodos que so atalhos
para muitas tarefas comuns dos controladores. Por exemplo, uma vez que voc no queira colocar cdigo HTML no
seu controlador, voc pode usar o mtodo render() para renderizar e retornar o contedo de um template.
58
Captulo 2. Livro
Em outros captulos, voc ver como o controlador pode ser usado para persistir e buscar objetos em um banco de
dados, processar submisses de formulrios, gerenciar cache e muito mais.
Saiba mais no Cookbook
Como personalizar as pginas de erro
Como definir Controladores como Servios
2.1.5 Roteamento
URLs bonitas so uma obrigao absoluta para qualquer aplicao web sria. Isto significa deixar para trs URLs feias
como index.php?article_id=57 em favor de algo como /read/intro-to-symfony.
Ter flexibilidade ainda mais importante. E se voc precisasse mudar a URL de uma pgina de /blog para /news?
Quantos links voc precisaria para investig-los e atualizar para fazer a mudana ? Se voc est usando o roteador do
Symfony, a mudana simples.
O roteador do Symfony2 deixa voc definir URLs criativas que voc mapeia para diferentes reas de sua aplicao. No
final deste captulo, voc ser capaz de : * Criar rotas complexas que mapeiam para os controladores * Gerar URLs
dentro de templates e controladores * Carregar recursos de roteamento de pacotes (ou algum lugar a mais) * Depurar
suas rotas
Roteamento em Ao
Um rota um mapa de um padro URL para um controlador. Por exemplo, suponha que voc queira ajustar qualquer
URL como /blog/my-post ou /blog/all-about-symfony e envi-la ao controlador que pode olhar e mudar
aquela entrada do blog. A rota simples:
YAML
# app/config/routing.yml
blog_show:
pattern:
/blog/{slug}
defaults: { _controller: AcmeBlogBundle:Blog:show }
XML
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog_show, new Route(/blog/{slug}, array(
2.1. Livro
59
O padro definido pela rota blog_show age como /blog/* onde o coringa dado pelo nome slug. Para a URL
/blog/my-blog-post, a varivel slug obtm um valor de my-blog-post, que est disponvel para voc usar
em seu controlador (continue lendo).
O parmetro _controller uma chave especial que avisa o Symfony qual controlador deveria ser executado
quando uma URL corresponde a essa rota. A string _controller chamada logical name. Ela segue um padro
que aponta para uma classe e mtodo PHP especfico:
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller
{
public function showAction($slug)
{
$blog = // use the $slug varible to query the database
return $this->render(AcmeBlogBundle:Blog:show.html.twig, array(
blog => $blog,
));
}
}
Parabns ! Voc agora criou sua primeira rota conectou ela a um controlador. Agora, quando voc visitar
/blog/my-post, o controlador showAction ser executado e a varivel $slug ser igual a my-post.
Esse o objetivo do roteador do Symfony2: mapear a URL de uma requisio para um controlador. Ao longo do
caminho, voc aprender todos os tipos de truques que tornam o mapeamento fcil, mesmo das URLS mais complexas.
Roteamento: Por debaixo do capuz
Quando uma requisio feita para sua aplicao, ela contm um endereo para o recurso exato que o cliente est
requisitando.Esse endereo chamado de URL, (ou URI), e poderia ser /contact, /blog/read-me, ou qualquer
coisa a mais. Considere a seguinte requisio de exemplo :
GET /blog/my-blog-post
O objetido do sistema de roteamento do Symfony2 analisar esta URL e determinar qual controlador deveria ser
executado. O processo interior parece isso:
1. A requisao controlada pelo front controller do Symfony2 front controller (ex: app.php);
2. O ncleo do Symfony2 (ex: Kernel) pergunta ao roteador para inspecionar a requisio;
3. O roteador ajusta a URL recebida para uma rota especfica e retorna informao sobre a rota, incluindo o
controlador que deveria ser executado;
4. O kernel do Symfony2 executa o controlador, que retorna por ltimo um objeto Response.
60
Captulo 2. Livro
Figura 2.2: A camada de roteamento uma ferramenta que traduza a URL recebida em um controlador especfico para
executar.
Criando rotas
Symfony carrega todas as rotas para sua aplicao de um arquivo de configurao de roteamento. O arquivo geralmente app/config/routing.yml, mas pode ser configurado para ser qualquer coisa (incluindo um arquivo
XML ou PHP) via arquivo de configurao de aplicao:
YAML
# app/config/config.yml
framework:
# ...
router:
{ resource: "%kernel.root_dir%/config/routing.yml" }
XML
<!-- app/config/config.xml -->
<framework:config ...>
<!-- ... -->
<framework:router resource="%kernel.root_dir%/config/routing.xml" />
</framework:config>
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(
// ...
router
=> array(resource => %kernel.root_dir%/config/routing.php),
));
Dica: Mesmo que toda as rotas sejam carregadas de um arquivo s, uma prtica comum incluir recursos de
roteamento adicionais de dentro do arquivo. Veja a seo:ref:routing-include-external-resources para mais informao.
2.1. Livro
61
Definir uma rota fcil, e uma aplicao tpica ter um monte de rotas. A basic route consists of just two parts: the
pattern to match and a defaults array:
YAML
_welcome:
pattern:
defaults:
/
{ _controller: AcmeDemoBundle:Main:homepage }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="_welcome" pattern="/">
<default key="_controller">AcmeDemoBundle:Main:homepage</default>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(_welcome, new Route(/, array(
_controller => AcmeDemoBundle:Main:homepage,
)));
return $collection;
A rota combina a homepage (/) e mapeia ele para o controlador AcmeDemoBundle:Main:homepage. A string
_controller traduzida pelo Symfony2 em uma funo verdadeira do PHP e exectudada. Aquele processo ir ser
explicado brevemente na seo Padro de nomeao do Controlador.
Roteando com Espaos reservados
Claro que o sistema de roteamento suporta rotas muito mais interessantes. Muitas rotas iro conter uma ou mais
chamadas de espaos reservados coringa:
YAML
blog_show:
pattern:
defaults:
/blog/{slug}
{ _controller: AcmeBlogBundle:Blog:show }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
62
Captulo 2. Livro
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog_show, new Route(/blog/{slug}, array(
_controller => AcmeBlogBundle:Blog:show,
)));
return $collection;
O padro ir corresponder a qualquer coisa que parea /blog/*. Melhor ainda, o valor correspondendo ao espao
reservado {slug} estar disponvel no seu controlador. Em outras palavras, se a URL /blog/hello-world,
uma varivel $slug, com o valor de hello-world, estar disponvel no controlador. Isto pode ser usado, por
exemplo, para carregar o post do blog correspondendo quela string.
Este padro no ir, entretanto, simplesmente ajustar /blog. Isso porque, por padro, todos os espaos reservados
so requeridos. Isto pode ser mudado ao adicionar um valor de espao reservado ao array defaults.
Espaos reservados Requeridos e Opcionais
Para tornar as coisas mais excitantes, adicione uma nova rota que mostre uma lista de todos os posts do blog para essa
aplicao de blog imaginria:
YAML
blog:
pattern:
defaults:
/blog
{ _controller: AcmeBlogBundle:Blog:index }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="blog" pattern="/blog">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog, new Route(/blog, array(
_controller => AcmeBlogBundle:Blog:index,
)));
2.1. Livro
63
return $collection;
At agora, essa rota to simples quanto possvel - contm nenhum espao reservado e s ir corresponder URL
exata /blog. Mas e se voc precisar dessa rota para suportar paginao, onde /blog/2 mostre a segunda pgina do
entradas do blog ? Atualize a rota para ter uma nova {page} de espao reservado:
YAML
blog:
pattern:
defaults:
/blog/{page}
{ _controller: AcmeBlogBundle:Blog:index }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="blog" pattern="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog, new Route(/blog/{page}, array(
_controller => AcmeBlogBundle:Blog:index,
)));
return $collection;
Como o espao reservado {slug} anterior, o valor correspondendo a {page} estar disponvel dentro do seu controlador. Este valor pode ser usado para determinar qual conjunto de posts do blog mostrar para determinada pgina.
Mas espere ! Como espaos reservados so requeridos por padro, essa rota no ir mais corresponder simplesmente
a /blog. Ao invs disso, para ver a pgina 1 do blog, voc precisaria usar a URL /blog/1! Como no h meios
para uma aplicao web ricase comportar, modifique a rota para fazer o parmetro {page} opcional. Isto feito ao
incluir na coleo defaults:
YAML
blog:
pattern:
defaults:
/blog/{page}
{ _controller: AcmeBlogBundle:Blog:index, page: 1 }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="blog" pattern="/blog/{page}">
64
Captulo 2. Livro
<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</default>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog, new Route(/blog/{page}, array(
_controller => AcmeBlogBundle:Blog:index,
page => 1,
)));
return $collection;
Ao adicionar page para a chave defaults, o espao reservado {page} no mais requerido. A URL /blog ir
corresponder a essa rota e o valor do parmetro page ser fixado para 1. A URL /blog/2 ir tambm corresponder,
atribuindo ao parmetro page o valor 2. Perfeito.
/blog
/blog/1
/blog/2
{page} = 1
{page} = 1
{page} = 2
Adicionando Requisitos
/blog/{page}
{ _controller: AcmeBlogBundle:Blog:index, page: 1 }
blog_show:
pattern:
defaults:
/blog/{slug}
{ _controller: AcmeBlogBundle:Blog:show }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="blog" pattern="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</default>
</route>
<route id="blog_show" pattern="/blog/{slug}">
<default key="_controller">AcmeBlogBundle:Blog:show</default>
</route>
</routes>
2.1. Livro
65
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog, new Route(/blog/{page}, array(
_controller => AcmeBlogBundle:Blog:index,
page => 1,
)));
$collection->add(blog_show, new Route(/blog/{show}, array(
_controller => AcmeBlogBundle:Blog:show,
)));
return $collection;
Voc pode apontar o problema ? Perceba que ambas as rotas tem padro que combinam URLs que paream /blog/*.
O roteador do Symfony ir sempre escolher a primeira rota correspondente que ele encontra. Em outras palavras, a
rota blog_show nunca ser correspondida. Ao invs disso, uma URL como /blog/my-blog-post ir corresponder primeira rota (blog) e retorna um valor sem sentido de my-blog-post ao parmetro {page}.
URL
/blog/2
/blog/my-blog-post
route
blog
blog
parameters
{page} = 2
{page} = my-blog-post
A resposta para o problema adicionar mais requisitos de rota. As rotas neste exemplo funcionariam perfeitamente
se o padro /blog/{page} somente correspondesse a URLs onde a poro {page} fosse um integer. Felizmente,
requisitos de expresses regulares podem facilmente ser adicionados para cada parmetro. Por exemplo:
YAML
blog:
pattern:
/blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
requirements:
page: \d+
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="blog" pattern="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</default>
<requirement key="page">\d+</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog, new Route(/blog/{page}, array(
66
Captulo 2. Livro
O requisito \d+ uma expresso regular que diz o valor do parmetro {page} deve ser um dgito (em outras palavras,
um nmero). A rotablog ainda ir correponder a uma URL como /blog/2 (porque 2 um nmero), mas no ir
mair corresponder a URL como /blog/my-blog-post (porque my-blog-post no um nmero).
Como resultado, uma URL como /blog/my-blog-post no ir corresponder apropriadamente rota
blog_show.
URL
/blog/2
/blog/my-blog-post
rota
blog
blog_show
parmetros
{page} = 2
{slug} = my-blog-post
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="homepage" pattern="/{culture}">
<default key="_controller">AcmeDemoBundle:Main:homepage</default>
<default key="culture">en</default>
<requirement key="culture">en|fr</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
2.1. Livro
67
Para requisies recebidas, a parte {culture} da URL comparada com a expresso regular (en|fr).
/
/en
/fr
/es
{culture} = en
{culture} = en
{culture} = fr
wont match this route
Em adio URL, voc tambm pode ajustar o mtodo da requisio recebida (em outras palavras, GET, HEAD,
POST, PUT, DELETE).Suponha que voc tenha um formulrio de contato com dois controladores - um para exibir
o formulrio (em uma requisio GET) e uma para processar o formulrio quando ele enviado (em uma requisio
POST). Isto pode ser acompanhando com a seguinte configurao de rota:
YAML
contact:
pattern: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
requirements:
_method: GET
contact_process:
pattern: /contact
defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
requirements:
_method: POST
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="contact" pattern="/contact">
<default key="_controller">AcmeDemoBundle:Main:contact</default>
<requirement key="_method">GET</requirement>
</route>
<route id="contact_process" pattern="/contact">
<default key="_controller">AcmeDemoBundle:Main:contactProcess</default>
<requirement key="_method">POST</requirement>
</route>
</routes>
PHP
68
Captulo 2. Livro
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(contact, new Route(/contact, array(
_controller => AcmeDemoBundle:Main:contact,
), array(
_method => GET,
)));
$collection->add(contact_process, new Route(/contact, array(
_controller => AcmeDemoBundle:Main:contactProcess,
), array(
_method => POST,
)));
return $collection;
Apesar do fato que estas duas rotas tem padres idnticos (/contact), a primeira rota ir aceitar somente requisies
GET e a segunda rota ir somente aceitar requisis POST. Isso significa que voc pode exibir o formulrio e enviar o
formulrio pela mesma URL, enquanto usa controladores distintos para as duas aes.
Nota: Se nenhum valor _method em requitement for especificado, a rota ir aceitar todos os metodos.
Como os outros requisitos, o requisito _method analisado como uma expresso regular. Para aceitar requisies
GET ou POST, voc pode usar GET|POST.
Exemplo avanado de roteamento
At esse ponto, voc tem tudo que voc precisa para criar uma poderosa estrutura de roteamento em Symfony. O
exemplo seguinte mostra quo flexvel o sistema de roteamento pode ser:
YAML
article_show:
pattern: /articles/{culture}/{year}/{title}.{_format}
defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
requirements:
culture: en|fr
_format: html|rss
year:
\d+
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="article_show" pattern="/articles/{culture}/{year}/{title}.{_format}">
<default key="_controller">AcmeDemoBundle:Article:show</default>
<default key="_format">html</default>
<requirement key="culture">en|fr</requirement>
<requirement key="_format">html|rss</requirement>
<requirement key="year">\d+</requirement>
2.1. Livro
69
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(homepage, new Route(/articles/{culture}/{year}/{title}.{_format}, array(
_controller => AcmeDemoBundle:Article:show,
_format => html,
), array(
culture => en|fr,
_format => html|rss,
year => \d+,
)));
return $collection;
Como voc viu, essa rota s ir funcionar se a parte {culture} da URL ou en ou fr e se {year} um nmero.
Esta rota tambm mostra como voc pode usar um perodo entre espaos reservados ao invs de uma barra. URLs que
correspondam a esta rota poderia parecer como:
/articles/en/2010/my-post
/articles/fr/2010/my-post.rss
O Parmetro de Roteamento Especial _format
Esse exemplo tambm resslta o parmetro de roteamento especial _format. Quando usa esse parmetro, o
valor correspondido se torna o formato requisitado do ojeto Request. Ultimamente, o formato requisitado
usado para certas coisas como as configurar o Content-Type da resposta (ex: um formato de requisio
json traduz em um Content-Type de application/json). Ele tambm pode ser usado no controlador
para alterar um template diferente para cada valor de _format. O parmetro _format um modo muito
poderoso para alterar o mesmo contedo em formatos diferentes.
Como voc viu, cada parmetro de roteamento ou valor padro est eventualmente disponvel como um argumento no
mtodo do controlador. Adicionalmente, existem trs parmetros que so especiais: cada um adiciona uma parte nica
de funcionalidade dentro da sua aplicao:
_controller: Como voc viu, este parmetro usado para determinar qual controlador executado quando
a rota correspondida;
_format: Usado para fixar o formato de requisio (read more);
_locale: Usado para fixar a localidade no pedido (read more);
Dica: Se voc usar o parmetro _locale na rota, aquele valor ser tambm armazenado na sesso, ento, os pedidos
posteriores mantm a mesma localidade.
70
Captulo 2. Livro
Classe do Controlador
BlogController
Nome do Mtodo
showAction
Perceba que Symfony adiciona a string Controller para o nome da classe (Blog => BlogController) e
Action para o nome do mtodo (show => showAction).
Voc tambm poderia referir a esse controler usando os nomes totalmente qualificados de classe e mtodo:Acme\BlogBundle\Controller\BlogController::showAction. Mas se voc seguir alguma convenes simples, o nome lgico mais conciso e permite mais flexibilidade.
Nota: Em complemento ao utilizar o nome lgico ou o nome de classe totalmente qualificado, Symfony suporta um terceiro modo de referir a um controlador. Esse mtodo usa somente um separador de dois pontos (ex:
service_name:indexAction) e referir um controlador como um servio (veja Como definir Controladores
como Servios).
Na realidade, a coleo inteira defaults mesclada com um valor de parmetro para formar um nico array. Cada
chave daquele array est disponvel como um argumento no controlador.
Em outras palavras, para cada argumento do mtodo do seu controlador, Symfony procura por um parmetro de
rota daquele nome e atribui o valor para aquele argumento. No exemplo avanado acima, qualquer combinao (em
qualquer ordem) das seguintes variveis poderia ser usada como argumentos para o mtodo showAction():
$culture
2.1. Livro
71
$year
$title
$_format
$_controller
Como os espaos resercados e a coleo defaults so mesclados juntos, mesmo a varivel $_controller est
disponvel. Para uma discusso mais detalhada, veja Parmetros de Rota como Argumentos do Controlador.
Dica: Voc tambm pode usar uma varivel especial $_route, que fixada para o nome da rota que foi correspondida.
XML
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<import resource="@AcmeHelloBundle/Resources/config/routing.xml" />
</routes>
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
$collection = new RouteCollection();
$collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"));
return $collection;
Nota: Quando importar recursos do YAML, a chave (ex: acme_hello) insignificante. Somente esteja certo que
nica, ento nenhuma outra linha a sobrescrever.
A chave resource carrega o recurso de determinado roteamento. Neste exemplo o recurso um atalho inteiro para
o arquivo, onde a sintaxe do atalho @AcmeHelloBundle resolve o atalho daquele pacote. O arquivo importado
poderia parecer algo como isso:
YAML
72
Captulo 2. Livro
# src/Acme/HelloBundle/Resources/config/routing.yml
acme_hello:
pattern: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }
XML
<!-- src/Acme/HelloBundle/Resources/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="acme_hello" pattern="/hello/{name}">
<default key="_controller">AcmeHelloBundle:Hello:index</default>
</route>
</routes>
PHP
// src/Acme/HelloBundle/Resources/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(acme_hello, new Route(/hello/{name}, array(
_controller => AcmeHelloBundle:Hello:index,
)));
return $collection;
As rotas daquele arquivo so analisadas e carregadas da mesma forma que o arquivo principal de roteamento.
Prefixando Rotas Importadas
Voc tambm pode escolher providenciar um prefixo para as rotas importadas. Por exemplo suponha que voc
queira que a rota acme_hello tnha um padro final de /admin/hello/{name} ao invs de simplesmente
/hello/{name}:
YAML
# app/config/routing.yml
acme_hello:
resource: "@AcmeHelloBundle/Resources/config/routing.yml"
prefix:
/admin
XML
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<import resource="@AcmeHelloBundle/Resources/config/routing.xml" prefix="/admin" />
</routes>
2.1. Livro
73
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
A string /admin ir agora ser prefixada ao padro de cada rota carregada do novo recurso de roteamento.
Visualizando e Depurando Rotas
Enquanto adiciona e personalizar rotas, til ser capaz de visualizar e obter informao detalhada sobre suas rotas. Um
grande modo para ver cada rota em sua aplicao pelo comando de console router:debug. Execute o seguinte
comando a partir da raiz de seu projeto.
php app/console router:debug
O comando ir imprimir uma lista til de todas as rotas configuradas em sua aplicao:
homepage
contact
contact_process
article_show
blog
blog_show
ANY
GET
POST
ANY
ANY
ANY
/
/contact
/contact
/articles/{culture}/{year}/{title}.{_format}
/blog/{page}
/blog/{slug}
Voc tambm pode obter informao muito especfica em uma rota individual ao incluir o nome da rota aps o comando:
php app/console router:debug article_show
Gerando URLs
O sistema de roteamento deveria tambm ser usado para gerar URLs. Na realidade, roteamento um sistema bidirecional: mapeando a URL para um controlador+parmetros e parmetros+rota de voltar para a URL. Os mtodos
match() e generate() formam esse sistema bi-directional. Considere a rota blog_show de um exemplo anterior:
$params = $router->match(/blog/my-blog-post);
// array(slug => my-blog-post, _controller => AcmeBlogBundle:Blog:show)
$uri = $router->generate(blog_show, array(slug => my-blog-post));
// /blog/my-blog-post
Para gerar a URL, voc precisa especificar o nome da rota (ex: blog_show) e quaisquer coringas(ex: slug =
my-blog-post) usado no padro para aquela rota. Com essa informao, qualquer URL pode ser facilmente gerada:
74
Captulo 2. Livro
Em uma sesso futura, voc ir aprender como gerar URLs a partir de templates.
Dica: Se o frontend de sua aplicao usa requisies AJAX, voc poderia querer ser capaz de ferar URLs em
JavaScript baseados na sua configurao de roteamento. Ao usar FOSJsRoutingBundle, voc poderia fazer exatamente
isso:
var url = Routing.generate(blog_show, { "slug": my-blog-post});
Por padro, o roteador ir gerar URLs relativas (ex: /blog). Para gerar uma URL absoluta, simplesmente passe
true ao terceiro argumento do mtodo generate():
$router->generate(blog_show, array(slug => my-blog-post), true);
// http://www.example.com/blog/my-blog-post
Nota: O host que usado quando gera uma URL absoluta o host do objeto Request atual. Isso detectado
automaticamente baseado na informao do servidor abastecida pelo PHP. Quando gerar URLs absolutas para rodar
scripts a partir da linha de comando, voc precisar fixar manualmente o host no objeto Request:
$request->headers->set(HOST, www.example.com);
O mtodo generate pega um array de valores coringa para gerar a URI. Mas se voc passar valores extras, eles
sero adicionados ao URI como uma string de consulta:
$router->generate(blog, array(page => 2, category => Symfony));
// /blog/2?category=Symfony
O lugar mais comum para gerar uma URL pelo template, ao fazer vinculao entre pginas na sua aplicao.Isso
feito da mesma forma que antes, mas usando uma funo helper de template:
Twig
<a href="{{ path(blog_show, { slug: my-blog-post }) }}">
Read this blog post.
</a>
2.1. Livro
75
PHP
PHP
Sumrio
Roteamento um sistema para mapear a URL de requisis recebidas para a funo do controlador que deveria ser
chamada para processar a requisio. Em ambas permite a voc especificar URLs bonitas e manter a funcionalidade
de sua aplicao desacoplada daquelas URLs. Roteamento um mecanismo de duas vias, significando que tambm
deveria ser usada para gerar URLs.
Aprenda mais do Cookbook
Como forar as rotas a usar sempre HTTPS ou HTTP
76
Captulo 2. Livro
<ul id="navigation">
<?php foreach ($navigation as $item): ?>
<li>
<a href="<?php echo $item->getHref() ?>">
<?php echo $item->getCaption() ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</body>
</html>
Mas Symfony2 empacota at mesmo uma linguagem muito poderosa de template chamada Twig. Twig permite a voc
escrever templates consisos e legveis que so mais amigveis para web designers e, de certa forma, mais poderosos
que templates de PHP:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Symfony!</title>
</head>
<body>
<h1>{{ page_title }}</h1>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>
</body>
</html>
}}: Diga algo: exibe uma varivel ou o resultado de uma expresso para o template;
{% ... %}: Faa algo: uma tag que controla a lgica do template; ela usada para executar certas
sentenas como for-loops por exemplo.
Nota: H uma terceira sintaxe usada para criar comentrios {# this is a comment #}. Essa sintaxe pode ser
usada atravs de mltiplas linhas, parecidas com a sintaxe equivalente em PHP /* comment */.
Twig tambm contm filtros, que modificam contedo antes de serem interpretados. O seguinte filtro transforma a
varivel title toda em letra maiscula antes de interpret-la:
{{ title | upper }}
Twig vem com uma longa lista de tags e filtros_ que esto disponveis por padro. Voc pode at mesmo adicionar
suas prprias extenses_ para o Twig quando precisar.
Dica:
Registrar uma extenso Twig to fcil quanto criar um novo servio e atribuir tag nele com
twig.extension tag.
Como voc ver atravs da documentao, Twig tambm suporta funes e nova funes podem ser facilmente adicionadas. Por exemplo, a seguinte funo usa uma tag padro for e a funo cycle para ento imprimir dez tags div,
alternando entre classes odd e even:
2.1. Livro
77
{% for i in 0..10 %}
<div class="{{ cycle([odd, even], i) }}">
<!-- some HTML here -->
</div>
{% endfor %}
Durante este captulo, exemplos de template sero mostrados tanto em Twig como PHP.
Por que Twig?
Templates Twig so feitas para serem simples e no iro processar tags PHP. Isto pelo design: o sistema de
template do Twig feito para expressar apresentao, no lgica de programa. Quanto mais voc usa Twig, mais
voc ir apreciar e beneficiar desta distino. E claro, voc ser amado por web designers de todos os lugares.
Twig pode tambm fazer coisas que PHP no pode, como por exemplo herana verdadeira de template (Templates do Twig compilam classes PHP que herdam uma da outra), controle de espao em branco, caixa de areia, e a
incluso de funes personalizadas e filtros que somente afetam templates. Twig contm pequenos recursos que
fazem escrita de templates mais fcil e mais concisa. Considere o seguinte exemplo, que combina um loop com
uma sentena lgia if:
<ul>
{% for user in users %}
<li>{{ user.username }}</li>
{% else %}
<li>No users found</li>
{% endfor %}
</ul>
Twig rpido. Cada template Twig compilado para uma classe nativa PHP que processada na execuo. As
classes compiladas so localizadas no diretrio app/cache/{environment}/twig (onde {environment}
o ambiente, como dev ou prod), e em alguns casos pode ser til durante a depurao. Veja environments-summary
para mais informaes de ambientes.
Quando o modo debug habilitado (comum no ambiente dev), um template Twig ser automaticamente recompilado
quando mudanas so feitas nele. Isso significa que durante o desenvolvimento voc pode alegremente fazer mudanas
para um template Twig e imediatamente ver as mudanas sem precisar se preocupar sobre limpar qualquer cache.
Quando o modo debug desabilitado (comum no ambiente prod), entretanto, voc deve limpar o cache do diretrio
Twig para que ento os templates Twig se regenerem. Lembre de fazer isso quando distribuir sua aplicao.
Herana e Layouts de Template
Mais frequentemente que no, templates compartilham elementos comuns em um projeto, como o header, footer,
sidebar ou outros. Em Symfony2, ns gostamos de pensar sobre esse problema de forma diferente: um template pode
ser decorado por outro. Isso funciona exatemente da mesma forma como classes PHP: herana de template permite
voc construir um layout de template base que contenha todos os elementos comuns de seu site definidos como
blocos (pense em classe PHP com mtodos base). Um template filho pode extender o layout base e sobrepor os
blocos (pense subclasse PHP que sobreponha certos mtodos de sua classe pai).
Primeiro, construa um arquivo de layout de base:
Twig
78
Captulo 2. Livro
{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{% block title %}Test Application{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block body %}{% endblock %}
</div>
</body>
</html>
PHP
<!-- app/Resources/views/base.html.php -->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php $view[slots]->output(title, Test Application) ?></title>
</head>
<body>
<div id="sidebar">
<?php if ($view[slots]->has(sidebar): ?>
<?php $view[slots]->output(sidebar) ?>
<?php else: ?>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
<?php endif; ?>
</div>
<div id="content">
<?php $view[slots]->output(body) ?>
</div>
</body>
</html>
Nota: Apesar da discusso sobre herana de template ser em termos do Twig, a filosofia a mesma entre templates
Twig e PHP.
Este template define o esqueleto do documento base HTML de um pgina simples de duas colunas. Neste exemplo, trs
reas {% block %} so definidas (title, sidebar e body). Cada bloco pode ser sobreposto por um template
filho ou largado com sua implementao padro. Esse template poderia tambm ser processado diretamente. Neste
caso os blocos title, sidebar e body blocks deveriam simplesmente reter os valores padro neste template.
2.1. Livro
79
PHP
<!-- src/Acme/BlogBundle/Resources/views/Blog/index.html.php -->
<?php $view->extend(::base.html.php) ?>
<?php $view[slots]->set(title, My cool blog posts) ?>
<?php $view[slots]->start(body) ?>
<?php foreach ($blog_entries as $entry): ?>
<h2><?php echo $entry->getTitle() ?></h2>
<p><?php echo $entry->getBody() ?></p>
<?php endforeach; ?>
<?php $view[slots]->stop() ?>
Nota: O template pai idenficado por uma sintaxe especial de string (::base.html.twig) que indica que o
template reside no diretrio app/Resources/views do projeto. Essa conveno de nomeamento explicada
inteiramente em Nomeao de Template e Localizaes.
A chave para herana template a tag {% extends %}. Ela avisa o engine de template para primeiro avaliar o template base, que configura o layout e define vrios blocos. O template filho ento processado, ao ponto que os blocos
title e body do template pai sejam substitudos por aqueles do filho. Dependendo do valor de blog_entries,
a sada poderia parecer com isso:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>My cool blog posts</title>
</head>
<body>
<div id="sidebar">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</div>
<div id="content">
<h2>My first post</h2>
<p>The body of the first post.</p>
<h2>Another post</h2>
<p>The body of the second post.</p>
80
Captulo 2. Livro
</div>
</body>
</html>
Perceba que como o template filho no definiu um bloco sidebar, o valor do template pai usado no lugar. Contedo
dentro de uma tag {% block %} em um template pai sempre usado por padro.
Voc pode usar muitos nveis de herana quanto quiser. Na prxima sesso, um modelo comum de herana de trs
nveis ser explicado assim como os templates so organizados dentro de um projeto Symfony2.
Quando trabalhar com herana de template, aqui esto algumas dicas para guardar na cabea:
Se voc usa {% extends %} em um template, ele deve ser a primeira tag naquele template.
Quanto mais tags {% block %} voc tiver no template base, melhor. Lembre, templates filhos no precisam
definir todos os blocos do pai, ento criar tantos blocos em seus templates base quanto voc quiser e dar a cada
um padro sensato. Quanto mais blocos seus templates base tiverem, mais flexvel seu layout ser.
Se voc achar voc mesmo duplicando contedo em um determinado nmero de templates, isto provavelmente
significa que voc deveria mover aquele contedo para um {% block %} no template pai. Em alguns casos,
uma soluo melhor pode ser mover o contedo para um novo template e incluir ele (veja Incluir outras
Templates).
Se voc precisa obter o contedo de um bloco do template pai, voc pode usar a funo {{ parent() }}.
Isso til se voc quiser adicionar ao contedo de um bloco pai ao invs de sobrepor ele:
.. code-block:: html+jinja
{% block sidebar %}
<h3>Table of Contents</h3>
...
{{ parent() }}
{% endblock %}
template
reside
entro
de
AcmeBlogBundle
Blog
(e.g.
de
81
Assumindo que o AcmeBlogBundle reside em src/Acme/BlogBundle, o atalho final para o layout seria
src/Acme/BlogBundle/Resources/views/Blog/index.html.twig.
AcmeBlogBundle::layout.html.twig: Essa sintaxe refere ao template base que especfica para
AcmeBlogBundle. Since the middle, controller, portion is missing (e.g. Blog), the template lives at
Resources/views/layout.html.twig inside AcmeBlogBundle.
::base.html.twig: Esta sintaxe refere a uma template base para toda a aplicao ou layout. Perceba
que a string comea com dois sinais de dois pontos (::), significando que ambas as partes bundle controller
esto faltando. Isto significa que o template no localizado em qualquer pacote, mas sim na raiz do diretrio
app/Resources/views/.
Na seo Sobrepondo Templates de Pacote, voc ir descobrir como cada template reside dentro do
AcmeBlogBundle, por exemplo, pode ser sobreposto ao colocar um template de mesmo nome no diretrio
app/Resources/AcmeBlogBundle/views/. Isso d o poder de sobrepor templates de qualquer pacote pago.
Dica: Esperanosamente, a sintaxe de nomeao de template parece familiar - a mesma conveno para nomeao
usada para referir para Padro de nomeao do Controlador.
Sufixo de Template
O formato bundle:controller:template de cada template especifica onde o arquivo de template est localizado. Cada
nome de template tambm tem duas expresses que especificam o formato e engine para aquela template.
AcmeBlogBundle:Blog:index.html.twig - formato HTML, engine Twig
AcmeBlogBundle:Blog:index.html.php - formato HTML, engine PHP
AcmeBlogBundle:Blog:index.css.twig - formato CSS, engine Twig
Por padro, qualquer template Symfony2 ou pode ser escrito em Twig ou em PHP, e a ltima parte da extenso (ex:
.twig ou .php) especifica qual dessas duas engines deveria ser usada. A primeira parte da extenso, (ex: .html,
.css, etc) o formato final que o template ir gerar. Ao contrrio de engine, que determina como Symfony2 analisa
o template, isso simplesmente uma ttica organizacional em caso do mesmo recurso precisar ser transformado como
HTML(index.html.twig), XML (index.xml.twig), ou qualquer outro formato. Para mais informaess,
leia a seo Debugging.
Nota: As engines disponveis podem ser configurados e at mesmo ter novas engines adicionadas. Veja Configurao de Template para mais detalhes.
Tags e Helpers
Voc j entende as bases do templates, como eles so chamados e como usar herana de template. As partes mais
difceis esto realmente atrs de voc. Nesta seo, voc ir aprender sobre um grande grande grupo de ferramentas
disponveis para ajudar a desempenhar as tarefas de template mais comuns como incluir outras templates, vincular
pginas e incluir imagens.
Symfony2 vem acompanhado com vrias tags Twig especializadas e funes que facilitam o trabalho do designer de
template. Em PHP, o sistema de template providencia um sistema extenso de helper que providencia funcionalidades
teis no contexto de template.
Ns realmente vimos umas poucas tags Twig construdas ({% block %} e {% extends %}) como exemplo de
um helper PHP ($view[slots]). Vamos aprender um pouco mais.
82
Captulo 2. Livro
Voc ir frequntemente querer incluir a mesma template ou fragmento de cdigo em vrias pginas diferentes. Por
exemplo, em uma aplicao com artigos de notcias, a exibio do artigo no cdigo do template poderia ser usada
numa pgina de detalhes do artigo, num a pgina exibindo os artigos mais populares, ou em uma lista dos ltimos
artigos.
Quando voc precisa reutilizar um pedao de um cdigo PHP, voc tipicamente move o cdigo para uma nova classe
ou funo PHP. O mesmo verdade para template. Ao mover o cdigo do template reutilizado em um template
prprio, ele pode ser includo em qualquer outro template. Primeiro, crie o template que voc precisar reutilizar.
Twig
{# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #}
<h2>{{ article.title }}</h2>
<h3 class="byline">by {{ article.authorName }}</h3>
<p>
{{ article.body }}
</p>
PHP
<!-- src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.php -->
<h2><?php echo $article->getTitle() ?></h2>
<h3 class="byline">by <?php echo $article->getAuthorName() ?></h3>
<p>
<?php echo $article->getBody() ?>
</p>
PHP
<!-- src/Acme/ArticleBundle/Resources/Article/list.html.php -->
<?php $view->extend(AcmeArticleBundle::layout.html.php) ?>
<?php $view[slots]->start(body) ?>
<h1>Recent Articles</h1>
O template includo usando a tag {% include %}. Perceba que o nome do template segue a mesma conveno
2.1. Livro
83
tpica. O template articleDetails.html.twig usa uma varivel article. Isso passado por um template
list.html.twig usando o comando with.
Dica: A sintaxe {article: article} a sintaxe Twig padro para hash maps (ex: um array com chaves
nomeadas). Se ns precisarmos pass-lo em elementos mltiplos, ele poderia ser algo como: {foo: foo,
bar: bar}.
Incorporao de Controllers
Em alguns casos, voc precisa fazer mais do que incluir um template simples. Suponha que voc tenha uma barra
lateral no seu layout que contenha os trs artigos mais recentes. Recuperar os trs artigos podem incluir consultar a
base de dados ou desempenhar outra lgica pesada que no pode ser a partir de um template.
A soluo simplesmnte incorporar o resultado de um controller inteiro para seu template. Primeiro, crie o controller
que retorne um certo nmero de artigos recentes :
// src/Acme/ArticleBundle/Controller/ArticleController.php
class ArticleController extends Controller
{
public function recentArticlesAction($max = 3)
{
// make a database call or other logic to get the "$max" most recent articles
$articles = ...;
PHP
<!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php -->
<?php foreach ($articles as $article): ?>
<a href="/article/<?php echo $article->getSlug() ?>">
<?php echo $article->getTitle() ?>
</a>
<?php endforeach; ?>
Nota:
Perceba que ns fizemos uma gambiarra e fizemos um hardcode no artigo URL desse exemplo (ex:
/article/*slug*). Isso uma prtica ruim. Na prxima seo, voc ir aprender como fazer isso corretamente.
Para incluir um controller, voc ir precisar referir a ela usando a sintaxe de string padro para controllers (isto ,
bundle:controller:action):
Twig
84
Captulo 2. Livro
{# app/Resources/views/base.html.twig #}
...
<div id="sidebar">
{% render "AcmeArticleBundle:Article:recentArticles" with {max: 3} %}
</div>
PHP
<!-- app/Resources/views/base.html.php -->
...
<div id="sidebar">
<?php echo $view[actions]->render(AcmeArticleBundle:Article:recentArticles, array(max
</div>
Sempre quando voc pensar que voc precisa de uma varivel ou uma pea de informao que voc no tenha acesso
em um template, considere transformar o controller. Controllers so rpidos para executar e promovem uma boa
organizao e utilizao do cdigo.
Contedo Assncrono com hinclude.js
PHP
<?php echo $view[actions]->render(...:news, array(), array(standalone => js)) ?>
XML
<!-- app/config/config.xml -->
<framework:config>
<framework:templating hinclude-default-template="AcmeDemoBundle::hinclude.html.twig" />
</framework:config>
PHP
2.1. Livro
85
// app/config/config.php
$container->loadFromExtension(framework, array(
// ...
templating
=> array(
hinclude_default_template => array(AcmeDemoBundle::hinclude.html.twig),
),
));
Vinculao s Pginas
Criar links para outras pgina em sua aplicao uma das tarefas mais comuns para um template. Ao invs de fazer
um hardcode nas URLs nos templates, use a funo do Twig path (ou o helper router no PHP) para gerar URLs
baseadas na configurao de roteamento. Mais tarde, se voc quiser modificar a URL de uma pgina particular, tudo
que voc precisar fazer mudar as configuraes de roteamento; os templates iro automatricamente gerar a nova
URL.
Primeiro, vincule a pgina _welcome , que acessvel pela seguinte configurao de roteamento:
YAML
_welcome:
pattern: /
defaults: { _controller: AcmeDemoBundle:Welcome:index }
XML
<route id="_welcome" pattern="/">
<default key="_controller">AcmeDemoBundle:Welcome:index</default>
</route>
PHP
$collection = new RouteCollection();
$collection->add(_welcome, new Route(/, array(
_controller => AcmeDemoBundle:Welcome:index,
)));
return $collection;
Para vincular pgina, apenas use a funo Twig path e refira para a rota:
Twig
<a href="{{ path(_welcome) }}">Home</a>
PHP
<a href="<?php echo $view[router]->generate(_welcome) ?>">Home</a>
Como esperado, isso ir gerar a URL /. Vamos ver como isso ir funcionar com uma rota mais complicada:
YAML
article_show:
pattern: /article/{slug}
defaults: { _controller: AcmeArticleBundle:Article:show }
XML
86
Captulo 2. Livro
PHP
$collection = new RouteCollection();
$collection->add(article_show, new Route(/article/{slug}, array(
_controller => AcmeArticleBundle:Article:show,
)));
return $collection;
Neste caso, voc precisa especificar tanto o nome da rota (article_show) como um valor para o parmetro
{slug}. Usando esta rota, vamos revisitar o template recentList da sesso anterior e vincular aos artigos corretamente:
Twig
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
{% for article in articles %}
<a href="{{ path(article_show, { slug: article.slug }) }}">
{{ article.title }}
</a>
{% endfor %}
PHP
Dica: Voc tambm pode gerar uma URL absoluta ao usar a funo url do Twig:
<a href="{{ url(_welcome) }}">Home</a>
O mesmo pode ser feito em templates PHP ao passar um terceiro argumento ao mtodo generate():
<a href="<?php echo $view[router]->generate(_welcome, array(), true) ?>">Home</a>
Vinculando os Assets
Templates podem frequentemente referir a imagens, Javascript, folhas de estilo e outros recursos. Claro voc poderia
fazer um hardcode do atalho desses assets (ex: /images/logo.png), mas Symfony2 providencia uma opo mais
dinmica via funo assets do Twig:
Twig
<img src="{{ asset(images/logo.png) }}" alt="Symfony!" />
<link href="{{ asset(css/blog.css) }}" rel="stylesheet" type="text/css" />
PHP
2.1. Livro
87
O principal propsito da funo asset tornar sua aplicao mais porttil. Se sua aplicao reside na raiz do
seu host (ex: http://example.com), ento os atalhos interpretados deveriam ser /images/logo.png. Mas se sua
aplicao reside em um sub-diretrio (ex: http://example.com/my_app), cada caminho do asset deveria interpretar
com o diretrio (e.g. /my_app/images/logo.png). A funo asset toma conta disto ao determinar como sua
aplicao est sendo usada e gerando os atalhos de acordo com o correto.
Adicionalmente, se voc usar funo asset, Symfony pode automaticamente anexar uma string de consulta para asset,
em detrimento de garantir que assets estticos atualizados no sero armazenados quando distribudos. Por exemplo,
/images/logo.png poderia parecer como /images/logo.png?v2. Para mais informaes, veja a opo de
configurao ref-framework-assets-version.
Incluindo Folhas de Estilo e Javascript no Twig
Nenhum site seria completo sem incluir arquivos Javascript e folhas de estilo. Em Symfony, a incluso desses assets
elegantemente manipulada ao tirar vantagem das heranas de template do Symfony.
Dica: Esta seo ir ensinar voc a filosofia por trs disto, incluindo folha de estilo e asset Javascript em Symfony.
Symfony tambm engloba outra biblioteca, chamada Assetic, que segue essa filosofia mas tambm permite voc fazer
mais coisas muito interessantes com esses assets. Para mais informaes sobre usar Assetic veja Como usar o Assetic
para o Gerenciamento de Assets.
Comece adicionando dois blocos a seu template base que ir abrigar seus assets: uma chamada stylesheets dentro
da tag head e outra chamada javascripts justamente acima do fechamento da tag body. Esses blocos iro conter
todas as folhas de estilo e Javascripts que voc ir precisar atravs do seu site:
{# app/Resources/views/base.html.twig #}
<html>
<head>
{# ... #}
{% block stylesheets %}
<link href="{{ asset(/css/main.css) }}" type="text/css" rel="stylesheet" />
{% endblock %}
</head>
<body>
{# ... #}
{% block javascripts %}
<script src="{{ asset(/js/main.js) }}" type="text/javascript"></script>
{% endblock %}
</body>
</html>
Isso fcil o bastante ! Mas e se voc precisar incluir uma folha de estilo ou Javascript de um template filho ? Por
exemplo, suponha que voc tenha uma pgina de contatos e voc precise incluir uma folha de estilo contact.css
bem naquela pgina. Dentro do template da pgina de contatos, faa o seguinte:
{# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #}
{# extends ::base.html.twig #}
{% block stylesheets %}
{{ parent() }}
88
Captulo 2. Livro
No template filho, voc simplesmente sobrepe o bloco stylesheets e coloca sua nova tag de folha de estilo dentro
daquele bloco. Claro, desde que voc queira adicionar ao contedo do bloco pai (e realmente no ir *substitu-lo),
voc deveria usar a funo parent() do Twig function para incluir tudo do bloco stylesheets do template base.
Voc pode tambm incluir assets localizados em seus arquivos de pacotes Resources/public. Voc precisar
executar o comandophp app/console assets:install target [symlink] , que move (ou symlinks) arquivos dentro da
localizao correta. (target sempre por padro web).
<link href="{{ asset(bundles/acmedemo/css/contact.css) }}" type="text/css" rel="stylesheet" />
O resultado final uma pgina que inclui ambas as folhas de estilo main.css e contact.css.
Configurando e usando o Servio templating
O corao do sistema de template em Symfony2 o template Engine. Este objeto especial responsvel por manipular templates e retornar o contedo deles. Quando voc manipula um template em um controller, por exemplo, voc
est na verdade usando o servio do template engine. Por exemplo:
return $this->render(AcmeArticleBundle:Article:index.html.twig);
equivalente a:
$engine = $this->container->get(templating);
$content = $engine->render(AcmeArticleBundle:Article:index.html.twig);
return $response = new Response($content);
O engine de template (ou servio) pr-configurada para trabalhar automaticamente dentro de Symfony2. Ele pode,
claro, ser configurado mais adiante no arquivo de configurao da aplicao:
YAML
# app/config/config.yml
framework:
# ...
templating: { engines: [twig] }
XML
<!-- app/config/config.xml -->
<framework:templating>
<framework:engine id="twig" />
</framework:templating>
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(
// ...
templating
=> array(
engines => array(twig),
),
));
2.1. Livro
89
Quando AcmeBlogBundle:Blog:index.html.twig manipulado, Symfony2 realmente observa duas diferentes localizaes para o template:
1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig
2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
Para sobrepor o template de pacote, s copie o template index.html.twig do pacote
para
app/Resources/AcmeBlogBundle/views/Blog/index.html.twig
(o
diretrio
app/Resources/AcmeBlogBundle no existiro, ento voc precisar cri-lo). Voc est livre agora
para personalizar o template.
Esta lgica tambm se aplica a templates de pacote base. Suponha tambm que cada template em AcmeBlogBundle
herda de um template base chamado AcmeBlogBundle::layout.html.twig. Justo como antes, Symfony2 ir
observar os seguintes dois lugares para o template:
1. app/Resources/AcmeBlogBundle/views/layout.html.twig
2. src/Acme/BlogBundle/Resources/views/layout.html.twig
Uma
vez
novamente,
para
sobrepor
o
template,
app/Resources/AcmeBlogBundle/views/layout.html.twig.
nalizar esta cpia como voc quiser.
apenas
copie
ele
para
Voc agora est livre para perso-
Se voc voltar um passo atrs, ver que Symfony2 sempre comea a observar no diretrio
app/Resources/{BUNDLE_NAME}/views/ por um template. Se o template no existe aqui, ele continua checando dentro do diretrio Resources/views do prprio pacote. Isso significa que todos os templates do
pacote podem ser sobrepostos ao coloc-los no sub-diretrio correto app/Resources.
Sobrepondo Templates Centrais
Como o framework Symfony um pacote por si s, templates centrais podem ser sobrepostos da mesma forma.
Por exemplo, o ncleo TwigBundle contm um nmeto de diferentes templates exception e error que podem
ser sobrepostas ao copiar cada uma do diretrio Resources/views/Exception do TwigBundle para, voc
adivinhou, o diretrio app/Resources/TwigBundle/views/Exception .
90
Captulo 2. Livro
Criar templates individuais para cada pgina e fazer cada um estender a template de sesso apropriada.
Por exemplo, a pgina index deveria ser chamada de algo prximo a
AcmeBlogBundle:Blog:index.html.twig e listar os blogs de posts reais.
{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
{% extends AcmeBlogBundle::layout.html.twig %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
Perceba que este template estende a template de sesso - (AcmeBlogBundle::layout.html.twig) que por
sua vez estende o layout de aplicao base (::base.html.twig). Isso o modelo comum de herana de trs
nveis.
Quando construir sua aplicao, voc pode escolher seguir esse mtodo ou simplesmente tornar cada template de
pgina estender a template de aplicao base diretamente (ex: {% extends ::base.html.twig %}). O
modelo de trs templates o mtodo de melhor prtica usado por vendor bundles ento aquele template base para um
pacote pode ser facilmente sobreposto para propriamente estender seu layout base de aplicao.
Sada para escape
Quando gerar HTML de um template, sempre h um risco que uma varivel de template pode gerar HTML involutrio
ou codigo do lado cliente perigoso. O resultado que o contedo dinmico poderia quebrar o HTML de uma pgina
de resultados ou permitir um usurio maldoso realizar um ataque Cross Site Scripting (XSS). Considere esse exemplo
clssico:
Twig
Hello {{ name }}
PHP
2.1. Livro
91
Sem qualquer outra sada de escape, o resultado da template ir causar uma caixa de alerta em JavaScript para saltar
na tela:
Hello <script>alert(hello!)</script>
E enquanto isso parece inofensivo, se um usurio pode chegar to longe, o mesmo usurio deveria tambm ser capaz
de escrever Javascript que realiza aes maliciosas dentro de uma rea segura de um usurio legtimo e desconhecido.
A resposta para o problema sada para escape. Sem a sada para escape ativa, o mesmo template ir manipular
inofensivamente, e literalmente imprimir a tag script na tela:
Hello <script>alert('helloe')</script>
Os sistemas de templating Twig e PHP aproximam-se do problema de formas diferentes. Se voc est usando Twig,
sada para escape ativado por padro e voc est protegido. Em PHP, sada para escape no automtico, significando
que voc precisar manualmente fazer o escape quando necessrio.
Sada para escape em Twig
Se voc est usando templates Twig, ento sada para escape ativado por padro. Isto significa que voc est protegido
externamente de consequencias acidentais por cdigo submetido por usurio. Por padro, a sada para escape assume
que o contedo est sendo escapado pela sada HTML.
Em alguns casos, voc precisar desabilitar sada para escape quando voc est manipulando uma varivel que
confivel e contm marcao que no poderia ter escape. Suponha que usurios administrativos so capazes de escrever
artigos que contenham cdigo HTML. Por padro, Twig ir escapar o corpo do artigo. Para faz-lo normalamente,
adicione o filtro raw: {{ article.body | raw }}.
Voc pode tambm desabilitar sada para escape dentro de uma rea {% block %} ou para um template inteiro.
Para mais informaes, veja Output Escaping na documentao do Twig.
Sada para escape em PHP
Sada para escape no automtica quando usamos templates PHP. Isso significa que a menos que voc escolha escapar
uma varivel explicitamente, voc no est protegido. Para usar sada para escape use o mtodo de view escape():
Hello <?php echo $view->escape($name) ?>
Por padro, o mtodo escape() assume que a varivel est sendo manipulada dentro de um contexto HTML (e assim
a varivel escapa e est segura para o HTML). O segundo argumento deixa voc mudar o contexto. Por exemplo, para
gerar algo em uma string Javascript, use o contexto js :
var myMsg = Hello <?php echo $view->escape($name, js) ?>;
Debugging
Novo na verso 2.0.9: Esta funcionalidade est disponvel no Twig 1.5.x, e foi adicionada primeiramente no Symfony 2.0.9.
92
Captulo 2. Livro
Ao utilizar o PHP, voc pode usar o var_dump() se precisa encontrar rapidamente o valor de uma varivel passada.
Isso til, por exemplo, dentro de seu controlador. O mesmo pode ser conseguido ao usar o Twig com a extenso de
depurao. Esta extenso precisa ser ativada na configurao:
YAML
# app/config/config.yml
services:
acme_hello.twig.extension.debug:
class:
Twig_Extension_Debug
tags:
- { name: twig.extension }
XML
<!-- app/config/config.xml -->
<services>
<service id="acme_hello.twig.extension.debug" class="Twig_Extension_Debug">
<tag name="twig.extension" />
</service>
</services>
PHP
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$definition = new Definition(Twig_Extension_Debug);
$definition->addTag(twig.extension);
$container->setDefinition(acme_hello.twig.extension.debug, $definition);
O dump dos parmetros do template pode ser feito usando a funo dump:
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
{{ dump(articles) }}
{% for article in articles %}
<a href="/article/{{ article.slug }}">
{{ article.title }}
</a>
{% endfor %}
O dump das variveis somente ser realizado se a definio debug do Twig (no config.yml) for true. Por
padro, isto signifca que ser feito o dump das variveis no ambiente dev mas no no prod.
Verificao de Sintaxe
Novo na verso 2.1: O comando twig:lint foi adicionado no Symfony 2.1
Voc pode verificar erros de sintaxe nos templates do Twig usando o comando de console twig:lint:
# You can check by filename:
$ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig
# or by directory:
$ php app/console twig:lint src/Acme/ArticleBundle/Resources/views
# or using the bundle name:
$ php app/console twig:lint @AcmeArticleBundle
2.1. Livro
93
Formatos de Template
Templates so uma forma genrica de modificar contedo em qualquer formato. E enquanto em muitos casos voc ir
usar templates para modificar contedo HTML, um template pode to fcil como certo gerar JavaScript, CSS, XML
ou qualquer outro formato que voc possa sonhar.
Por exemplo, o mesmo recurso sempre modificado em diversos formatos diferentes. Para modificar uma pgina
inicial de um artigo XML, simplesmente inclua o formato no nome do template:
nome do template XML: AcmeArticleBundle:Article:index.xml.twig
nome do arquivo do template XML: index.xml.twig
Na realidade, isso nada mais que uma conveno de nomeao e o template no realmente modificado de forma
diferente ao baseado no formato dele.
Em muitos casos, voc pode querer permitir um controller unitrio para modificar mltiplos formatos diferentes baseado no formato de requisio. Por aquela razo, um padro comum fazer o seguinte:
public function indexAction()
{
$format = $this->getRequest()->getRequestFormat();
return $this->render(AcmeBlogBundle:Blog:index..$format..twig);
}
O getRequestFormat no objeto Request padroniza para html, mas pode retornar qualquer outro formato baseado no formato solicitado pelo usurio. O formato solicitado frequentemente mais gerenciado pelo roteamento, onde
uma rota pode ser configurada para que /contact configure o formato requisitado html enquanto /contact.xml
configure o formato para xml. Para mais informaes, veja Advanced Example in the Routing chapter.
Para criar links que incluam o parmetro de formato, inclua uma chave _format no detalhe do parmetro:
Twig
<a href="{{ path(article_show, {id: 123, _format: pdf}) }}">
PDF Version
</a>
PHP
Consideraes finais
O engine de template em Symfony uma ferramenta poderosa que pode ser usada cada momento que voc precisa para
gerar contedo de apresentao em HTML, XML ou qualquer outro formato. E apesar de tempaltes serem um jeito
comum de gerar contedo em um controller, o uso deles no so obrigatrios. O objeto Response object retornado
por um controller pode ser criado com ou sem o uso de um template:
// creates a Response object whose content is the rendered template
$response = $this->render(AcmeArticleBundle:Article:index.html.twig);
// creates a Response object whose content is simple text
$response = new Response(response content);
94
Captulo 2. Livro
Engine de template do Symfony muito flexvel e dois editores de template diferentes esto disponveis por padro:
os tradicionais templates do PHP e os polidos e poderosos templates do Twig . Ambos suportam uma hierarquia de
template e vm empacotados com um conjunto rico de funes helper capazes de realizar as tarefas mais comuns.
No geral, o tpico de template poderia ser pensado como uma ferramenta poderosa que est sua disposio. Em
alguns casos, voc pode no precisar modificar um template, e em Symfony2, isso absolutamente legal.
Aprenda mais do Cookbook
/cookbook/templating/PHP
Como personalizar as pginas de erro
Antes de comear realmente, voc precisa configurar a informao de conexo do seu banco. Por conveno, essa
informao geralmente configurada no arquivo app/config/parameters.yml:
# app/config/parameters.yml
parameters:
database_driver:
pdo_mysql
database_host:
localhost
database_name:
test_project
database_user:
root
database_password: password
2.1. Livro
95
Nota: Definir a configurao pelo parameters.yml apenas uma conveno. Os parmetros definidos naquele
arquivo so referenciados pelo arquivo de configurao principal na configurao do Doctrine:
doctrine:
dbal:
driver:
host:
dbname:
user:
password:
%database_driver%
%database_host%
%database_name%
%database_user%
%database_password%
Colocando a informao do banco de dados em um arquivo separado, voc pode manter de forma fcil diferentes
verses em cada um dos servidores. Voc pode tambm guardar facilmente a configurao de banco (ou qualquer
outra informao delicada) fora do seu projeto, por exemplo dentro do seu diretrio de configurao do Apache. Para
mais informaes, de uma olhada em Como definir Parmetros Externos no Container de Servios.
Agora que o Doctrine sabe sobre seu banco, pode deixar que ele faa a criao dele para voc:
php app/console doctrine:database:create
Suponha que voc esteja criando uma aplicao onde os produtos precisam ser mostrados. Antes mesmo de pensar
sobre o Doctrine ou banco de dados, voc j sabe que ir precisar de um objeto Product para representar esses
produtos. Crie essa classe dentro do diretrio Entity no seu bundle AcmeStoreBundle:
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
class Product
{
protected $name;
protected $price;
protected $description;
}
A classe - frequentemente chamada de entidade, que significa uma classe bsica para guardar dados - simples
e ajuda a cumprir o requisito de negcio referente aos produtos na sua aplicao. Essa classe ainda no pode ser
persistida no banco de dados - ela apenas uma classe PHP simples.
Dica: Depois que voc aprender os conceitos por trs do Doctrine, voc pode deix-lo criar essa classe entidade para
voc:
O Doctrine permite que voc trabalhe de uma forma muito mais interessante com banco de dados do que apenas buscar
registros de uma tabela baseada em colunas para um array. Em vez disso, o Doctrine permite que voc persista objetos
inteiros no banco e recupere objetos inteiros do banco de dados. Isso funciona mapeando uma classe PHP com uma
tabela do banco, e as propriedades dessa classe com as colunas da tabela:
96
Captulo 2. Livro
Para o Doctrine ser capaz disso, voc tem apenas que criar metadados, em outras palavras a configurao que diz ao
Doctrine exatamente como a classe Product e suas propriedades devem ser mapeadas com o banco de dados. Esses
metadados podem ser especificados em vrios diferentes formatos incluindo YAML, XML ou diretamente dentro da
classe Product por meio de annotations:
Nota: Um bundle s pode aceitar um formato para definio de metadados. Por exemplo, no possvel misturar
definies em YAML com definies por annotations nas classes entidade.
Annotations
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="product")
*/
class Product
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=100)
*/
protected $name;
/**
* @ORM\Column(type="decimal", scale=2)
*/
protected $price;
/**
* @ORM\Column(type="text")
*/
protected $description;
}
2.1. Livro
97
YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
type: entity
table: product
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
name:
type: string
length: 100
price:
type: decimal
scale: 2
description:
type: text
XML
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Acme\StoreBundle\Entity\Product" table="product">
<id name="id" type="integer" column="id">
<generator strategy="AUTO" />
</id>
<field name="name" column="name" type="string" length="100" />
<field name="price" column="price" type="decimal" scale="2" />
<field name="description" column="description" type="text" />
</entity>
</doctrine-mapping>
Dica: O nome da tabela opcional e, se omitido, ser determinado automaticamente baseado no nome da classe
entidade.
O Doctrine permite que voc escolha entre uma grande variedade de diferentes tipos de campo, cada um com suas
opes especficas. Para informaes sobre os tipos de campos disponveis, d uma olhada na seo Referncia dos
Tipos de Campos do Doctrine.
Veja tambm:
Voc tambm pode conferir a Documentao Bsica sobre Mapeamento do Doctrine para todos os detalhes sobre o
tema. Se voc usar annotations, ir precisar prefixar todas elas com ORM\ (i.e. ORM\Column(..)), o que no
citado na documentao do Doctrine. Voc tambm ir precisar incluir o comando use Doctrine\ORM\Mapping
as ORM;, que importa o prefixo ORM das annotations.
98
Captulo 2. Livro
Cuidado: Tenha cuidado para que o nome da sua classe e suas propriedades no esto mapeadas com o nome
de um comando SQL protegido (como groupou user). Por exemplo, se o nome da sua classe entidade
Group ento, por padro, o nome da sua tabela ser group, que causar um erro de SQL em alguns dos bancos
de dados. D uma olhada na Documentao sobre os nomes de comandos SQL reservados para ver como escapar
adequadamente esses nomes. Alternativamente, se voc pode escolher livremente seu esquema de banco de dados,
simplesmente mapeie para um nome de tabela ou nome de coluna diferente. Veja a documentao do Doctrine
sobre Classes persistentes e Mapeamento de propriedades
Nota: Quando usar outra biblioteca ou programa (i.e. Doxygen) que usa annotations voc dever colocar a annotation
@IgnoreAnnotation na classe para indicar que annotations o Symfony deve ignorar.
Por exemplo, para prevenir que a annotation @fn gere uma exceo, inclua o seguinte:
/**
@IgnoreAnnotation(fn)
*/
class Product
Apesar do Doctrine agora saber como persistir um objeto Product num banco de dados, a classe ainda no realmente til. Como Product apenas uma classe PHP usual, voc precisa criar os mtodos getters e setters (i.e.
getName(), setName() para acessar sua suas propriedades (at as propriedades protected). Felizmente o
Doctrine pode fazer isso por voc executando:
php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product
Esse comando garante que todos os getters e setters esto criados na classe Product. Ele um comando seguro voc pode execut-lo muitas e muitas vezes: ele apenas gera getters e setters que ainda no existem (i.e. ele no altera
os models j existentes).
Cuidado: O comando doctrine:generate:entities gera um backup do Product.php original chamado de Product.php~. Em alguns casos, a presena desse arquivo pode causar um erro Cannot redeclare
class. seguro remov-lo.
Voc pode gerar todas as entidades que so conhecidas por um bundle (i.e. cada classe PHP com a informao de
mapeamento do Doctrine) ou de um namespace inteiro.
php app/console doctrine:generate:entities AcmeStoreBundle
php app/console doctrine:generate:entities Acme
Agora voc tem uma classe utilizvel Product com informao de mapeamento assim o Doctrine sabe exatamente
como fazer a persistncia dela. claro, voc ainda no tem a tabela correspondente product no seu banco de dados.
2.1. Livro
99
Felizmente, o Doctrine pode criar automaticamente todas as tabelas necessrias no banco para cada uma das entidades
conhecidas da sua aplicao. Para isso, execute:
php app/console doctrine:schema:update --force
Dica: Na verdade, esse comando extremamente poderoso. Ele compara o que o banco de dados deveria se
parecer (baseado na informao de mapeamento das suas entidades) com o que ele realmente se parece, e gera os
comandos SQL necessrios para atualizar o banco para o que ele deveria ser. Em outras palavras, se voc adicionar
uma nova propriedade com metadados de mapeamento na classe Producte executar esse comando
novamente, ele ir criar a instruo alter table para adicionar as novas
colunas na tabela product existente.
Uma maneira ainda melhor de se aproveitar dessa funcionalidade por meio das migrations, que lhe permitem
criar essas instrues SQL e guard-las em classes migration que podem ser rodadas de forma sistemtica no seu
servidor de produo para que se possa acompanhar e migrar o schema do seu banco de dados de uma forma mais
segura e confivel.
Seu banco de dados agora tem uma tabela product totalmente funcional com as colunas correspondendo com os
metadados que foram especificados.
Persistindo Objetos no Banco de Dados
Agora que voc tem uma entidade Product mapeada e a tabela correspondente product, j est pronto para
persistir os dados no banco. De dentro de um controller, isso bem simples. Inclua o seguinte mtodo no
DefaultController do bundle:
1
2
3
4
// src/Acme/StoreBundle/Controller/DefaultController.php
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
// ...
5
6
7
8
9
10
11
12
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
13
14
15
16
17
18
Nota: Se voc estiver seguindo o exemplo na prtica, precisar criar a rota que aponta para essa action se quiser v-la
funcionando.
Vamos caminhar pelo exemplo:
linhas 8-11 Nessa parte voc instancia o objeto $product como qualquer outro objeto PHP normal;
linha 13 Essa linha recuperar o objeto entity manager do Doctrine, que o responsvel por lidar com o processo
de persistir e retornar objetos do e para o banco de dados;
linha 14 O mtodo persist() diz ao Doctrine para gerenciar o objeto $product. Isso no gera (ainda)
um comando real no banco de dados.
100
Captulo 2. Livro
linha 15 Quando o mtodo flush() chamado, o Doctrine verifica em todos os objetos que ele gerencia para
ver se eles necessitam ser persistidos no banco. Nesse exemplo, o objeto $product ainda no foi persistido,
por isso o entity manager executa um comando INSERT e um registro criado na tabela product.
Nota: Na verdade, como o Doctrine conhece todas as entidades gerenciadas, quando voc chama o mtodo flush(),
ele calcula um changeset geral e executa o comando ou os comandos mais eficientes possveis. Por exemplo, se voc
vai persistir um total de 100 objetos Product e em seguida chamar o mtodo flush(), o Doctrine ir criar um
nico prepared statment e reutiliz-lo para cada uma das inseres. Esse padro chamado de Unit of Work, e
utilizado porque rpido e eficiente.
Na hora de criar ou atualizar objetos, o fluxo de trabalho quase o mesmo. Na prxima seo, voc ver como o
Doctrine inteligente o suficiente para rodar uma instruo UPDATE de forma automtica se o registro j existir no
banco.
Dica: O Doctrine fornece uma biblioteca que permite a voc carregar programaticamente dados de teste no seu
projeto (i.e. fixture data). Para mais informaes, veja /bundles/DoctrineFixturesBundle/index.
Trazer um objeto a partir do banco ainda mais fcil. Por exemplo, suponha que voc tenha configurado uma rota
para mostrar um Product especfico baseado no seu valor id:
public function showAction($id)
{
$product = $this->getDoctrine()
->getRepository(AcmeStoreBundle:Product)
->find($id);
if (!$product) {
throw $this->createNotFoundException(No product found for id .$id);
}
// faz algo, como passar o objeto $product para um template
}
Quando voc busca um tipo de objeto em particular, voc sempre usa o que chamamos de repositrio. Voc pode
pensar num repositrio como uma classe PHP cuja nica funo auxiliar a trazer entidades de uma determinada
classe. Voc pode acessar o objeto repositrio por uma classe entidade dessa forma:
$repository = $this->getDoctrine()
->getRepository(AcmeStoreBundle:Product);
Nota: A string AcmeStoreBundle:Product um atalho que voc pode usar em qualquer lugar no Doctrine em
vez do nome completo da classe entidade (i.e Acme\StoreBundle\Entity\Product). Desde que sua entidade
esteja sob o namespace Entity do seu bundle, isso vai funcionar.
Uma vez que voc tiver seu repositrio, ter acesso a todos os tipos de mtodos teis:
// Busca pela chave primria (geralmente "id")
$product = $repository->find($id);
// nomes de mtodos dinmicos para busca baseados no valor de uma coluna
$product = $repository->findOneById($id);
$product = $repository->findOneByName(foo);
2.1. Livro
101
Nota: Naturalmente, voc pode tambm pode rodar consultas complexas, vamos aprender mais sobre isso na seo
Consultando Objetos.
Voc tambm pode se aproveitar dos mtodos bem teis findBy e findOneBy para retornar facilmente objetos
baseando-se em mltiplas condies:
// busca por um produto que corresponda a um nome e um preo
$product = $repository->findOneBy(array(name => foo, price => 19.99));
// busca por todos os produtos correspondentes a um nome, ordenados por
// preo
$product = $repository->findBy(
array(name => foo),
array(price => ASC)
);
Dica: Quando voc renderiza uma pgina, voc pode ver quantas buscas foram feitas no canto inferior direito da web
debug toolbar.
Se voc clicar no cone, ir abrir o profiler, mostrando a voc as consultas exatas que foram feitas.
Atualizando um Objeto
Depois que voc trouxe um objeto do Doctrine, a atualizao fcil. Suponha que voc tenha uma rota que mapeia o
id de um produto para uma action de atualizao em um controller:
public function updateAction($id)
{
$em = $this->getDoctrine()->getManager();
$product = $em->getRepository(AcmeStoreBundle:Product)->find($id);
if (!$product) {
throw $this->createNotFoundException(No product found for id .$id);
}
$product->setName(New product name!);
102
Captulo 2. Livro
$em->flush();
return $this->redirect($this->generateUrl(homepage));
}
Apagar um objeto muito semelhante, mas requer um chamada ao mtodo remove() do entity manager:
$em->remove($product);
$em->flush();
Como voc podia esperar, o mtodo remove() notifica o Doctrine que voc quer remover uma determinada entidade
do banco. A consulta real DELETE, no entanto, no executada de verdade at que o mtodo flush() seja chamado.
Consultando Objetos
Voc j viu como o repositrio objeto permite que voc execute consultas bsicas sem nenhum esforo:
$repository->find($id);
$repository->findOneByName(Foo);
claro, o Doctrine tambm permite que se escreva consulta mais complexas usando o Doctrine Query Language
(DQL). O DQL similar ao SQL exceto que voc deve imaginar que voc est consultando um ou mais objetos de
uma classe entidade (i.e. Product) em vez de consultar linhas em uma tabela (i.e. product).
Quando estiver consultando no Doctrine, voc tem duas opes: escrever consultas Doctrine puras ou usar o Doctrines
Query Builder.
Consultando Objetos com DQL
Imagine que voc queira buscar por produtos, mas retornar apenas produtos que custem menos que 19,99, ordenados
do mais barato para o mais caro. De um controller, faa o seguinte:
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC
)->setParameter(price, 19.99);
$products = $query->getResult();
Se voc se sentir confortvel com o SQL, ento o DQL deve ser bem natural. A grande diferena que voc precisa
pensar em termos de objetos em vez de linhas no banco de dados. Por esse motivo, voc faz um select from
AcmeStoreBundle:Product e d para ele o alias p.
2.1. Livro
103
O mtodo getResult() retorna um array de resultados. Se voc estiver buscando por apenas um objeto, voc pode
usar em vez disso o mtodo getSingleResult():
$product = $query->getSingleResult();
A sintaxe DQL incrivelmente poderosa, permitindo que voc faa junes entre entidades facilmente (o tpico de
relacionamentos ser coberto posteriormente), grupos etc. Para mais informaes, veja a documentao oficial do
Doctrine Query Language.
Configurando parmetros
Tome nota do mtodo setParameter(). Quando trabalhar com o Doctrine, sempre uma boa ideia configurar os valores externos como placeholders, o que foi feito na consulta acima:
... WHERE p.price > :price ...
Usar parmetros em vez de colocar os valores diretamente no texto da consulta feito para prevenir ataques
de SQL injection e deve ser feito sempre. Se voc estiver usando mltiplos parmetros, voc pode definir seus
valores de uma vez s usando o mtodo setParameters():
->setParameters(array(
price => 19.99,
name => Foo,
))
Em vez de escrever diretamente suas consultas, voc pode alternativamente usar o QueryBuilder do Doctrine para
fazer o mesmo servio usando uma bela interface orientada a objetos. Se voc utilizar uma IDE, pode tambm se
beneficiar do auto-complete medida que voc digita o nome dos mtodos. A partir de um controller:
$repository = $this->getDoctrine()
->getRepository(AcmeStoreBundle:Product);
$query = $repository->createQueryBuilder(p)
->where(p.price > :price)
104
Captulo 2. Livro
->setParameter(price, 19.99)
->orderBy(p.price, ASC)
->getQuery();
$products = $query->getResult();
O objeto QueryBuilder contm todos os mtodos necessrios para criar sua consulta. Ao chamar o mtodo
getQuery(), o query builder retorna um objeto Query normal, que o mesmo objeto que
voc criou diretamente na seo anterior.
Para mais informaes, consulte a documentao do Query Builder do Doctrine.
Classes Repositrio Personalizadas
Nas sees anteriores, voc comeou a construir e usar consultas mais complexas de dentro de um controller. De modo
a isolar, testar e reutilizar essas consultas, uma boa ideia criar uma classe repositrio personalizada para sua entidade
e adicionar mtodos com sua lgica de consultas l dentro.
Para fazer isso, adicione o nome da classe repositrio na sua definio de mapeamento.
Annotations
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="Acme\StoreBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
type: entity
repositoryClass: Acme\StoreBundle\Repository\ProductRepository
# ...
XML
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->
<!-- ... -->
<doctrine-mapping>
<entity name="Acme\StoreBundle\Entity\Product"
repository-class="Acme\StoreBundle\Repository\ProductRepository">
<!-- ... -->
</entity>
</doctrine-mapping>
O Doctrine pode gerar para voc a classe repositrio usando o mesmo comando utilizado anteriormente para criar os
mtodos getters e setters que estavam faltando:
2.1. Livro
105
Em seguida, adicione um novo mtodo - findAllOrderedByName() - para sua recm-gerada classe repositrio.
Esse mtodo ir buscar por todas as entidades Product, ordenadas alfabeticamente.
// src/Acme/StoreBundle/Repository/ProductRepository.php
namespace Acme\StoreBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC)
->getResult();
}
}
Dica: O entity manager pode ser acessado via $this->getEntityManager() de dentro do repositrio.
Voc pode usar esse novo mtodo da mesma forma que os mtodos padres find do repositrio:
$em = $this->getDoctrine()->getManager();
$products = $em->getRepository(AcmeStoreBundle:Product)
->findAllOrderedByName();
Nota: Quando estiver usando uma classe repositrio personalizada, voc continua tendo acesso aos mtodos padres
finder com find() e findAll().
Relacionamentos/Associaes de Entidades
Suponha que todos os produtos na sua aplicao pertenam exatamente a uma categoria. Nesse caso, voc precisa
de um objeto Category e de uma forma de relacionar um objeto Produto com um objeto Category. Comece
criando uma entidade Category. Como voc sabe que ir eventualmente precisar de fazer a persistncia da classe
atravs do Doctrine, voc pode deix-lo criar a classe por voc.
Esse comando gera a entidade Category para voc, com um campo id, um campo name e as funes getters e
setters relacionadas.
Metadado para Mapeamento de Relacionamentos
Para relacionar as entidades Category e Product, comece criando a propriedade products na classe
Category:
// src/Acme/StoreBundle/Entity/Category.php
// ...
use Doctrine\Common\Collections\ArrayCollection;
class Category
{
106
Captulo 2. Livro
// ...
/**
* @ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
}
Primeiro, como o objeto Category ir se relacionar a vrios objetos Product, uma propriedade array
products adicionada para guardar esses objetos Product. Novamente, isso no feito porque o Doctrine
precisa dele, mas na verdade porque faz sentido dentro da aplicao guardar um array de objetos Product.
Nota:
O cdigo no mtodo __construct() importante porque o Doctrine requer que a propriedade
$productsseja um objeto ArrayCollection. Esse objeto se parece e age quase exatamente como
um array, mas tem mais um pouco de flexibilidade embutida. Se isso te deixa desconfortvel, no se preocupe. Apenas
imagine que ele um array e voc estar em boas mos.
Em seguida, como cada classe Product pode se relacionar exatamente com um objeto Category, voc ir querer
adicionar uma propriedade $category na classe Product:
// src/Acme/StoreBundle/Entity/Product.php
// ...
class Product
{
// ...
/**
* @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* @ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
}
Finalmente, agora que voc adicionou um nova propriedade tanto na classe Category quanto na Product, diga ao
Doctrine para gerar os mtodos getters e setters que esto faltando para voc:
php app/console doctrine:generate:entities Acme
Ignore o metadado do Doctrine por um instante. Agora voc tem duas classes - Category e Product com um relacionamento natural um-para-muitos. A classe categoria contm um array de objetos Product e o objeto Product
pode conter um objeto Category. Em outras palavras - voc construiu suas classes de um jeito que faz sentido para
as suas necessidades. O fato de que os dados precisam ser persistidos no banco sempre secundrio.
Agora, olhe o metadado acima da propriedade $category na classe Product. A informao aqui diz para o
Doctrine que a classe relacionada a Category e que ela deve guardar o id do registro categoria em um campo
category_id que fica na tabela product. Em outras palavras, o objeto Category ser guardado na propriedade
$category, mas nos bastidores, o Doctrine ir persistir esse relacionamento guardando o valor do id da categoria
na coluna category_id da tabela product.
2.1. Livro
107
O metadado acima da propriedade $products do objeto Category menos importante, e simplesmente diz ao
Doctrine para olhar a propriedade Product.category para descobrir como o relacionamento mapeado.
Antes de continuar, tenha certeza de dizer ao Doctrine para adicionar uma nova tabela category, alm de uma
coluna product.category_id e uma nova chave estrangeira:
php app/console doctrine:schema:update --force
Nota: Esse comando deve ser usado apenas durante o desenvolvimento. Para um mtodo mais robusto de atualizao
sistemtica em um banco de dados de produo, leia sobre as Doctrine migrations.
Agora o momento de ver o cdigo em ao. Imagine que voc est dentro de um controller:
108
Captulo 2. Livro
// ...
use Acme\StoreBundle\Entity\Category;
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
// ...
class DefaultController extends Controller
{
public function createProductAction()
{
$category = new Category();
$category->setName(Main Products);
$product = new Product();
$product->setName(Foo);
$product->setPrice(19.99);
// relaciona a categoria com esse produto
$product->setCategory($category);
$em = $this->getDoctrine()->getManager();
$em->persist($category);
$em->persist($product);
$em->flush();
return new Response(
Created product id: .$product->getId(). and category id: .$category->getId()
);
}
}
Quando voc precisa pegar objetos associados, seu fluxo de trabalho parecido com o que foi feito anteriormente.
Primeiro, consulte um objeto $product e ento acesse seu o objeto Category relacionado:
public function showAction($id)
{
$product = $this->getDoctrine()
->getRepository(AcmeStoreBundle:Product)
->find($id);
$categoryName = $product->getCategory()->getName();
// ...
}
Nesse exemplo, voc primeiro busca por um objeto Product baseado no id do produto. Isso gera uma consulta
apenas para os dados do produto e faz um hydrate do objeto $product com esses dados. Em seguida, quando voc
chamar $product->getCategory()->getName(), o Doctrine silenciosamente faz uma segunda consulta para
buscar a Category que est relacionada com esse Product. Ele prepara o objeto $category e o retorna para
voc.
2.1. Livro
109
O que importante o fato de que voc tem acesso fcil as categorias relacionadas com os produtos, mas os dados da
categoria no so realmente retornados at que voc pea pela categoria (i.e. sofre lazy load).
Voc tambm pode buscar na outra direo:
public function showProductAction($id)
{
$category = $this->getDoctrine()
->getRepository(AcmeStoreBundle:Category)
->find($id);
$products = $category->getProducts();
// ...
}
Nesse caso, ocorre a mesma coisa: primeiro voc busca por um nico objeto Category, e ento o Doctrine faz
uma segunda busca para retornar os objetos Product relacionados, mas apenas se voc pedir por eles (i.e. quando
voc chama ->getProducts()). A varivel $products uma array de todos os objetos Product que esto
relacionados com um dado objeto Category por meio do valor de seu campo category_id.
110
Captulo 2. Livro
Esse objeto proxy estende o verdadeiro objeto Category, e se parece e age exatamente como ele.
A diferena que, por usar um objeto proxy, o Doctrine pode retardar a busca pelos dados reais da Categoryat que voc realmente precise daqueles dados (e.g. at que
voc chame $category->getName()).
As classes proxy so criadas pelo Doctrine e armazenadas no diretrio cache. E apesar de que voc provavelmente nunca ir notar que o seu objeto $category na verdade um objeto proxy, importante manter isso em
mente.
Na prxima seo, quando voc retorna os dados do produto e categoria todos de uma vez (via um join), o
Doctrine ir retornar o verdadeiro objeto Category, uma vez que nada precisa ser carregado de modo lazy
load.
Nos exemplos acima, duas consultas foram feitas - uma para o objeto original (e.g uma Category) e uma para os
objetos relacionados (e.g. os objetos Product).
Dica: Lembre que voc pode visualizar todas as consultas feitas durante uma requisio pela web debug toolbar.
claro, se voc souber antecipadamente que vai precisar acessar ambos os objetos, voc pode evitar a segunda consulta
atravs da emisso de um join na consulta original. Inclua o mtodo seguinte na classe ProductRepository:
// src/Acme/StoreBundle/Repository/ProductRepository.php
public function findOneByIdJoinedToCategory($id)
{
$query = $this->getEntityManager()
->createQuery(
SELECT p, c FROM AcmeStoreBundle:Product p
JOIN p.category c
WHERE p.id = :id
)->setParameter(id, $id);
try {
return $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
Agora, voc pode usar esse mtodo no seu controller para buscar um objeto Product e sua Category relacionada
com apenas um consulta:
2.1. Livro
111
Essa seo foi uma introduo para um tipo comum de relacionamento de entidades, o um-para-muitos. Para
detalhes mais avanados e exemplos de como usar outros tipos de relacionamentos (i.e. um-para-um,
muitos-para-muitos), verifique a Documentao sobre Mapeamento e Associaes do Doctrine.
Nota: Se voc estiver usando annotations, ir precisar prefixar todas elas com ORM\ (e.g ORM\OneToMany),
o que no est descrito na documentao do Doctrine. Voc tambm precisar incluir a instruo use
Doctrine\ORM\Mapping as ORM;, que faz a importao do prefixo ORM das annotations.
Configurao
O Doctrine altamente configurvel, embora voc provavelmente no vai precisar se preocupar com a maioria de suas
opes. Para saber mais sobre a configurao do Doctrine, veja a seo Doctrine do reference manual.
Lifecycle Callbacks
s vezes, voc precisa executar uma ao justamente antes ou depois de uma entidade ser inserida, atualizada ou
apagada. Esses tipos de aes so conhecidas como lifecycle callbacks, pois elas so mtodos callbacks que voc
precisa executar durante diferentes estgios do ciclo de vida de uma entidade (i.e. a entidade foi inserida, atualizada,
apagada, etc.).
Se voc estiver usando annotations para seus metadados, comece habilitando esses callbacks. Isso no necessrio se
estiver utilizando YAML ou XML para seus mapeamentos:
/**
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
*/
class Product
{
// ...
}
Agora, voc pode dizer ao Doctrine para executar um mtodo em cada um dos eventos de ciclo de vida disponveis.
Por exemplo, suponha que voc queira definir uma coluna created do tipo data para a data atual, apenas quando for
a primeira persistncia da entidade (i.e. insero):
Annotations
/**
* @ORM\prePersist
*/
112
Captulo 2. Livro
YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
type: entity
# ...
lifecycleCallbacks:
prePersist: [ setCreatedValue ]
XML
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->
<!-- ... -->
<doctrine-mapping>
<entity name="Acme\StoreBundle\Entity\Product">
<!-- ... -->
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="setCreatedValue" />
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
Nota: O exemplo acima presume que voc tenha criado e mapeado uma propriedade created (que no foi mostrada
aqui).
Agora, logo no momento anterior a entidade ser persistida pela primeira vez, o Doctrine ir automaticamente chamar
esse mtodo e o campo created ser preenchido com a data atual.
Isso pode ser repetido para qualquer um dos outros eventos de ciclo de vida, que incluem:
preRemove
postRemove
prePersist
postPersist
preUpdate
postUpdate
postLoad
loadClassMetadata
Para mais informaes sobre o que esses eventos significam e sobre os lifecycle callbacks em geral, veja a Documentao sobre Lifecycle Events do Doctrine.
2.1. Livro
113
114
Captulo 2. Livro
Opes de Campo
Cada campo pode ter um conjunto de opes aplicado sobre ele. As opes disponveis incluem type (o padro
string), name, lenght, unique e nullable. Olhe alguns exemplos de annotations:
/**
* Um campo string com tamanho 255 que no pode ser nulo
* (segue os valores padres para "type", "length" e *nullable* options)
*
* @ORM\Column()
*/
protected $name;
/**
* Um campo string com tamanho 150 persistido na coluna "email_adress"
* e com um ndice nico
*
* @ORM\Column(name="email_address", unique="true", length="150")
*/
protected $email;
Nota: Existem mais algumas opes que no esto listadas aqui. Para mais detalhes, veja a Documentao sobre
Mapeamento de Propriedades do Doctrine.
Comandos de Console
A integrao com o Doctrine2 ORM fornece vrios comandos de console no namespace doctrine. Para ver a lista
de comandos, voc pode executar o console sem nenhum argumento:
php app/console
A lista dos comandos disponveis ser mostrada, muitos dos quais comeam com o prefixo doctrine. Voc pode encontrar mais informaes sobre qualquer um desses comandos (e qualquer comando do Symfony) rodando o comando
help. Por exemplo, para pegar detalhes sobre o comando doctrine:database:create, execute:
php app/console help doctrine:database:create
2.1. Livro
115
Sumrio
Com o Doctrine, voc pode se focar nos seus objetos e como eles podem ser teis na sua aplicao, deixando a
preocupao com a persistncia de banco de dados em segundo plano. Isso porque o Doctrine permite que voc use
qualquer objeto PHP para guardar seus dados e se baseia nos metadados de mapeamento para mapear os dados de um
objetos para um tabela especfica no banco.
E apesar do Doctrine girar em torno de um conceito simples, ele incrivelmente poderoso, permitindo que voc crie
consultas complexas e faa subscrio em eventos que permitem a voc executar aes diferentes medida que os
objetos vo passando pelo seu ciclo de vida de persistncia.
Para mais informaes sobre o Doctrine, veja a seo Doctrine do cookbook, que inclui os seguintes artigos:
/bundles/DoctrineFixturesBundle/index
Como usar as extens?es do Doctrine: Timestampable, Sluggable, Translatable, etc.
2.1.8 Testes
Sempre que voc escrever uma nova linha de cdigo, voc tambm adiciona potenciais novos bugs. Para construir
aplicaes melhores e mais confiveis, voc deve testar seu cdigo usando testes funcionais e unitrios.
O Framework de testes PHPUnit
O Symfony2 se integra com uma biblioteca independente - chamada PHPUnit - para dar a voc um rico framework
de testes. Esse capitulo no vai abranger o PHPUnit propriamente dito, mas ele tem a sua excelente documentao
documentation.
Nota: O Symfony2 funciona com o PHPUnit 3.5.11 ou posterior, embora a verso 3.6.4 necessria para testar o
cdigo do ncleo do Symfony.
Cada teste - quer seja teste unitrio ou teste funcional - uma classe PHP que deve residir no sub-diretrio Tests/ de
seus bundles. Se voc seguir essa regra, voc pode executar todos os testes da sua aplicao com o seguinte comando:
# espefifique o diretrio de configurao na linha de comando
$ phpunit -c app/
A opo -c diz para o PHPUnit procurar no diretrio app/ por um arquivo de configurao. Se voc est curioso
sobre as opes do PHPUnit, d uma olhada no arquivo app/phpunit.xml.dist.
Dica: O Code coverage pode ser gerado com a opo --coverage-html.
Testes Unitrios
Um teste unitrio geralmente um teste de uma classe PHP especifica. Se voc quer testar o comportamento global
da sua aplicao, veja a seo sobre Testes Funcionais.
Escrever testes unitrios no Symfony2 no nada diferente do que escrever um teste unitrio padro do PHPUnit.
Vamos supor que, por exemplo, voc tem uma classe incrivelmente simples chamada Calculator no diretrio
Utility/ do seu bundle:
116
Captulo 2. Livro
// src/Acme/DemoBundle/Utility/Calculator.php
namespace Acme\DemoBundle\Utility;
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
}
Para testar isso, crie um arquivo chamado CalculatorTest no diretrio Tests/Utility do seu bundle:
// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
namespace Acme\DemoBundle\Tests\Utility;
use Acme\DemoBundle\Utility\Calculator;
class CalculatorTest extends \PHPUnit_Framework_TestCase
{
public function testAdd()
{
$calc = new Calculator();
$result = $calc->add(30, 12);
// assert that our calculator added the numbers correctly!
$this->assertEquals(42, $result);
}
}
Nota: Por conveno, o sub-diretrio Tests/ deve replicar o diretrio do seu bundle. Ento, se voc estiver testando
uma classe no diretrio Utility/ do seu bundle, coloque o teste no diretrio Tests/Utility/.
Assim como na rua aplicao verdadeira - o autoloading automaticamente habilitado via o arquivo
bootstrap.php.cache (como configurado por padro no arquivo phpunit.xml.dist).
Executar os testes para um determinado arquivo ou diretrio tambm muito fcil:
# executa todos os testes no diretrio Utility
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/
# executa os testes para a classe Article
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
# executa todos os testes para todo o Bundle
$ phpunit -c app src/Acme/DemoBundle/
Testes Funcionais
Testes funcionais verificam a integrao das diferentes camadas de uma aplicao (do roteamento as views). Eles no
so diferentes dos testes unitrios levando em considerao o PHPUnit, mas eles tem um fluxo bem especifico:
Fazer uma requisio;
Testar a resposta;
Clicar em um link ou submeter um formulrio;
2.1. Livro
117
Testar a resposta;
Repetir a operao.
Seu Primeiro Teste Funcional
Testes funcionais so arquivos PHP simples que esto tipicamente no diretrio Tests/Controller do seu bundle. Se voc quer testar as pginas controladas pela sua classe DemoController, inicie criando um novo arquivo
DemoControllerTest.php que extende a classe especial WebTestCase.
Por exemplo, o Symfony2 Standard Edition fornece um teste funcional simples para o DemoController (DemoControllerTest) descrito assim:
// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DemoControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request(GET, /demo/hello/Fabien);
$this->assertTrue($crawler->filter(html:contains("Hello Fabien"))->count() > 0);
}
}
Dica: Para executar seus testes funcionais, a classe WebTestCase class inicializa o kernel da sua aplicao. Na maioria dos casos, isso acontece automaticamente. Entretando, se o seu kernel est em um diretrio diferente do padro,
voc vai precisar modificar seu arquivo phpunit.xml.dist para alterar a varivel de ambiente KERNEL_DIR
para o diretrio do seu kernel:
<phpunit
<!-- ... -->
<php>
<server name="KERNEL_DIR" value="/path/to/your/app/" />
</php>
<!-- ... -->
</phpunit>
O mtodo createClient() retorna um cliente, que como um navegador que voc vai usar para navegar no seu
site:
$crawler = $client->request(GET, /demo/hello/Fabien);
O mtodo request() (veja mais sobre o mtodo request) retorna um objeto Crawler que pode ser usado para
selecionar um elemento na Response, clicar em links, e submeter formulrios.
Dica: O Crawler s funciona se a resposta um documento XML ou HTML. Para pegar a resposta bruta, use
$client->getResponse()->getContent().
Clique em um link primeiramente selecionando-o com o Crawler usando uma expresso XPath ou um seletor CSS,
ento use o Client para clicar nele. Por exemplo, o segunte cdigo acha todos os links com o texto Greet, ento
seleciona o segundo, e ento clica nele:
118
Captulo 2. Livro
$link = $crawler->filter(a:contains("Greet"))->eq(1)->link();
$crawler = $client->click($link);
Submeter um formulrio muito parecido, selecione um boto do formulrio, opcionalmente sobrescreva alguns
valores do formulrio, ento submeta-o:
$form = $crawler->selectButton(submit)->form();
// pega alguns valores
$form[name] = Lucas;
$form[form_name[subject]] = Hey there!;
// submete o formulrio
$crawler = $client->submit($form);
Dica: O formulrio tambm pode manipular uploads e tem mtodos para preencher diferentes tipos de campos (ex.
select() e tick()). Para mais detalhers, veja a seo Forms_ abaixo.
Agora que voc pode facilmente navegar pela sua aplicao, use as afirmaes para testar que ela realmente faz o que
voc espera que ela faa. Use o Crawler para fazer afirmaes no DOM:
// Afirma que a resposta casa com um seletor informado
$this->assertTrue($crawler->filter(h1)->count() > 0);
Ou, teste contra o contedo do Response diretamente se voc s quer afirmar que o conteudo contm algum texto ou
se o Response no um documento XML/HTML:
$this->assertRegExp(/Hello Fabien/, $client->getResponse()->getContent());
O array server so valores brutos que voc espera encontrar normalmente na varivel superglobal do PHP
$_SERVER. Por exemplo, para setar os cabealhos HTTP Content-Type e Referer, voc passar o seguinte:
$client->request(
GET,
/demo/hello/Fabien,
array(),
array(),
array(
CONTENT_TYPE => application/json,
HTTP_REFERER => /foo/bar,
)
);
2.1. Livro
119
O mtodo request() pega o mtodo HTTP e a URL como argumentos e retorna uma instancia de Crawler.
Utilize o Crawler para encontrar elementos DOM no Response. Esses elementos podem ento ser usados para clicar
em links e submeter formulrios:
$link = $crawler->selectLink(Go elsewhere...)->link();
$crawler = $client->click($link);
$form = $crawler->selectButton(validate)->form();
$crawler = $client->submit($form, array(name => Fabien));
Os mtodos click() e submit() retornam um objeto Crawler. Esses mtodos so a melhor maneira de navegar
na sua aplicao por tomarem conta de vrias coisas para voc, como detectar o mtodo HTTP de um formulrio e dar
para voc uma tima API para upload de arquivos.
Dica: Voc vai aprende rmais sobre os objetos Link e Form na seo Crawler abaixo.
O mtodo request pode tambm ser usado para simular submisses de formulrios diretamente ou fazer requisies
mais complexas:
// Submeter diretamente um formurio (mas utilizando o Crawler mais fcil!)
$client->request(POST, /submit, array(name => Fabien));
// Submisso de formulrio com um upload de arquivo
use Symfony\Component\HttpFoundation\File\UploadedFile;
$photo = new UploadedFile(
/path/to/photo.jpg,
photo.jpg,
image/jpeg,
123
);
// ou
$photo = array(
tmp_name => /path/to/photo.jpg,
name => photo.jpg,
type => image/jpeg,
size => 123,
error => UPLOAD_ERR_OK
);
$client->request(
POST,
/submit,
array(name => Fabien),
array(photo => $photo)
);
// Executa uma requisio de DELETE e passa os cabealhos HTTP
$client->request(
DELETE,
/post/12,
array(),
array(),
120
Captulo 2. Livro
Por ltimo mas no menos importante, voc pode forar cara requisio para ser executada em seu proprio processo
PHP para evitar qualquer efeito colateral quando estiver trabalhando com vrios clientes no mesmo script:
$client->insulate();
Navegando
O Cliente suporta muitas operao que podem ser realizadas em um navegador real:
$client->back();
$client->forward();
$client->reload();
// Limpa todos os cookies e histrico
$client->restart();
Se voc usa o cliente para testar sua aplicao, voc pode querer acessar os objetos internos do cliente:
$history
= $client->getHistory();
$cookieJar = $client->getCookieJar();
Acessando o Container
altamente recomendado que um teste funcional teste somente o Response. Mas em circunstancias extremamente
raras, voc pode querer acessar algum objeto interno para escrever afirmaes. Nestes casos, voc pode acessar o
dependency injection container:
$container = $client->getContainer();
Esteja ciente que isso no funciona se voc isolar o cliente ou se voc usar uma camada HTTP. Para ver a lista de
servios disponves na sua aplicao, utilize a task container:debug.
Dica: Se a informao que voc precisa verificar est disponvel no profiler, uso-o ento
2.1. Livro
121
En cada requisio, o profiler do Symfony coleta e guarda uma grande quantidade de dados sobre a manipulao
interna de cada request. Por exemplo, o profiler pode ser usado para verificar se uma determinada pgina executa
menos consultas no banco quando estiver carregando.
Para acessar o Profiler da ltima requisio, fao o seguinte:
$profile = $client->getProfile();
profiler
dentro
de
um
teste,
seja
artigo
Redirecionamento
Quando uma requisio retornar uma redirecionamento como resposta, o cliente automaticamente segue o redirecionamento. Se voc quer examinar o Response antes do redirecionamento use o mtodo followRedirects():
$client->followRedirects(false);
Quando o cliente no segue os redirecionamentos, voc pode forar o redirecionamento com o mtodo
followRedirect():
$crawler = $client->followRedirect();
O Crawler
Uma instancia do Crawler retornada cada vez que voc faz uma requisio com o Client. Ele permite que voc
examinar documentos HTML, selecionar ns, encontrar links e formulrios.
Examinando
Como o jQuery, o Crawler tem metodos para examinar o DOM de um documento HTML/XML. Por exemplo, isso
encontra todos os elementos input[type=submit], seleciona o ltimo da pgina, e ento seleciona o elemento
imediatamente acima dele:
$newCrawler = $crawler->filter(input[type=submit])
->last()
->parents()
->first()
;
122
Captulo 2. Livro
Metodos
filter(h1.title)
filterXpath(h1)
eq(1)
first()
last()
siblings()
nextAll()
previousAll()
parents()
children()
reduce($lambda)
Descrio
Ns que casam com o seletor CSS
Ns que casam com a expresso XPath
N para a posio especifica
Primeiro n
ltimo n
Irmos
Todos os irmos posteriores
Todos os irmos anteriores
Ns de um nivel superior
Filhos
Ns que a funo no retorne false
Como cada um desses mtodos retorna uma nova instncia de Crawler, voc pode restringir os ns selecionados
encadeando a chamada de mtodos:
$crawler
->filter(h1)
->reduce(function ($node, $i)
{
if (!$node->getAttribute(class)) {
return false;
}
})
->first();
Dica: Utilize a funo count() para pegar o nmero de ns armazenados no Crawler: count($crawler)
Extraindo Informaes
Links
Para selecionar links, voc pode usar os mtodos acima ou o conveniente atalho selectLink():
$crawler->selectLink(Click here);
2.1. Livro
123
Isso seleciona todos os links que contm o texto, ou imagens que o atributo alt contm o determinado texto. Como
outros mtodos de filtragem, esse retorna outro objeto Crawler.
Uma vez selecionado um link, voc pode ter acesso a um objeto especial Link, que tem mtodos especificos muito
ties para links (como getMethod() e getUri()). Para clicar no link, use o mtodo do Client click() e passe
um objeto do tipo Link:
$link = $crawler->selectLink(Click here)->link();
$client->click($link);
Formulrios
Assim como nos links, voc seleciona o form com o mtodo selectButton():
$buttonCrawlerNode = $crawler->selectButton(submit);
Nota: Note que selecionamos os botes do formulrio e no os forms, pois o form pode ter vrios botes; se voc
usar a API para examinar, tenha em mente que voc deve procurar por um boto.
O mtodo selectButton() pode selecionar tags button e submit tags input. Ele usa diversas partes diferentes
do boto para encontr-los:
O atributo value;
O atributo id ou alt para imagens;
O valor do atributo id ou name para tags button.
Uma vez que voc tenha o Crawler representanto um boto, chame o mtodo form() para pegar a instancia de Form
do form que envolve o n do boto:
$form = $buttonCrawlerNode->form();
Quando chamar o mtodo form(), voc pode tambm passar uma array com valores dos campos para sobreescrever
os valores padres:
$form = $buttonCrawlerNode->form(array(
name
=> Fabien,
my_form[subject] => Symfony rocks!,
));
E se voc quiser simular algum mtodo HTTP especifico para o form, passe-o como um segundo argumento:
$form = $crawler->form(array(), DELETE);
Os valores dos campos tambm posem ser passsados como um segundo argumento do mtodo submit():
$client->submit($form, array(
name
=> Fabien,
my_form[subject] => Symfony rocks!,
));
Para situaes mais complexas, use a instancia de Form como um array para setar o valor de cada campo individualmente:
124
Captulo 2. Livro
Tambm existe uma API para manipular os valores do campo de acordo com o seu tipo:
// Seleciona um option ou um radio
$form[country]->select(France);
// Marca um checkbox
$form[like_symfony]->tick();
// Faz o upload de um arquivo
$form[photo]->upload(/path/to/lucas.jpg);
Dica: Voc pode pegar os valores que sero submetidos chamando o mtodo getValues() no objeto Form.
Os arquivos do upload esto disponiveis em um array separado retornado por getFiles(). Os mtodos
getPhpValues() e getPhpFiles() tambm retorna valores submetidos, mas no formato PHP (ele converte
as chaves para a notao de colchetes - ex. my_form[subject] - para PHP arrays).
Configurao de Testes
O Client usado pelos testes funcionais cria um Kernel que roda em um ambiente especial chamado test. Uma
vez que o Symfony carrega o app/config/config_test.yml no ambiente test, voc pode ajustar qualquer
configurao de sua aplicao especificamente para testes.
Por exemplo, por padro, o swiftmailer configurado para no enviar realmente os e-mails no ambiente test. Voc
pode ver isso na opo de configurao swiftmailer:
YAML
# app/config/config_test.yml
# ...
swiftmailer:
disable_delivery: true
XML
<!-- app/config/config_test.xml -->
<container>
<!-- ... -->
<swiftmailer:config disable-delivery="true" />
</container>
PHP
// app/config/config_test.php
// ...
$container->loadFromExtension(swiftmailer, array(
disable_delivery => true
));
Voc tambm pode usar um ambiente completamente diferente, ou sobrescrever o modo de debug (true) passando
cada um como uma opo para o mtodo createClient():
2.1. Livro
125
$client = static::createClient(array(
environment => my_test_env,
debug
=> false,
));
Se a sua aplicao se comporta de acordo com alguns cabealhos HTTP, passe eles como o segundo argumento de
createClient():
$client = static::createClient(array(), array(
HTTP_HOST
=> en.example.com,
HTTP_USER_AGENT => MySuperBrowser/1.0,
));
Voc tambm pode sobrescrever cabealhos HTTP numa base por requisies:
$client->request(GET, /, array(), array(), array(
HTTP_HOST
=> en.example.com,
HTTP_USER_AGENT => MySuperBrowser/1.0,
));
Dica: O cliente de testes est disponvel como um servio no container no ambiente teste (ou em qualquer lugar
que a opo framework.test esteja habilitada). Isso significa que voc pode sobrescrever o servio inteiramente se voc
precisar.
Configurao do PHPUnit
Cada aplicao tem a sua prpria configurao do PHPUnit, armazenada no arquivo phpunit.xml.dist. Voc
pode editar o arquivo para mudar os valores padres ou criar um arquivo phpunit.xml para ajustar a configurao
para sua mquina local.
Dica: Armazene o arquivo phpunit.xml.dist no seu repositrio de cdigos e ignore o arquivo phpunit.xml.
Por padro, somente os testes armazenados nos bundles standard so rodados pelo comando phpunit (standard
sendo os testes nos diretrios src/*/Bundle/Tests ou src/*/Bundle/*Bundle/Tests) Mas voc pode
facilmente adicionar mais diretrios. Por exemplo, a seguinte configurao adiciona os testes de um bundle de terceiros
instalado:
<!-- hello/phpunit.xml.dist -->
<testsuites>
<testsuite name="Project Test Suite">
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/Acme/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>
Para incluir outros diretrios no code coverage, edite tambm a sesso <filter>:
<filter>
<whitelist>
<directory>../src</directory>
<exclude>
<directory>../src/*/*Bundle/Resources</directory>
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/Acme/Bundle/*Bundle/Resources</directory>
<directory>../src/Acme/Bundle/*Bundle/Tests</directory>
</exclude>
126
Captulo 2. Livro
</whitelist>
</filter>
2.1.9 Validao
Validao uma tarefa muito comum em aplicaes web. Dado inserido em formulrio precisa ser validado. Dado
tambm precisa ser revalidado antes de ser escrito num banco de dados ou passado a um servio web.
Symfony2 vem acompanhado com um componente Validator que torna essa tarefa fcil e transparente. Esse componente baseado na especificao JSR303 Bean Validation_. O qu ? Uma especificao Java no PHP? Voc ouviu
corretamente, mas no to ruim quanto parece. Vamos olhar como isso pode ser usado no PHP.
As bases da validao
A melhor forma de entender validao v-la em ao. Para comear, suponha que voc criou um bom e velho objeto
PHP que voc precisa usar em algum lugar da sua aplicao:
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
class Author
{
public $name;
}
At agora, essa somente uma classe comum que serve para alguns propsitos dentro de sua aplicao. O objetivo
da validao avisar voc se um dado de um objeto vlido ou no. Para esse trabalho, voc ir configura uma lista
de regras (chamada constraints) em que o objeto deve seguir em ordem para ser validado. Essas regras podem ser
especificadas por um nmero de diferentes formatos (YAML, XML, annotations, ou PHP).
Por exemplo, para garantir que a propriedade $name no vazia, adicione o seguinte:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
name:
- NotBlank: ~
Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
2.1. Livro
127
* @Assert\NotBlank()
*/
public $name;
}
XML
PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
class Author
{
public $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(name, new NotBlank());
}
}
Dica:
Propriedades protected e private podem tambm ser validadas, bem como os mtodos
getter (veja validator-constraint-targets
Prximo passo, para realmente validar um objetoAuthor, use o mtodo validate no servio validator (classe
Validator). A tarefa do validator fcil: ler as restries (i.e. regras) de uma classe e verificar se o dado no
objeto satisfaz ou no aquelas restries. Se a validao falhar, retorna um array de erros. Observe esse simples
exemplo de dentro do controller:
use Symfony\Component\HttpFoundation\Response;
use Acme\BlogBundle\Entity\Author;
// ...
public function indexAction()
{
$author = new Author();
128
Captulo 2. Livro
PHP
<!-- src/Acme/BlogBundle/Resources/views/Author/validate.html.php -->
<h3>The author has the following errors</h3>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error->getMessage() ?></li>
<?php endforeach; ?>
</ul>
Nota:
Cada erro de validao (chamado de constraint violation), representado por um objeto
ConstraintViolation .
2.1. Livro
129
Validao e formulrios
O servio validator pode ser usado a qualquer momento para validar qualquer objeto. Na realidade, entretanto,
voc ir trabalhar frequentemente com o validator indiretamente enquanto trabalhar com formulrio. A biblioteca
Symfonys form usa o servio validator internamente para validar o objeto oculto aps os valores terem sido
enviados e fixados. As violaes de restrio no objeto so convertidas em objetos FieldError que podem ser
facilmente exibidos com seu formulrio. O tipico fluxo de envio do formulrio parece o seguinte dentro do controller:
use Acme\BlogBundle\Entity\Author;
use Acme\BlogBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;
// ...
public function updateAction(Request $request)
{
$author = new Acme\BlogBundle\Entity\Author();
$form = $this->createForm(new AuthorType(), $author);
if ($request->isMethod(POST)) {
$form->bind($request);
if ($form->isValid()) {
// the validation passed, do something with the $author object
$this->redirect($this->generateUrl(...));
}
}
return $this->render(BlogBundle:Author:form.html.twig, array(
form => $form->createView(),
));
}
Nota: Esse exemplo usa uma classe de formulrios AuthorType , que no mostrada aqui.
Para mais informaes, veja: doc:Forms</book/forms> chapter.
Configurao
O validador do Symfony2 abilitado por padro, mas voc deve abilitar explicitamente anotaes se voc usar o
mtodo de anotao para especificar suas restries:
YAML
# app/config/config.yml
framework:
validation: { enable_annotations: true }
XML
<!-- app/config/config.xml -->
<framework:config>
<framework:validation enable_annotations="true" />
</framework:config>
130
Captulo 2. Livro
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(validation => array(
enable_annotations => true,
)));
Restries
O validator designado para validar objtos perante restries (i.e. regras). Em ordem para validar um objeto,
simplesmente mapeie uma ou mais restries para aquela classe e ento passe para o servio validator.
Por trs dos bastidores, uma restrio simplesmente um objeto PHP que faz uma sentena afirmativa. Na vida real,
uma restrio poderia ser: O bolo no deve queimar. No Symfony2, restries so similares: elas so afirmaes
que uma condio verdadeira. Dado um valor, a restriao ir indicar a voc se o valor adere ou no s regras da
restrio.
Resties Suportadas
Algumas restries, como NotBlank, so simples como as outras, como a restrio Choice , tem vrias opes de
configurao disponveis. Suponha que a classeAuthor tenha outra propriedade, gender que possa ser configurado
como male ou female:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
gender:
- Choice: { choices: [male, female], message: Choose a valid gender. }
Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Choice(
choices = { "male", "female" },
*
message = "Choose a valid gender."
*
* )
*/
public $gender;
}
XML
2.1. Livro
131
PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
class Author
{
public $gender;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(gender, new Choice(array(
choices => array(male, female),
message => Choose a valid gender.,
)));
}
}
A opo de uma restrio pode sempre ser passada como um array. Algumas restries, entretanto, tambm permitem
a voc passar o valor de uma opo default no lugar do array. No cado da restrio Choice , as opes choices
podem ser espeficadas dessa forma.
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
gender:
- Choice: [male, female]
Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Choice({"male", "female"})
132
Captulo 2. Livro
*/
protected $gender;
}
XML
PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\Choice;
class Author
{
protected $gender;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(gender, new Choice(array(male, female)));
}
}
Isso significa simplesmente fazer a configurao da opo mais comum de uma restrio mais curta e rpida.
Se voc est incerto de como especificar uma opo, ou verifique a documentao da API para a restrio ou faa de
forma segura sempre passando um array de opes (o primeiro mtodo mostrado acima).
Escopos da restrio
Restries podem ser aplicadas a uma propriedade de classe (e.g. name) ou um mtodo getter pblico (e.g.
getFullName). O primeiro mais comum e fcil de usar, mas o segundo permite voc especificar regras de validao mais complexas.
Propriedades
Validar as propriedades de uma classe a tcnica de validao mais bsica.Symfony2 permite a voc validar propriedades private, protected ou public. A prxima listagem mostra a voc como configurar a propriedade $firstName
da classe Author para ter ao menos 3 caracteres.
YAML
2.1. Livro
133
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
firstName:
- NotBlank: ~
- MinLength: 3
Annotations
// Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\NotBlank()
* @Assert\MinLength(3)
*/
private $firstName;
}
XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<property name="firstName">
<constraint name="NotBlank" />
<constraint name="MinLength">3</constraint>
</property>
</class>
PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\MinLength;
class Author
{
private $firstName;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(firstName, new NotBlank());
$metadata->addPropertyConstraint(firstName, new MinLength(3));
}
}
Getters
Restries podem tambm ser aplicadas no mtodo de retorno de um valor.Symfony2 permite a voc adicionar uma
restrio para qualquer mtodo public cujo nome comec com get ou is. Nesse guia, ambos os tipos de mtodos
so referidos como getters.
O benefcio dessa tcnica que permite a voc validar seu objeto dinamicamente. Por exemplo, suponha que voc
queira ter certeza que um campo de senha no coincida com o primeiro nome do usurio (por motivos de segurana).
134
Captulo 2. Livro
Voc pode fazer isso criando um mtodo isPasswordLegal, e ento afirmando que esse mtodo deva retornar
true:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
getters:
passwordLegal:
- "True": { message: "The password cannot match your first name" }
Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\True(message = "The password cannot match your first name")
*/
public function isPasswordLegal()
{
// return true or false
}
}
XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<getter property="passwordLegal">
<constraint name="True">
<option name="message">The password cannot match your first name</option>
</constraint>
</getter>
</class>
PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\True;
class Author
{
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addGetterConstraint(passwordLegal, new True(array(
message => The password cannot match your first name,
)));
}
}
2.1. Livro
135
Nota: Com uma viso apurada, voc ir perceber que o prefixo do getter (get ou is) omitido no mapeamento.
Isso permite voc mover a restrio para uma propriedade com o mesmo nome mais tarde (ou vice-versa) sem mudar
sua lgica de validao.
Classes
Algumas restries aplicam para a classe inteira ser validada. Por exemplo, a restrio Callback uma restrio
genrica que aplicada para a prpria classe. Quando a classe validada, mtodos especificados por aquela restrio
so simplesmente executadas ento cada um pode prover uma validao mais personalizada.
Grupos de validao
At agora, voc foi capaz de adicionar restries a uma classe e perguntar se aquela classe passa ou no por todas as
restries definidas. Em alguns casos, entretanto, voc precisar validar um objeto a somente algumas das restries
naqula classe. Para fazer isso, voc pode organizar cada restrio dentro de um ou mais grupos de validao, e ento
aplicar validao a apenas um grupo de restries.
Por exemplo, suponha que voc tenha uma classe User , que usada tanto quando um usurio registra e quando um
usurio atualiza sua informaes de contato posteriormente:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\User:
properties:
email:
- Email: { groups: [registration] }
password:
- NotBlank: { groups: [registration] }
- MinLength: { limit: 7, groups: [registration] }
city:
- MinLength: 2
Annotations
// src/Acme/BlogBundle/Entity/User.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
class User implements UserInterface
{
/**
* @Assert\Email(groups={"registration"})
*/
private $email;
/**
* @Assert\NotBlank(groups={"registration"})
* @Assert\MinLength(limit=7, groups={"registration"})
*/
private $password;
/**
136
Captulo 2. Livro
* @Assert\MinLength(2)
*/
private $city;
}
XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\User">
<property name="email">
<constraint name="Email">
<option name="groups">
<value>registration</value>
</option>
</constraint>
</property>
<property name="password">
<constraint name="NotBlank">
<option name="groups">
<value>registration</value>
</option>
</constraint>
<constraint name="MinLength">
<option name="limit">7</option>
<option name="groups">
<value>registration</value>
</option>
</constraint>
</property>
<property name="city">
<constraint name="MinLength">7</constraint>
</property>
</class>
PHP
// src/Acme/BlogBundle/Entity/User.php
namespace Acme\BlogBundle\Entity;
use
use
use
use
Symfony\Component\Validator\Mapping\ClassMetadata;
Symfony\Component\Validator\Constraints\Email;
Symfony\Component\Validator\Constraints\NotBlank;
Symfony\Component\Validator\Constraints\MinLength;
class User
{
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(email, new Email(array(
groups => array(registration)
)));
$metadata->addPropertyConstraint(password, new NotBlank(array(
groups => array(registration)
)));
$metadata->addPropertyConstraint(password, new MinLength(array(
limit => 7,
groups => array(registration)
)));
2.1. Livro
137
Captulo 2. Livro
2.1.10 Formulrios
Lidar com formulrios HTML uma das mais comuns - e desafiadoras - tarefas para um desenvolvedor web. O
Symfony2 integra um componente de formulrio que torna fcil a tarefa de lidar com formulrios. Neste captulo, voc
vai construir um formulrio complexo a partir do zero, aprendendo as caractersticas mais importantes da biblioteca
de formulrios ao longo do caminho.
Nota: O componente de formulrio do Symfony uma biblioteca independente que pode ser utilizada fora de projetos
Symfony2. Para mais informaes, consulte o Componente de Formulrio do Symfony2 no Github.
Nota: Se voc est codificando junto com este exemplo, crie o AcmeTaskBundle primeiro, executando o seguinte
comando (e aceite todas as opes padro):
php app/console generate:bundle --namespace=Acme/TaskBundle
2.1. Livro
139
Essa classe um antigo objeto PHP simples, porque, at agora, no tem nada a ver com Symfony ou qualquer outra
biblioteca. simplesmente um objeto PHP normal que, diretamente resolve um problema no interior da sua aplicao
(ou seja, a necessidade de representar uma tarefa na sua aplicao). Claro, at o final deste captulo, voc ser capaz
de enviar dados para uma instncia Task (atravs de um formulrio HTML), validar os seus dados, e persisti-los para
o banco de dados.
Construindo o Formulrio
Agora que voc j criou a classe Task, o prximo passo criar e renderizar o formulrio HTML real. No Symfony2,
isto feito atravs da construo de um objeto de formulrio e, em seguida, renderizando em um template. Por ora,
tudo isso pode ser feito dentro de um controlador:
// src/Acme/TaskBundle/Controller/DefaultController.php
namespace Acme\TaskBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\TaskBundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
public function newAction(Request $request)
{
// create a task and give it some dummy data for this example
$task = new Task();
$task->setTask(Write a blog post);
$task->setDueDate(new \DateTime(tomorrow));
$form = $this->createFormBuilder($task)
->add(task, text)
->add(dueDate, date)
->getForm();
return $this->render(AcmeTaskBundle:Default:new.html.twig, array(
form => $form->createView(),
));
}
}
Dica: Este exemplo mostra como construir o seu formulrio diretamente no controlador. Mais tarde, na seo
Criando classes de formulrio, voc aprender como construir o seu formulrio em uma classe independente, que
o recomendado pois torna o seu formulrio reutilizvel.
A criao de um formulrio requer relativamente pouco cdigo porque os objetos de formulrio do Symfony2 so
construdos com um construtor de formulrios. A finalidade do construtor de formulrios permitir que voc
escreva receitas simples de formulrios, e ele fazer todo o trabalho pesado, de, realmente, construir o formulrio.
Neste exemplo, voc acrescentou dois campos ao seu formulrio - task e dueDate - que correspondem as propriedades task e dueDate da classe Task. Voc tambm atribuiu a cada um deles um type (exemplo: text, date),
que, entre outras coisas, determina qual(ais) tag(s) HTML de formulrio sero renderizadas para esse campo.
O Symfony2 vem com muitos tipos embutidos, que sero discutidos em breve (veja Tipos de campos integrados
(Built-in)).
140
Captulo 2. Livro
Renderizando o Formulrio
Agora que o formulrio foi criado, o prximo passo renderiz-lo. Isto feito passando um objeto view especial
para o seu template (note o $form->createView() no controlador acima) e usando um conjunto de funes
helper para o formulrio:
Twig
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path(task_new) }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
PHP
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
Nota:
Este exemplo assume que voc criou uma rota chamada task_new que aponta para o controlador
AcmeTaskBundle:Default:new o qual foi criado anteriormente.
isso! Ao imprimir o form_widget(form), cada campo do formulrio renderizado, juntamente com uma label
e uma mensagem de erro (se houver). Fcil assim, embora no muito flexvel (ainda). Normalmente, voc desejar
renderizar cada campo do formulrio individualmente, pois poder controlar como ser a aparncia do formulrio.
Voc aprender como fazer isso na seo Renderizando um formulrio em um Template.
Antes de prosseguirmos, observe como o campo input task renderizado tem o valor da propriedade task do objeto
$task (Ex. Write a blog post). Este o primeiro trabalho de um formulrio: pegar os dados de um objeto e
traduzi-lo em um formato que seja adequado para ser renderizado em um formulrio HTML.
Dica: O sistema de formulrios inteligente o suficiente para acessar o valor da propriedade protegida task atravs
dos mtodos getTask() e setTask() na classe Task. A menos que a propriedade seja pblica, ela deve ter um
mtodo getter e setter para que o componente de formulrio possa obter e definir os dados na propriedade. Para
uma propriedade Boolean, voc pode usar um mtodo isser (por exemplo, isPublished()) em vez de um getter
(Ex. getPublished()).
2.1. Livro
141
O segundo trabalho de um formulrio traduzir os dados enviados pelo usurio de volta as propriedades de um objeto.
Para que isso acontea, os dados enviados pelo usurio devem ser vinculados (bound) ao formulrio. Adicione as
seguintes funcionalidades no seu controlador:
// ...
public function newAction(Request $request)
{
// just setup a fresh $task object (remove the dummy data)
$task = new Task();
$form = $this->createFormBuilder($task)
->add(task, text)
->add(dueDate, date)
->getForm();
if ($request->isMethod(POST)) {
$form->bind($request);
if ($form->isValid()) {
// perform some action, such as saving the task to the database
return $this->redirect($this->generateUrl(task_success));
}
}
// ...
}
Novo na verso 2.1: O mtodo bind tornou-se mais flexvel no Symfony 2.1. Ele aceita agora os dados brutos do
cliente (como antes) ou um objeto Request do Symfony. Ele preferido ao invs do mtodo obsoleto bindRequest.
Agora, quando enviar o formulrio, o controlador vincula (bind) ao formulrio os dados enviados, que traduz os dados
de volta as propriedades task e dueDate do objeto $task. Isso tudo acontece atravs do mtodo bind().
Nota: Assim que o bind() chamado, os dados enviados so transferidos imediatamente para o objeto implcito.
Isso acontece independentemente dos dados implcitos serem realmente vlidos.
Este controlador segue um padro comum para a manipulao de formulrios, e possui trs caminhos possveis:
1. Inicialmente quando se carrega a pgina em um navegador, o mtodo de solicitao (request) GET e o formulrio simplesmente criado e renderizado;
2. Quando o usurio envia o formulrio (Ex., o mtodo POST) mas os dados no so vlidos (a validao ser
discutida na prxima seo), o formulrio vinculado (bound) e ento processado, desta vez exibindo todos os
erros de validao;
3. Quando o usurio envia o formulrio com dados vlidos, o formulrio vinculado (bound) e voc tem a oportunidade de executar algumas aes usando o objeto $task (por exemplo, persisti-lo para o banco de dados)
antes de redirecionar o usurio para outra pgina (por exemplo, uma pgina de obrigado ou sucesso).
Nota: Redirecionar o usurio aps o envio bem sucedido do formulrio impede que ele, ao clicar em atualizar,
reenvie os dados do formulrio.
142
Captulo 2. Livro
Validao do formulrio
Na seo anterior, voc aprendeu como um formulrio pode ser enviado com dados vlidos ou invlidos. No Symfony2, a validao aplicada ao objeto implcito (Ex., Task). Em outras palavras, a questo no se o formulrio vlido, mas se o objeto $task vlido aps a aplicao dos dados enviados pelo formulrio. A chamada
$form->isValid() um atalho que pergunta ao objeto $task se ele possui ou no dados vlidos.
A validao feita adicionando um conjunto de regras (chamadas constraints) uma classe. Para ver isso em ao,
adicione constraints de validao para que o campo task no deve ser vazio e o campo dueDate no deve ser vazio
e deve ser um objeto DateTime vlido.
YAML
# Acme/TaskBundle/Resources/config/validation.yml
Acme\TaskBundle\Entity\Task:
properties:
task:
- NotBlank: ~
dueDate:
- NotBlank: ~
- Type: \DateTime
Annotations
// Acme/TaskBundle/Entity/Task.php
use Symfony\Component\Validator\Constraints as Assert;
class Task
{
/**
* @Assert\NotBlank()
*/
public $task;
/**
* @Assert\NotBlank()
* @Assert\Type("\DateTime")
*/
protected $dueDate;
}
XML
<!-- Acme/TaskBundle/Resources/config/validation.xml -->
<class name="Acme\TaskBundle\Entity\Task">
<property name="task">
<constraint name="NotBlank" />
</property>
<property name="dueDate">
<constraint name="NotBlank" />
<constraint name="Type">
<value>\DateTime</value>
</constraint>
</property>
</class>
PHP
// Acme/TaskBundle/Entity/Task.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
2.1. Livro
143
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
class Task
{
// ...
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(task, new NotBlank());
$metadata->addPropertyConstraint(dueDate, new NotBlank());
$metadata->addPropertyConstraint(dueDate, new Type(\DateTime));
}
}
isso! Se voc reenviar o formulrio com dados invlidos, ver os erros correspondentes exibidos com o formulrio.
Validao HTML5
Com o HTML5, muitos navegadores podem, nativamente, impor certas constraints de validao no lado do cliente. A validao mais comum ativada renderizando um atributo required em campos que so obrigatrios.
Para navegadores que suportam HTML5, isso ir resultar em uma mensagem nativa do navegador sendo exibida
se o usurio tentar enviar o formulrio com o campo em branco.
Os formulrios gerados podem aproveitar ao mximo esta nova funcionalidade, adicionando atributos HTML
que disparam a validao. A validao ao lado do cliente, entretanto, pode ser desativada ao adicionar o atributo
novalidate na tag form ou formnovalidate na tag submit. Isto especialmente til quando voc
quiser testar suas constraints de validao ao lado do servidor, mas esto sendo impedidas pelo seu navegador,
por exemplo, ao enviar campos em branco.
A validao um recurso muito poderoso do Symfony2 e tem seu prprio captulo dedicado.
Grupos de Validao
Dica: Se voc no estiver usando grupos de validao, ento, voc pode pular esta seo.
Se o seu objeto aproveita a grupos de validao, voc precisa especificar qual(ais) grupo(s) de validao seu formulrio
deve usar:
$form = $this->createFormBuilder($users, array(
validation_groups => array(registration),
))->add(...)
;
Se voc est criando classes de formulrio (uma boa prtica), ento voc precisa adicionar o seguinte ao mtodo
setDefaultOptions():
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
validation_groups => array(registration)
));
}
144
Captulo 2. Livro
Em ambos os casos, apenas o grupo de validao registration ser usado para validar o objeto implcito.
Grupos com base nos dados submetidos
Isso ir chamar o mtodo esttico determineValidationGroups() na classe Client aps o formulrio ser
vinculado (bound), mas antes da validao ser executada. O objeto do formulrio passado como um argumento para
esse mtodo (veja o exemplo seguinte). Voc tambm pode definir toda a lgica inline usando uma Closure:
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
validation_groups => function(FormInterface $form) {
$data = $form->getData();
if (Entity\Client::TYPE_PERSON == $data->getType()) {
return array(person)
} else {
return array(company);
}
},
));
}
Cada tipo de campo possui um nmero de opes que podem ser usadas para configur-lo. Por exemplo, o campo
dueDate atualmente processado como 3 select boxes. No entanto, o campo date pode ser configurado para ser
renderizado como uma caixa de texto simples (onde o usurio deve digitar a data como uma string na caixa):
2.1. Livro
145
Cada tipo de campo tem um nmero de opes diferentes que podem ser passadas ele. Muitas delas so especficas
para o tipo de campo e os detalhes podem ser encontrados na documentao de cada tipo.
A opo required
A opo mais comum a opo required, que pode ser aplicada qualquer campo. Por padro, a opo
required definida como true, o que significa que os navegadores prontos para o HTML5 aplicaro a
validao ao lado do cliente se o campo for deixado em branco. Se voc no deseja esse comportamento, defina
a opo required em seu campo para false ou desabilite a validao HTML5.
Alm disso, note que a configurao da opo required para true no resultar em validao aplicada ao
lado do servidor. Em outras palavras, se um usurio enviar um valor em branco para o campo (ou usar um
navegador antigo ou web service, por exemplo), ela ser aceita como um valor vlido, a menos que voc utilize
a constraint de validao do Symfony NotBlank ou NotNull.
Em outras palavras, a opo required agradvel, mas a validao verdadeira ao lado do servidor sempre
dever ser usada.
A adivinhao ativada quando voc omitir o segundo argumento do mtodo add() (ou se voc passar null para
ele). Se voc passar um array de opes como o terceiro argumento (feito para o dueDate acima), estas opes so
aplicadas ao campo adivinhado.
Cuidado: Se o formulrio usa um grupo de validao especfico, o adivinhador do tipo de campo ainda vai
considerar todas as constraints de validao quando estiver adivinhando os seus tipos de campos (incluindo as
constraints que no fazem parte dos grupos de validao sendo utilizados).
146
Captulo 2. Livro
Alm de adivinhar o tipo para um campo, o Symfony tambm pode tentar adivinhar os valores corretos de uma srie
de opes do campo.
Dica: Quando essas opes so definidas, o campo ser renderizado com atributos HTML especiais que fornecem
para a validao HTML5 ao lado do cliente. Entretanto, ele no gera as constraints equivalentes ao lado do servidor
(Ex. Assert\MaxLength). E, embora voc precisar adicionar manualmente a validao ao lado do servidor, essas
opes de tipo de campo podem, ento, ser adivinhadas a partir dessa informao.
required: A opo required pode ser adivinhada com base nas regras de validao (ou seja, o campo
NotBlank ou NotNull) ou metadados do Doctrine (ou seja, o campo nullable). Isto muito til, pois
a sua validao ao lado do cliente ir corresponder automaticamente as suas regras de validao.
min_length: Se o campo uma espcie de campo de texto, ento, a opo min_length pode ser adivinhada
a partir das constraints de validao (se o MinLength ou Min usado) ou a partir dos metadados do Doctrine
(atravs do tamanho do campo).
max_length: Semelhante ao min_length, o tamanho mximo tambm pode ser adivinhado.
Nota: Estas opes de campo so adivinhadas apenas se voc estiver usando o Symfony para adivinhar o tipo de
campo (ou seja, omitir ou passar null como o segundo argumento para o add()).
Se voc desejar modificar um dos valores adivinhados, voc pode sobrescrev-lo passando a opo no array de opes
do campo:
->add(task, null, array(min_length => 4))
PHP
<!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->
2.1. Livro
147
PHP
<?php echo $view[form]->get(value)->getTask() ?>
O helper form_row timo porque voc pode renderizar rapidamente cada campo de seu formulrio (e tambm
possvel personalizar a marcao utilizada para a linha ). Mas, como a vida nem sempre to simples, voc tambm
pode renderizar cada campo inteiramente mo. O produto final do que segue o mesmo de quando voc usou o
helper form_row:
Twig
{{ form_errors(form) }}
<div>
{{ form_label(form.task) }}
{{ form_errors(form.task) }}
{{ form_widget(form.task) }}
</div>
<div>
{{ form_label(form.dueDate) }}
{{ form_errors(form.dueDate) }}
{{ form_widget(form.dueDate) }}
</div>
148
Captulo 2. Livro
{{ form_rest(form) }}
PHP
<?php echo $view[form]->errors($form) ?>
<div>
<?php echo $view[form]->label($form[task]) ?>
<?php echo $view[form]->errors($form[task]) ?>
<?php echo $view[form]->widget($form[task]) ?>
</div>
<div>
<?php echo $view[form]->label($form[dueDate]) ?>
<?php echo $view[form]->errors($form[dueDate]) ?>
<?php echo $view[form]->widget($form[dueDate]) ?>
</div>
<?php echo $view[form]->rest($form) ?>
Se a label auto-gerada para um campo no estiver correta, voc pode especific-la explicitamente:
Twig
{{ form_label(form.task, Task Description) }}
PHP
<?php echo $view[form]->label($form[task], Task Description) ?>
Finalmente, alguns tipos de campos tem opes de renderizao adicionais que podem ser passadas para o widget.
Estas opes esto documentadas com cada tipo, mas uma opo em comum o attr, que permite modificar atributos no elemento do formulrio. O seguinte cdigo adiciona a classe task_field para o campo texto de entrada
renderizado:
Twig
{{ form_widget(form.task, { attr: {class: task_field} }) }}
PHP
<?php echo $view[form]->widget($form[task], array(
attr => array(class => task_field),
)) ?>
Se voc est usando o Twig, uma referncia completa das funes de renderizao do formulrio est disponvel no
manual de referncia. Leia ele para saber tudo sobre os helpers disponveis e as opes que podem ser usadas
com cada um.
Criando classes de formulrio
Como voc viu, um formulrio pode ser criado e usado diretamente em um controlador. No entanto, uma prtica
melhor construir o formulrio separadamente, em uma classe PHP independente, que poder, ento, ser reutilizada
em qualquer lugar na sua aplicao. Crie uma nova classe que vai abrigar a lgica da construo do formulrio de
tarefas:
2.1. Livro
149
// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(task);
$builder->add(dueDate, null, array(widget => single_text));
}
public function getName()
{
return task;
}
}
Esta nova classe contm todas as orientaes necessrias para criar o formulrio de tarefas (Note que o mtodo
getName() deve retornar um identificador exclusivo para esse tipo do formulrio). Ele pode ser usado para
construir rapidamente um objeto de formulrio no controlador:
// src/Acme/TaskBundle/Controller/DefaultController.php
// add this new use statement at the top of the class
use Acme\TaskBundle\Form\Type\TaskType;
public function newAction()
{
$task = // ...
$form = $this->createForm(new TaskType(), $task);
// ...
}
Colocando a lgica do formulrio em sua prpria classe significa que o formulrio pode ser facilmente reutilizado em
outros lugares no seu projeto. Esta a melhor forma de criar formulrios, mas, a deciso final depende de voc.
Setando o data_class
Todo formulrio precisa saber o nome da classe que contm os dados implcitos (Ex.
Acme\TaskBundle\Entity\Task). Normalmente, ele apenas adivinhado com base no objeto
passado no segundo argumento para o createForm (Ex. $task). Mais tarde, quando voc iniciar nos
formulrios embutidos, isto no ser suficiente. Ento, embora nem sempre necessrio, geralmente uma boa
idia especificar explicitamente a opo data_class adicionando o seguinte sua classe type de formulrio:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
data_class => Acme\TaskBundle\Entity\Task,
));
}
150
Captulo 2. Livro
Dica: Ao mapear formulrios para objetos, todos os campos so mapeados. Qualquer campo do formulrio que no
existe no objeto mapeado ir fazer com que uma exceo seja gerada.
Nos casos em que voc precisa de campos extras na formulrio (por exemplo: um checkbox voc concorda com os
termos) que no ser mapeado para o objeto implcito, voc precisa definir a opo property_path como false:
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(task);
$builder->add(dueDate, null, array(property_path => false));
}
Alm disso, se houver quaiquer campos do formulrio que no esto includos nos dados submetidos, esses campos
sero definidos explicitamente como null.
Os dados do campo podem ser acessados em um controlador com:
$form->get(dueDate)->getData();
Formulrios e o Doctrine
O objetivo de um formulrio traduzir os dados de um objeto (Ex. Task) para um formulrio HTML e, em seguida,
traduzir os dados enviados pelo usurio de volta ao objeto original. Como tal, o tpico da persistncia do objeto Task
no banco de dados totalmente no relacionado ao tpico de formulrios. Mas, se voc configurou a classe Task
para ser persistida atravs do Doctrine (ou seja, voc adicionou metadados de mapeamento ele), ento, a persistncia
aps a submisso do formulrio pode ser feita quando o formulrio vlido:
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
return $this->redirect($this->generateUrl(task_success));
}
Se, por algum motivo, voc no tem acesso ao seu objeto $task original, voc pode busc-lo a partir do formulrio:
$task = $form->getData();
2.1. Livro
151
Suponha que cada Task pertence a um simples objeto Category. Inicie, claro, criando o objeto Category:
// src/Acme/TaskBundle/Entity/Category.php
namespace Acme\TaskBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Category
{
/**
* @Assert\NotBlank()
*/
public $name;
}
Agora que a sua aplicao foi atualizada para refletir as novas exigncias, crie uma classe de formulrio para que o
objeto Category possa ser modificado pelo usurio:
// src/Acme/TaskBundle/Form/Type/CategoryType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(name);
}
152
Captulo 2. Livro
O objetivo final permitir que a Category de uma Task possa ser modificada direitamente dentro do prprio
formulrio da tarefa. Para fazer isso, adicione um campo category ao objeto TaskType cujo tipo uma instncia
da nova classe CategoryType:
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->add(category, new CategoryType());
}
Os campos do CategoryType podem agora ser renderizados juntamente com os campos da classe TaskType.
Para ativar a validao no CategoryType, adicione a opo cascade_validation:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
data_class => Acme\TaskBundle\Entity\Category,
cascade_validation => true,
));
}
PHP
<!-- ... -->
<h3>Category</h3>
<div class="category">
<?php echo $view[form]->row($form[category][name]) ?>
</div>
2.1. Livro
153
Quando o usurio enviar o formulrio, os dados submetidos para os campos Category so usados para construir
uma instncia de Category, que ento definida no campo Category da instncia Task.
A instncia Category acessvel naturalmente via $task->getCategory() e pode ser persistida no banco de
dados ou usada como voc precisar.
Embutindo uma coleo de formulrios
Voc tambm pode embutir uma coleo de formulrios em um formulrio (imagine um formulrio Category com
muitos sub-formulrios Product). Isto feito usando o tipo de campo collection.
Para mais informaes consulte no Como embutir uma Coleo de Formulrios e na collection referncia dos tipos
de campo.
Tematizando os formulrios
Cada parte de como um formulrio renderizado pode ser personalizada. Voc est livre para mudar como cada linha
do formulrio renderizada, alterar a marcao usada para renderizar os erros, ou at mesmo, personalizar como uma
tag textarea deve ser renderizada. Nada est fora dos limites, e possvel utilizar diferentes personalizaes em
diferentes lugares.
O Symfony utiliza templates para renderizar todas e cada uma das partes de um formulrio, tais como tags label,
tags input, mensagens de erro e tudo mais.
No Twig, cada fragmento do formulrio representado por um bloco Twig. Para personalizar qualquer parte de
como um formulrio renderizado, voc s precisa substituir o bloco apropriado.
No PHP, cada fragmento do formulrio renderizado por um arquivo de template individual. Para personalizar
qualquer parte de como um formulrio renderizado, voc s precisa sobrescrever o template j existente, criando um
novo.
Para entender como isso funciona, vamos personalizar o fragmento form_row e adicionar um atributo class para o
elemento div que envolve cada linha. Para fazer isso, crie um novo arquivo template que ir armazenar a marcao
nova:
Twig
{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
{% block field_row %}
{% spaceless %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock field_row %}
PHP
<!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php -->
<div class="form_row">
154
Captulo 2. Livro
O fragmento field_row do formulrio utilizado para renderizar a maioria dos campos atravs da da funo
form_row. Para dizer ao componente de formulrio para utilizar o seu novo fragmento field_row definido acima,
adicione o seguinte no topo do template que renderiza o formulrio:
Twig
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form AcmeTaskBundle:Form:fields.html.twig %}
<form ...>
PHP
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<?php $view[form]->setTheme($form, array(AcmeTaskBundle:Form)) ?>
<form ...>
A tag form_theme (no Twig) importa os fragmentos definidos no template informado e utiliza-os quando renderiza o formulrio. Em outras palavras, quando a funo form_row chamada mais tarde neste template, ela usar o
bloco field_row de seu tema personalizado (ao invs do bloco padro field_row que vem com o Symfony).
Para personalizar qualquer parte de um formulrio, voc s precisa substituir o fragmento apropriado. Saber exatamente qual bloco ou arquivo deve-se substituir o tema da prxima seo.
Novo na verso 2.1: Foi introduzida uma sintaxe alternativa do Twig para form_theme no 2.1. Ela aceita qualquer
expresso Twig vlida (a diferena mais notvel est no uso de um array quando utilizar vrios temas).
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form with AcmeTaskBundle:Form:fields.html.twig %}
Para uma discusso mais extensiva, consulte Como personalizar a Renderizao de Formulrios.
Nomeando os fragmentos do formulrio
No Symfony, cada parte de um formulrio que renderizada - elementos de formulrio HTML, erros, labels, etc -
definida em um tema base, que uma coleo de blocos no Twig e uma coleo de arquivos de template no PHP.
No Twig, cada bloco necessrio definido em um nico arquivo de template (form_div_layout.html.twig) que
encontra-se no interior do Twig Bridge. Dentro desse arquivo, voc pode ver todos os blocos necessrios para renderizar um formulrio e todo o tipo de campo padro.
No PHP, os fragmentos so arquivos de template individuais. Por padro, eles esto localizados no diretrio Resources/views/Form do framework bundle (veja no GitHub).
Cada nome de fragmento segue o mesmo padro bsico e dividido em duas partes, separadas por um nico caractere
de sublinhado (_). Alguns exemplos so:
field_row - usado pelo form_row para renderizar a maioria dos campos;
2.1. Livro
155
(Ex.
(Ex.
(Ex.
(Ex.
field_label)
field_widget)
field_errors)
field_row)
Nota: Na verdade, existem outras trs partes - rows, rest e enctype - mas voc raramente ou nunca vai precisar
se preocupar em sobrescrev-las
Ao conhecer o tipo do campo (Ex. textarea) e qual parte voc deseja personalizar (Ex. widget), voc pode
construir o nome do fragmento que precisa ser sobrescrito (Ex. textarea_widget).
Herana dos fragmentos de template
Em alguns casos, o fragmento que voc deseja personalizar parecer estar faltando. Por exemplo, no existe um
fragmento textarea_errors nos temas padro fornecidos com o Symfony. Ento, como so renderizados os
erros de um campo textarea?
A resposta : atravs do fragmento field_errors. Quando o Symfony renderiza os erros para um tipo textarea, ele
procura primeiro por um fragmento textarea_errors antes de voltar para o fragmento field_errors. Cada
tipo de campo tem um tipo pai (o tipo pai do textarea field), e o Symfony usa o fragmento para o tipo pai se
o fragmento base no existir.
Ento, para substituir os erros para apenas os campos textarea, copie o fragmento field_errors, renomeie
para textarea_errors e personalize-o. Para sobrescrever a renderizao de erro padro para todos os campos,
copie e personalize diretamente o fragmento field_errors.
Dica: O tipo pai de cada tipo de campo est disponvel na referncia de tipos do formulrio para cada tipo de
campo.
No exemplo acima, voc usou o helper form_theme (no Twig) para importar os fragmentos personalizados somente para este formulrio. Voc tambm pode dizer ao Symfony para importar as personalizaes do formulrio para
todo o seu projeto.
Twig Para incluir automaticamente os blocos personalizados do template fields.html.twig criado anteriormente em todos os templates, modifique o seu arquivo de configurao da aplicao:
YAML
# app/config/config.yml
twig:
form:
resources:
156
Captulo 2. Livro
- AcmeTaskBundle:Form:fields.html.twig
# ...
XML
<!-- app/config/config.xml -->
<twig:config ...>
<twig:form>
<resource>AcmeTaskBundle:Form:fields.html.twig</resource>
</twig:form>
<!-- ... -->
</twig:config>
PHP
// app/config/config.php
$container->loadFromExtension(twig, array(
form => array(resources => array(
AcmeTaskBundle:Form:fields.html.twig,
))
// ...
));
Quaisquer blocos dentro do template fields.html.twig agora so usados globalmente para definir a sada do
formulrio.
Personalizando toda a sada do formulrio em um nico arquivo com o Twig
No Twig, voc tambm pode personalizar um bloco de formulrio diretamente dentro do template onde a personalizao necessria:
{% extends ::base.html.twig %}
{# import "_self" as the form theme #}
{% form_theme form _self %}
{# make the form fragment customization #}
{% block field_row %}
{# custom field row output #}
{% endblock field_row %}
{% block content %}
{# ... #}
{{ form_row(form.task) }}
{% endblock %}
A tag {% form_theme form _self %} permite que blocos de formulrio sejam personalizados diretamente dentro do template que usar essas personalizaes. Utilize este mtodo para fazer personalizaes de
sada do formulrio rapidamente, que, somente sero necessrias em um nico template.
PHP Para
incluir
automaticamente
os
templates
personalizados
do
diretrio
Acme/TaskBundle/Resources/views/Form criado anteriormente em todos os templates, modifique o
seu arquivo de configurao da aplicao:
2.1. Livro
157
YAML
# app/config/config.yml
framework:
templating:
form:
resources:
- AcmeTaskBundle:Form
# ...
XML
<!-- app/config/config.xml -->
<framework:config ...>
<framework:templating>
<framework:form>
<resource>AcmeTaskBundle:Form</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config>
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(
templating => array(form =>
array(resources => array(
AcmeTaskBundle:Form,
)))
// ...
));
Qualquer fragmento dentro do diretrio Acme/TaskBundle/Resources/views/Form agora ser usado globalmente para definir a sada do formulrio.
Proteo CSRF
CSRF - ou Cross-site request forgery - um mtodo pelo qual um usurio mal-intencionado tenta fazer com que os
seus usurios legtimos, sem saber, enviem dados que eles no pretendem enviar. Felizmente, os ataques CSRF podem
ser prevenidos usando um token CSRF dentro do seu formulrio.
A boa notcia que o Symfony, por padro, incorpora e valida os tokens CSRF automaticamente para voc. Isso
significa que voc pode aproveitar a proteo CSRF sem precisar fazer nada. Na verdade, todo formulrio neste
captulo aproveitou a proteo CSRF!
A proteo CSRF funciona adicionando um campo oculto ao seu formulrio - chamado _token por padro - que
contm um valor que s voc e seu usurio sabem. Isto garante que o usurio - e no alguma outra entidade - est
enviando os dados. O Symfony automaticamente valida a presena e exatido deste token.
O campo _token um campo oculto e ser automaticamente renderizado se voc incluir a funo form_rest()
em seu template, que garante a sada de todos os campos no-renderizados.
O token CSRF pode ser personalizado formulrio por formulrio. Por exemplo:
158
Captulo 2. Livro
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
data_class
=> Acme\TaskBundle\Entity\Task,
csrf_protection => true,
csrf_field_name => _token,
// a unique key to help generate the secret token
intention
=> task_item,
));
}
// ...
}
Para desativar a proteo CSRF, defina a opo csrf_protection para false. As personalizaes tambm podem
ser feitas globalmente em seu projeto. Para mais informaes veja a seo referncia de configurao do formulrio .
Nota: A opo intention opcional, mas aumenta muito a segurana do token gerado, tornando-o diferente para
cada formulrio.
2.1. Livro
159
Por padro, um formulrio assume que voc deseja trabalhar com arrays de dados, em vez de um objeto. H exatamente
duas maneiras em que voc pode mudar esse comportamento e amarrar o formulrio um objeto:
1. Passar um objeto ao criar o formulrio (como o primeiro argumento para createFormBuilder ou o segundo
argumento para createForm);
2. Declarar a opo data_class no seu formulrio.
Se voc no fizer qualquer uma destas, ento o formulrio ir retornar os dados como um array. Neste exemplo, uma
vez que $defaultData no um objeto (e no foi definida a opo data_class), o $form->getData()
retorna um array.
Dica: Voc tambm pode acessar os valores POST (neste caso, name) diretamente atravs do objeto do pedido
(request), desta forma:
$this->get(request)->request->get(name);
Esteja ciente, no entanto, que, na maioria dos casos, usar o mtodo getData() uma melhor escolha, j que retorna os
dados (geralmente um objeto), aps ele ser transformado pelo framework de formulrio.
Adicionando a Validao
A pea que falta a validao. Normalmente, quando voc chama $form->isValid(), o objeto validado atravs
da leitura das constraints que voc aplicou classe. Mas, sem uma classe, como voc pode adicionar constraints para
os dados do seu formulrio?
A resposta configurar as constraints voc mesmo, e pass-las para o seu formulrio. A abordagem global explicada
um pouco mais no validation chapter, mas aqui est um pequeno exemplo:
// import the namespaces above your controller class
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\Collection;
$collectionConstraint = new Collection(array(
name => new MinLength(5),
email => new Email(array(message => Invalid email address)),
));
// create a form, no default values, pass in the constraint option
$form = $this->createFormBuilder(null, array(
validation_constraint => $collectionConstraint,
))->add(email, email)
// ...
;
Agora, quando voc chamar $form->bind($request), a configurao de constraints aqui ser executada em relao aos dados do seu formulrio. Se voc estiver usando uma classe de formulrio, sobrescreva o mtodo
setDefaultOptions para especificar a opo:
namespace Acme\TaskBundle\Form\Type;
use
use
use
use
use
use
160
Symfony\Component\Form\AbstractType;
Symfony\Component\Form\FormBuilder;
Symfony\Component\OptionsResolver\OptionsResolverInterface;
Symfony\Component\Validator\Constraints\Email;
Symfony\Component\Validator\Constraints\MinLength;
Symfony\Component\Validator\Constraints\Collection;
Captulo 2. Livro
Agora, voc tem a flexibilidade de criar formulrios - com validao - que retorna um array de dados, em vez de um
objeto. Na maioria dos casos, melhor - e certamente mais robusto - ligar (bind) o seu formulrio um objeto. Mas,
para formulrios simples, esta uma excelente abordagem.
Consideraes finais
Voc j conhece todos os blocos de construo necessrios para construir formulrios complexos e funcionais para a
sua aplicao. Ao construir formulrios, tenha em mente que a primeira meta de um formulrio traduzir os dados de
um objeto (Task) para um formulrio HTML, para que o usurio possa modificar os dados. O segundo objetivo de
um formulrio pegar os dados enviados pelo usurio e reaplic-los ao objeto.
Ainda h muito mais para aprender sobre o mundo poderoso das formulrios, tais como como lidar com uploads
de arquivos com o Doctrine ou como criar um formulrio onde um nmero dinmico de sub-formulrios podem ser
adicionados (por exemplo, uma lista de tarefas onde voc pode continuar a adicionar mais campos antes de enviar via
Javascript). Veja estes tpicos no cookbook. Alm disso, certifique-se de apoiar-se na documentao de referncia de
tipos de campo, que inclui exemplos de como usar cada tipo de campo e suas opes.
Aprenda mais no Cookbook
Como Manipular o Upload de Arquivos com o Doctrine
File Field Reference
Creating Custom Field Types
Como personalizar a Renderizao de Formulrios
/cookbook/form/dynamic_form_generation
Como usar os Transformadores de Dados
2.1.11 Segurana
Segurana um processo em dois passos principais. Seu objetivo evitar que um usurio tenha acesso a um recurso
que ele no deveria ter.
No primeiro passo do processo, o sistema de segurana identifica quem o usurio exigindo que o mesmo envie algum
tipo de identificao. Este primeiro passo chamado autenticao e signifcica que o sistema est tentando identificar
que o usurio.
2.1. Livro
161
Uma vez que o sistema sabe quem est acessando, o prximo passo determinar se o usurio pode acessar determinado
recurso. Este segundo passo chamado de autorizao e significa que o sistema ir checar se o usurio tem permisso
para executar determinada ao.
162
Captulo 2. Livro
memory:
users:
ryan: { password: ryanpass, roles: ROLE_USER }
admin: { password: kitten, roles: ROLE_ADMIN }
encoders:
Symfony\Component\Security\Core\User\User: plaintext
XML
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
secured_area => array(
pattern => ^/,
anonymous => array(),
http_basic => array(
realm => Secured Demo Area,
),
),
),
access_control => array(
array(path => ^/admin, role => ROLE_ADMIN),
),
providers => array(
in_memory => array(
memory => array(
users => array(
ryan => array(password => ryanpass, roles => ROLE_USER),
admin => array(password => kitten, roles => ROLE_ADMIN),
2.1. Livro
163
),
),
),
),
encoders => array(
Symfony\Component\Security\Core\User\User => plaintext,
),
));
Dica: A distribuio padro do Symfony coloca a configurao de segurana em um arquivo separado (e.g.
app/config/security.yml). Se voc no tem um arquivo separado para as configuraes de segurana, pode
colocar diretamente no arquivo de configurao principal (por exemplo, app/config/config.yml).
O resultado final desta configurao um completo sistema de segurana funcional com as seguintes caractersticas:
H dois usurios no sistema (ryan e admin);
Os usurios se autenticam atravs da janela de autenticao bsica HTTP;
Qualquer URL que comece com /admin/* ser protegida e somente o usurio admin ter acesso;
Todas URLs que no comecem com /admin/* so acessveis a todos usurios (e ao usurio nunca sero
solicitadas as credenciais de acesso).
Vamos dar uma olhada como funciona a segurana e como cada parte da configurao influencia no sistema.
Como funciona a segurana: Autenticao e Autorizao
O sistema de segurana do Symfony funciona determinando quem um usurio (autenticao) e depois checando se
o usurio tem acesso ao recurso especfico ou URL solicitado.
Firewalls (Autenticao)
Quando um usurio requisita uma URL que est protegida por um firewall, o sistema de segurana ativado. O
trabalho do firewall determinar se o usurio precisa ou no ser autenticado. Se ele precisar, envia a resposta de volta
e inicia o processo de autenticao.
Um firewall ser ativado quando a URL requisitada corresponda ao padro de caracteres da expresso regular configurada na configurao de segurana. Neste exemplo, o padro de caracteres (^/) corresponde a
qualquer solicitao. O fato do firewall ser ativado no significa, porm, que a janela de autenticao bsica HTTP
(solicitando login e senha) ser exibida para todas requisies. Por exemplo, qualquer usurio poder acessar /foo
sem que seja solicitada sua autenticao.
164
Captulo 2. Livro
Isto funciona primeiramente por que o firewall permite usurios annimos atravs do parmetro anonymous da
configurao. Em outras palavras, o firewall no exige que o usurio se autentique completamente. E por que nenhum
perfil (role) necessrio para acessar /foo (na seo access_control), a solicitao pode ser realizada
sem que o usurio sequer se identifique.
Se voc remover a chave anonymous, o firewall sempre far o usurio se identificar por completo imediatamente.
Controles de acesso (Autorizao)
Se o usurio solicitar /admin/foo, porm, o processo toma um rumo diferente. Isto acontecer por que a seo
access_control da configurao indica que qualquer URL que se encaixe no padro de caracteres ^/admin (isto
, /admin ou qualquer coisa do tipo /admin/*) deve ser acessada somente por usurios com o perfil ROLE_ADMIN.
Perfis so a base para a maioria das autorizaes: o usurio pode acessar /admin/foo somente se tiver o perfil
ROLE_ADMIN.
2.1. Livro
165
Como antes, o firewall no solicita credenciais de acesso. Assim que a camada de controle de acesso nega o acesso
(por que o usurio no tem o perfil ROLE_ADMIN), porm, o firewall inicia o processo de autenticao. Este processo
depende do mecanismo de autenticao que estiver utilizando. Por exemplo, se estiver utilizando o mtodo de formulrio de autenticao (form login), o usurio ser redirecionado para a pgina de login. Se estiver utilizando o
mtodo bsico de autenticao HTTP, o navegador recebe uma resposta do tipo HTTP 401 para que ao usurio seja
exibida a janela de login/senha do navegador.
O usurio agora tem a oportunidade de digitar suas credenciais no aplicativo. Se as credenciais forem vlidas, a
requisio original ser solicitada novamente.
166
Captulo 2. Livro
No exemplo, o usurio ryan se autentica com sucesso pelo firewall. Como, porm, ryan no tem o perfil
ROLE_ADMIN, ele ainda ter seu acesso negado ao recurso /admin/foo. Infelizmente, isto significa que o usurio
ver uma mensagem indicando que o acesso foi negado.
Dica: Quando o Symfony nega acesso a um usurio, o usurio v uma tela de erro e o navegador recebe uma resposta
com o HTTP status code 403 (Forbidden). possvel personalizar a tela de erro de acesso negado seguindo as
instrues em Error Pages do do texto do Symfony 2 - Passo-a-passo que ensina a personalizar a pgina de erro 403.
Finalmente, se o usurio admin requisitar /admin/foo, um processo similar entra em ao, mas neste caso, aps a
autenticao, a camada de controle de acesso permitir que a requisio seja completada:
2.1. Livro
167
O fluxo de requisio quando um usurio solicita um recurso protegido direto, mas muito flexvel. Como ver
mais tarde, a autenticao pode acontecer de diversas maneiras, incluindo formulrio de login, certificado X.509, ou
autenticao pelo Twitter. Independente do mtodo de autenticao, o fluxo de requisiao sempre o mesmo:
1. Um usurio acessa um recurso protegido;
2. O aplicativo redireciona o usurio para o formulrio de login;
3. O usurio envia suas credenciais (e.g. login/senha);
4. O firewall autentica o usurio;
5. O usurio autenticado redirecionado para o recurso solicitado originalmente.
Nota: O processo exato na verdade depende um pouco do mecanismo de autenticao que estiver usando. Por
exemplo, quando estiver utilizando formulrio de login, o usurio envia suas credenciais para a URL que processa o
formulrio (por exemplo, /login_check) e depois redirecionado de volta para a URL solicitada originalmente
(por exemplo, /admin/foo). Se utilizar autenticao bsica HTTP, porm, o usurio envia suas credenciais diretamente para a URL original (por exemplo, /admin/foo) e depois a pgina retornada para o usurio na mesma
requisio (isto significa que no h redirecionamentos).
Estes detalhes tcnicos no devem ser relevantes no uso do sistema de segurana, mas bom ter uma idia a respeito.
Dica: Voc aprender mais tarde como qualquer coisa pode ser protegida no Symfony2, incluindo controladores
especficos, objetos, ou at mtodos PHP.
168
Captulo 2. Livro
/login
/login_check
XML
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
secured_area => array(
pattern => ^/,
anonymous => array(),
form_login => array(
login_path => /login,
check_path => /login_check,
),
),
),
));
Dica: Se no precisar de personlizar os valores de login_path ou check_path (os valores utilizados acima so
os valores padro), voc pode encurtar seu configurao:
YAML
2.1. Livro
169
form_login: ~
XML
<form-login />
PHP
form_login => array(),
Agora, quando o sistema de segurana inicia o processo de autenticao, ele redirecionar o usurio para o formulrio
de login (/login por padro). sua tarefa implementar o visual desse formulrio. Primeiro, crie duas rotas: uma
para a exibio do formulrio de login (no caso, /login) e outra para processar a submisso do formulrio (no caso,
/login_check):
YAML
# app/config/routing.yml
login:
pattern:
/login
defaults: { _controller: AcmeSecurityBundle:Security:login }
login_check:
pattern:
/login_check
XML
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="login" pattern="/login">
<default key="_controller">AcmeSecurityBundle:Security:login</default>
</route>
<route id="login_check" pattern="/login_check" />
</routes>
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(login, new Route(/login, array(
_controller => AcmeDemoBundle:Security:login,
)));
$collection->add(login_check, new Route(/login_check, array()));
return $collection;
Nota: No preciso implementar o controller para a URL /login_check pois o firewall interceptar e processar o
que foi submitido para essa URL. opcional, porm til, criar uma rota para que voc possa gerar o link de submisso
na template do formulrio de login.
170
Captulo 2. Livro
Novo na verso 2.1: Com o Symfony 2.1, voc deve possuir rotas configuradas para suas URLs login_path (ex.
/login), check_path (ex. /login_check) e logout (ex. /logout - veja Logging Out_).
Observe que o nome da rota login no importante. O que importa que a URL da rota corresponda o que foi
colocado na configurao login_path, pois para onde o sistema de segurana redirecionar os usurios que
precisarem se autenticar.
O prximo passo criar o controller que exibir o formulrio de login:
// src/Acme/SecurityBundle/Controller/Main;
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
class SecurityController extends Controller
{
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// get the login error if there is one
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render(AcmeSecurityBundle:Security:login.html.twig, array(
// last username entered by the user
last_username => $session->get(SecurityContext::LAST_USERNAME),
error
=> $error,
));
}
}
No se confunda com esse controller. Como ver, quando o usurio submete o formulrio, o sistema de segurana
automaticamente processar a submisso para voc. Se o usurio entrou com login e/ou senha invlidos, este controller
pega o erro ocorrido do sistema de segurana para poder exibir ao usurio.
Em outras palavras, seu trabalho exibir o formulrio de login e qualquer erro ocorrido durante a tentativa de autenticao, mas o sistema de segurana j toma conta de checar se as credenciais so vlidas e de autenticar o usurio.
Finalmente crie a template correspondente:
Twig
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form action="{{ path(login_check) }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
2.1. Livro
171
{#
If you want to control the URL the user is redirected to on success (more details below)
<input type="hidden" name="_target_path" value="/account" />
#}
<input type="submit" name="login" />
</form>
PHP
<?php // src/Acme/SecurityBundle/Resources/views/Security/login.html.php ?>
<?php if ($error): ?>
<div><?php echo $error->getMessage() ?></div>
<?php endif; ?>
<form action="<?php echo $view[router]->generate(login_check) ?>" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="<?php echo $last_username ?>" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<!-If you want to control the URL the user is redirected to on success (more details below)
<input type="hidden" name="_target_path" value="/account" />
-->
<input type="submit" name="login" />
</form>
Dica: A varivel error passada para a template uma instncia de AuthenticationException. Esta pode
conter mais informaes - ou at informaes sensveis - sobre a falha na autenticao, por isso use-a com sabedoria!
O formulrio tem que atender alguns requisitos. Primeiro, ao submeter o formulrio para /login_check (atravs
da rota login_check), o sistema de segurana interceptar a submisso do formulrio e o processar. Segundo, o
sistema de segurana espera que os campos submetidos sejam chamados _username e _password (estes nomes
podem ser configured).
E isso! Quando submeter um formulrio, o sistema de segurana ir automaticamente checar as credenciais do
usurio e autentic-lo ou enviar o ele de volta ao formulrio de login para o erro ser exibido.
Vamos revisar o processo inteiro:
1. O usurio tenta acessar um recurso que est protegido;
2. O firewall inicia o processo de autenticao redirecionando o usurio para o formulrio de login(/login);
3. A pgina /login produz o formulrio de login atravs da rota e controlador criados neste exemplo;
4. O usurio submete o formulrio de login para /login_check;
5. O sistema de segurana intercepta a solicitao, verifica as credenciais submetidas pelo usurio, autentica o
mesmo se tiverem corretas ou envia de volta para o formulrio de login caso contrrio;
Por padro, se as credenciais estiverem corretas, o usurio ser redirecionado para a pgina que solicitou originalmente
(e.g. /admin/foo). Se o usurio originalmente solicitar a pgina de login, ele ser redirecionado para a pgina
principal. Isto pode ser modificado se necessrio, o que permitiria voc redirecionar o usurio para um outra URL
especfica.
172
Captulo 2. Livro
Para maiores detalhes sobre isso e como personalizar o processamento do formulrio de login acesse Como personalizar o seu Formulrio de Login.
2.1. Livro
173
XML
<access-control>
<rule path="^/" role="ROLE_ADMIN" />
</access-control>
PHP
access_control => array(
array(path => ^/, role => ROLE_ADMIN),
),
XML
<access-control>
<rule path="^/login" role="IS_AUTHENTICATED_ANONYMOUSLY" />
<rule path="^/" role="ROLE_ADMIN" />
</access-control>
PHP
access_control => array(
array(path => ^/login, role => IS_AUTHENTICATED_ANONYMOUSLY),
array(path => ^/, role => ROLE_ADMIN),
),
Alm disso, se o seu firewall no permite usurios annimos, voc precisar criar um firewall especial para
permitir usurios annimos para a pgina de login:
YAML
firewalls:
login_firewall:
pattern:
anonymous:
secured_area:
pattern:
form_login:
^/login$
~
^/
~
XML
174
Captulo 2. Livro
Autorizao
O primeiro passo na segurana sempre a autenticao: o processo de verficar quem o usurio . No Symfony, a
autenticao pode ser feita de vrias maneiras - via formulrio de login, autenticao bsica HTTP ou at mesmo pelo
Facebook.
Uma vez que o usurio est autenticado, a autorizao comea. Autorizao fornece uma maneira padro e poderosa
de decidir se o usurio pode acessar algum recurso (uma URL, um objeto do modelo, um mtodo...). Isto funciona
com perfis atribudos para cada usurio e exigindo perfis diferentes para diferentes recursos.
O processo de autorizao tem dois lados diferentes:
1. O usurio tem um conjunto de perfis especfico;
2. Um recurso requer um perfil especfico para ser acessado.
Nesta seo, o foco ser em como tornar seguros diferentes recursos (por exemplo URLs, chamadas a mtodos, etc)
com diferentes perfis. Mais tarde, voc aprender mais como perfis so criados e atribudos aos usurios.
Protegendo padres de URLs
A maneira mais bsica de proteger seu aplicativo proteger um padro de URL. Voc j viu no primeiro exemplo deste
captulo que qualquer requisio que se encaixasse na expresso regular ^/admin exigiria o perfil ROLE_ADMIN.
Voc pode definir quantos padres precisar. Cada um uma expresso regular.
YAML
# app/config/config.yml
security:
# ...
access_control:
- { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
- { path: ^/admin, roles: ROLE_ADMIN }
XML
<!-- app/config/config.xml -->
<config>
<!-- ... -->
<rule path="^/admin/users" role="ROLE_SUPER_ADMIN" />
<rule path="^/admin" role="ROLE_ADMIN" />
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
// ...
access_control => array(
array(path => ^/admin/users, role => ROLE_SUPER_ADMIN),
array(path => ^/admin, role => ROLE_ADMIN),
),
));
Dica: Iniciando o padro com ^ garante que somente URLs comeando com o padro ter uma comparao positiva.
Por exemplo, o padro simples /admin (sem o ^) resultaria em uma comparao positiva para /admin/foo, mas
tambm para URLs como /foo/admin.
2.1. Livro
175
Para cada requisio que chega, o Symfony2 tenta encontrar uma regra de acesso correspondente, com comparao
positiva do padro (a primeira que encontrar ganha). Se o usurio no estiver autenticado ainda, a autenticao
iniciada (isto , o usurio tem a chance de fazer login). Se o usurio, porm, j estiver autenticado, mas no tiver
o perfil exigido, uma exceo disparada AccessDeniedException , que voc pode tratar e transformar em
uma apresentvel pgina de Acesso Negado para o usurio. Veja Como personalizar as pginas de erro para mais
informaes.
Como o Symfony utiliza a primeira regra de acesso que der uma comparao positiva, uma URL como
/admin/users/new corresponder a primeira regra e exigir somente o perfil ROLE_SUPER_ADMIN. Qualquer
URL como /admin/blog corresponder a segunda regra e exigir o perfil ROLE_ADMIN.
Protegendo por IP
Algumas situaes podem exigir que voc restrinja o acesso de uma determinada rota com base no IP. Isto particularmente relevante no caso de Edge Side Includes (ESI), por exemplo, que utiliza a rota com nome _internal. Quanto
ESI utilizado, a rota _internal requerida pelo gateway cache (gerente de cache) para possibilitar diferentes opes
de caching para subsees dentro de uma determinada pgina. Esta rota vem com o prefixo ^/_internal por padro na
edio padro (assumindo que voc ativou estas linhas do seu arquivo de configurao de rotas - routing.yml).
Aqui est um exemplo de como poderia proteger esta rota de acesso externo:
YAML
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
XML
<access-control>
<rule path="^/_internal" role="IS_AUTHENTICATED_ANONYMOUSLY" ip="127.0.0.1" />
</access-control>
PHP
Assim como a proteo por IP, exigir o uso de SSL to simples quanto adicionar uma nova entrar em access_control:
YAML
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https
XML
<access-control>
<rule path="^/cart/checkout" role="IS_AUTHENTICATED_ANONYMOUSLY" requires_channel="https" />
</access-control>
176
Captulo 2. Livro
PHP
Protegendo um Controller
Proteger seu aplicativo baseado em padres de URL fcil, mas este mtodo pode no ser especfico o bastante em
certos casos. Quando necessrio, voc pode ainda facilmente forar autorizao de dentro de um controller:
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
public function helloAction($name)
{
if (false === $this->get(security.context)->isGranted(ROLE_ADMIN)) {
throw new AccessDeniedException();
}
// ...
}
Voc pode ainda instalar e utilizar opcionalmente o JMSSecurityExtraBundle, que te permite proteger controllers atravs de anotaes:
use JMS\SecurityExtraBundle\Annotation\Secure;
/**
* @Secure(roles="ROLE_ADMIN")
*/
public function helloAction($name)
{
// ...
}
Para mais informaes, veja a documentao JMSSecurityExtraBundle . Se voc a distribuio Standard do Symfony,
este bundle est habilitado por padro. Se no estiver, voc pode facilmente baixar e instal-lo.
Protegendo outros servios
De fato, qualquer coisa pode ser protegida em Symfony utilizando uma estratgia similar a apresentada na seo
anterior. Por exemplo, suponha que voc tem um servio (uma classe PHP, por exemplo) que seu trabalho enviar
e-mails de um usurio para outro. Voc pode restringir o uso dessa classe - no importa de onde est sendo utilizada a usurios que tenham um perfil especfico.
Para mais informaes sobre como voc pode utilizar o componente de segurana para proteger diferentes servios e
mtodos de seu aplicativo, consulte /cookbook/security/securing_services.
Listas De Controle De Acesso (ACLs): Protegendo Objetos Especficos Do Banco De Dados
Imagine que voc est projetando um sistema de blog onde seus usurios podem comentar seus posts. Agora, voc
quer que um usurio tenha a possibilidade de editar seus prprios comentrios, mas no aqueles de outros usurios.
Alm disso, como administrador, voc quer poder editar todos os comentrios.
2.1. Livro
177
O componente de segurana possui um sistema de listas de controle de acesso (ACL) que te permite controlar acesso
a instncias individuais de um objeto no seu sistema. Sem ACL, voc consegue proteger seu sistema para que somente
usurios especficos possam editar os comentrios. Com ACL, porm, voc pode restringir ou permitir o acesso por
comentrio.
Para mais informao, veja o passo-a-passo: Listas de controle de acesso (ACLs).
Usurios
Nas sees anteriores, voc aprendeu como proteger diferentes recursos exigindo um conjunto de perfis para o acesso
a um recurso. Nesta seo exploraremos outro aspecto da autorizao: os usurios.
De onde os usurios vm? (User Providers)
Durante a autenticao, o usurio submete um conjunto de credenciais (normalmente login e senha). O trabalho do
sistema de autenticao verificar essas credenciais contra um conjunto de usurios. De onde essa lista de usurios
vem ento?
No Symfony2, usurios podem vir de qualquer lugar - um arquivo de configurao, um banco de dados, um servio
web ou qualquer outra fonte que desejar. Qualquer coisa que disponibiliza um ou mais usurios para o sistema de
autenticao conhecido como user provider. O Symfony2 vem por padro com os dois mais comuns: um que
carrega os usurios do arquivo de configurao e outro que carrega os usurios do banco de dados.
Especificando usurios no arquivo de configurao O jeito mais fcil de especificar usurios diretamnete no
arquivo de configurao. De fato, voc j viu isso em um exemplo neste captulo.
YAML
# app/config/config.yml
security:
# ...
providers:
default_provider:
memory:
users:
ryan: { password: ryanpass, roles: ROLE_USER }
admin: { password: kitten, roles: ROLE_ADMIN }
XML
<!-- app/config/config.xml -->
<config>
<!-- ... -->
<provider name="default_provider">
<memory>
<user name="ryan" password="ryanpass" roles="ROLE_USER" />
<user name="admin" password="kitten" roles="ROLE_ADMIN" />
</memory>
</provider>
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
// ...
178
Captulo 2. Livro
Este user provider chamado de in-memory user provider, j que os usurios no esto armazenados em nenhum
banco de dados. O objeto usurio fornecido pelo Symfony (User).
Dica: Qualquer user provider pode carregar usurios diretamente da configurao se especificar o parmetro de
configurao users e listar os usurios abaixo dele.
Cuidado: Se seu login todo numrico (77, por exemplo) ou contm hfen (user-name, por exemplo), voc
deveria utilizar a sintaxe alternativa quando especificar usurios em YAML:
users:
- { name: 77, password: pass, roles: ROLE_USER }
- { name: user-name, password: pass, roles: ROLE_USER }
Para sites menores, este mtodo rpido e fcil de configurar. Para sistemas mais complexos, voc provavelmente
desejar carregar os usurios do banco de dados.
Carregando usurios do banco de dados Se voc desejar carregar seus usurios atravs do Doctrine ORM, voc
pode facilmente o fazer criando uma classe User e configurando o entity provider
Nessa abordagem, voc primeiro precisa criar sua prpria classe User, que ser persistida no banco de dados.
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class User implements UserInterface
{
/**
* @ORM\Column(type="string", length="255")
*/
protected $username;
// ...
}
Ao que diz respeito ao sistema de segurana, o nico requisito para sua classe User personalizada que ela implemente a interface UserInterface . Isto significa que conceito de usurio pode ser qualquer um, desde que
implemente essa interface.
2.1. Livro
179
Novo na verso 2.1: No Symfony 2.1, o mtodo equals foi removido do UserInterface. Se voc precisa sobrescrever a implementao default da lgica de comparao, implemente a nova interface EquatableInterface
.
Nota: O objeto User ser serializado e salvo na sesso entre requisies, por isso recomendado que voc implemente
a interface Serializable em sua classe User. Isto especialmente importante se sua classe User tem uma classe pai
com propriedades private.
Em seguida, configure um user provider entity e aponte-o para sua classe User:
YAML
# app/config/security.yml
security:
providers:
main:
entity: { class: Acme\UserBundle\Entity\User, property: username }
XML
<!-- app/config/security.xml -->
<config>
<provider name="main">
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
providers => array(
main => array(
entity => array(class => Acme\UserBundle\Entity\User, property => username
),
),
));
Com a introduo desse novo provider, o sistema de autenticao tentar carregar o objeto User do banco de dados a
partir do campo username da classe.
Nota: Este exemplo somente para demonstrar a idia bsica por trs do provider entity. Para um exemplo
completo, consulte /cookbook/security/entity_provider.
Para mais informaes sobre como criar seu prprio provider (se precisar carregar usurios do seu servio web por
exemplo), consulte Como criar um Provider de Usurio Personalizado.
Protegendo a senha do usurio
At agora, por simplicidade, todos os exemplos armazenavam as senhas dos usurios em texto puro (sendo armazenados no arquivo de configurao ou no banco de dados). Claro que em um aplicativo profissional voc desejar
proteger as senhas dos seus usurios por questes de segurana. Isto facilmente conseguido mapeando sua classe
User para algum encoder disponvel. Por exemplo, para armazenar seus usurio em memria, mas proteger a senha
deles atravs da funo de hash sha1, faa o seguinte:
YAML
180
Captulo 2. Livro
# app/config/config.yml
security:
# ...
providers:
in_memory:
memory:
users:
ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: ROLE_US
admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: ROLE_AD
encoders:
Symfony\Component\Security\Core\User\User:
algorithm:
sha1
iterations: 1
encode_as_base64: false
XML
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
// ...
providers => array(
in_memory => array(
memory => array(
users => array(
ryan => array(password => bb87a29949f3a1ee0559f8a57357487151281386, r
admin => array(password => 74913f5cd5f61ec0bcfdb775414c2fb3d161b620,
),
),
),
),
encoders => array(
Symfony\Component\Security\Core\User\User => array(
algorithm
=> sha1,
iterations
=> 1,
encode_as_base64 => false,
),
),
));
Ao definir iterations como 1 e encode_as_base64 como false, a senha codificada simplesmente obtida
como o resultado de sha1 aps uma iterao apenas, sem codificao extra. Voc pode agora calcular a senha
codificada por cdigo PHP (e.g. hash(sha1, ryanpass)) ou atravs de alguma ferramenta online como
functions-online.com .
2.1. Livro
181
Se voc estiver criando seus usurio dinamicamente e os armazenando no banco de dados, voc pode usar algoritmos
the hash ainda mais complexos e ento delegar em um objeto encoder para ajudar a codificar as senhas. Por exemplo, suponha que seu objeto User Acme\UserBundle\Entity\User (como no exemplo acima). Primeiro,
configure o encoder para aquele usurio:
YAML
# app/config/config.yml
security:
# ...
encoders:
Acme\UserBundle\Entity\User: sha512
XML
<!-- app/config/config.xml -->
<config>
<!-- ... -->
<encoder class="Acme\UserBundle\Entity\User" algorithm="sha512" />
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
// ...
encoders => array(
Acme\UserBundle\Entity\User => sha512,
),
));
Neste caso, voc est utilizando um algoritmo mais forte sha512. Alm disso, desde que voc especificou o algoritmo
(sha512) como um texto, o sistema ir, por padro, utilizar a funo de hash 5000 vezes em uma linha e ento
o codificar como base64. Em outras palavras, a senha foi muito codificada de maneira que a senha no pode ser
decodificada (isto , voc no pode determinar qual a senha a partir da senha codificada).
Se voc tem alguma espcie de formulrio de registro para os visitantes, voc precisar a senha codificada para poder armazenar. No importa o algoritmo que configurar para sua classe User, a senha codificada pode sempre ser
determinada da seguinte maneira a partir de um controller:
$factory = $this->get(security.encoder_factory);
$user = new Acme\UserBundle\Entity\User();
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword(ryanpass, $user->getSalt());
$user->setPassword($password);
Aps a autenticao, o objeto User do usurio atual pode ser acessado atravs do servio security.context.
De dentro de um controller, faa o seguinte:
public function indexAction()
{
182
Captulo 2. Livro
$user = $this->get(security.context)->getToken()->getUser();
}
Cada mecanismo de autenticao (exemplos: Autenticao HTTP, formulrio de login, etc) usa exatamente um user
provider, e utilizar, por padro, o primeiro user provider configurado. O que acontece se voc quiser que alguns de
seus usurios sejam autenticados por arquivo de configurao e o resto por banco de dados? Isto possvel criando
um novo user provider que ativa os dois juntos:
YAML
# app/config/security.yml
security:
providers:
chain_provider:
chain:
providers: [in_memory, user_db]
in_memory:
users:
foo: { password: test }
user_db:
entity: { class: Acme\UserBundle\Entity\User, property: username }
XML
<!-- app/config/config.xml -->
<config>
<provider name="chain_provider">
<chain>
<provider>in_memory</provider>
<provider>user_db</provider>
</chain>
</provider>
<provider name="in_memory">
<user name="foo" password="test" />
</provider>
<provider name="user_db">
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
2.1. Livro
183
XML
<!-- app/config/config.xml -->
<config>
<provider name=="main_provider">
<memory>
<user name="foo" password="test" />
</memory>
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
providers => array(
main_provider => array(
memory => array(
users => array(
foo => array(password => test),
),
),
184
Captulo 2. Livro
Voc pode ainda configurar o firewall ou mecanismos de autenticao individuais para utilizar um user provider especfico. Novamente, a menos que um provider seja especificado explicitamente, o primeiro ser sempre utilizado:
YAML
# app/config/config.yml
security:
firewalls:
secured_area:
# ...
provider: user_db
http_basic:
realm: "Secured Demo Area"
provider: in_memory
form_login: ~
XML
<!-- app/config/config.xml -->
<config>
<firewall name="secured_area" pattern="^/" provider="user_db">
<!-- ... -->
<http-basic realm="Secured Demo Area" provider="in_memory" />
<form-login />
</firewall>
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
firewalls => array(
secured_area => array(
// ...
provider => user_db,
http_basic => array(
// ...
provider => in_memory,
),
form_login => array(),
),
),
));
Neste exemplo, se um usurio tentar se autenticar atravs de autenticao HTTP, o sistema utilizar o user provider
in_memory. Se o usurio tentar, porm, se autenticar atravs do formulrio de login, o provider user_db ser
usado (pois o padro para todo o firewall).
Para mais informaes sobre a configurao
/reference/configuration/security.
2.1. Livro
do
user
provider
do
firewall,
veja
185
Perfis (Roles)
A idia de um perfil chave no processo de autorizao. Para cada usurio atribudo um conjunto de perfis e ento
cada recurso exige um ou mais perfis. Se um usurio tem os perfis requeridos, o acesso concedido. Caso contrrio,
o acesso negado.
Perfis so muito simples e basicamente textos que voc pode inventar e utilizar de acordo com suas necessidades
(embora perfis sejam objetos PHP internamente). Por exemplo, se precisar limitar acesso a uma seo administrativa
do blog de seu website, voc pode proteger a seo utilizando o perfil ROLE_BLOG_ADMIN. Este perfil no precisa
de estar definido em lugar nenhum - voc pode simplesmente usar o mesmo.
Nota: Todos os perfis devem comear com o prefixo ROLE_ para serem gerenciados pelo Symfony2. Se voc definir
seus prprios perfis com uma classe Role dedicada (mais avanado), no utilize o prefixo ROLE_.
Hierarquia de Perfis
Ao invs de associar muitos perfis aos usurios, voc pode defined regras de herana ao criar uma hierarquia de perfis:
YAML
# app/config/security.yml
security:
role_hierarchy:
ROLE_ADMIN:
ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
XML
<!-- app/config/security.xml -->
<config>
<role id="ROLE_ADMIN">ROLE_USER</role>
<role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
role_hierarchy => array(
ROLE_ADMIN
=> ROLE_USER,
ROLE_SUPER_ADMIN => array(ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH),
),
));
Na configurao acima, usurios com o perfil ROLE_ADMIN tero tambm o perfil ROLE_USER. O perfil ROLE_SUPER_ADMIN tem os ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH e ROLE_USER (herdado do
ROLE_ADMIN).
Saindo do sistema
Normalmente, voc tambm quer que seus usurios possam sair do sistema. Felizmente, o firewall consegue lidar com
isso automaticamente quando o parmetro de configurao logout est ativo:
YAML
186
Captulo 2. Livro
# app/config/config.yml
security:
firewalls:
secured_area:
# ...
logout:
path:
/logout
target: /
# ...
XML
<!-- app/config/config.xml -->
<config>
<firewall name="secured_area" pattern="^/">
<!-- ... -->
<logout path="/logout" target="/" />
</firewall>
<!-- ... -->
</config>
PHP
// app/config/config.php
$container->loadFromExtension(security, array(
firewalls => array(
secured_area => array(
// ...
logout => array(path => logout, target => /),
),
),
// ...
));
Uma vez que est configurado no seu firewall, redirecionando o usurio para /logout (ou qualquer outro caminho
que configurar em path), o usurio no estar mais autenticado. O usurio ser ento redirecionado para a pgina
principal (o valor definido no parmetro target). Ambas configuraes path e target tem valor padro iguais
ao especificado aqui. Em outras palavras, a menos que precise personalizar, voc pode simplesmente os omitir completamente e simplificar sua configurao:
YAML
logout: ~
XML
<logout />
PHP
logout => array(),
Note que voc no precisar implementar o controller para a URL /logout j que o firewall cuida disso. Voc deve,
entretante, precisar criar uma rota para que possa usar para gerar a URL:
Aviso: Com o Symfony 2.1, voc deve ter uma rota que corresponde ao seu caminho para logout. Sem esta rota,
o logout no ir funcionar.
YAML
2.1. Livro
187
# app/config/routing.yml
logout:
pattern:
/logout
XML
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="logout" pattern="/logout" />
</routes>
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(logout, new Route(/logout, array()));
return $collection;
Uma vez que o usurio no est mais autenticado, ele ser redirecionado para o que tiver definido no parmetro target. Para mais informaes sobre a configurao de logout, veja Security Configuration
Reference.
Controle de Acesso em Templates
Se voc quiser checar se o usurio atual tem um determinado perfil de dentro de uma template, use a funo:
Twig
{% if is_granted(ROLE_ADMIN) %}
<a href="...">Delete</a>
{% endif %}
PHP
<?php if ($view[security]->isGranted(ROLE_ADMIN)): ?>
<a href="...">Delete</a>
<?php endif; ?>
Nota: Se voc usar esta funo e no estiver em uma URL que est atrs de um firewall ativo, uma exceo ser
gerada. Novamente, quase sempre uma boa idia ter um firewall principal que protege todas as URLs (como visto
neste captulo).
188
Captulo 2. Livro
Nota: Um firewall deve estar ativo ou uma exceo ser gerada quanto o mtodo isGranted for chamado. Veja a
nota acima sobre templates para mais detalhes.
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<!-- ... -->
<switch-user />
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main=> array(
// ...
switch_user => true
),
),
));
Para mudar para outro usurio, basta adicionar o parmetro de URL _switch_user indicando o usurio (username)
na URL atual:
http://example.com/somewhere?_switch_user=thomas
Para voltar ao usurio original, use como nome de usurio o texto _exit:
http://example.com/somewhere?_switch_user=_exit
2.1. Livro
189
Claro que esta funcionalidade precisar estar disponvel para um grupo reduzido de usurios. Por padro, o acesso
restrito a usurios que tem o perfil ROLE_ALLOWED_TO_SWITCH. O nome deste perfil pode ser modificado atravs
do parmetro de configurao role. Para segurana extra, voc pode ainda mudar o nome do parmetro de URL
atravs da configurao parameter:
YAML
# app/config/security.yml
security:
firewalls:
main:
// ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<!-- ... -->
<switch-user role="ROLE_ADMIN" parameter="_want_to_be_this_user" />
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main=> array(
// ...
switch_user => array(role => ROLE_ADMIN, parameter => _want_to_be_this_user
),
),
));
XML
<!-- app/config/security.xml -->
<config>
<firewall stateless="true">
<http-basic />
190
Captulo 2. Livro
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(http_basic => array(), stateless => true),
),
));
Nota: Se utiliza formulrio de login, o Symfony2 criar um cookie mesmo se voc definir stateless como true.
Palavras Finais
Segurana pode ser um assunto profundo e complexo de se resolver em uma aplicao. Felizmente, o componente
de segurana do Symfony segue um bom modelo baseado em autenticao e autorizao. Autenticao, que sempre acontece antes, gerenciada pelo firewall cujo trabalho determinar a identidade do usurio atravs de diversos
possveis mtodos (exemplo, autenticao HTTP, formulrio de login, etc). No passo-a-passo, voc encontrar exemplos de como outros mtodos de autenticao podem ser utilizados, incluindo como implementar o funcionalidade de
Lembrar de mim baseada em cookie.
Uma vez que o usurio est autenticado, a camada de autorizao pode determinar se o usurio deve ou no deve ter
acesso a um recurso especfico. Comumente, perfis so aplicados a URLs, classes ou mtodos e se o usurio atual
no possuir o perfil, o acesso negado. A camada de autorizao, porm, muito mais extensa e segue o sistema de
votao onde vrias partes podem determinar se o usurio atual deve ter acesso a determinado recurso. Saiba mais
sobre este e outros tpicos no passo-a-passo.
Aprenda mais do Passo-a-Passo
Forando HTTP/HTTPS
Coloque usurios por IP na lista negra com um voter personalizado
Listas de Controle de Acesso (ACLs)
/cookbook/security/remember_me
2.1. Livro
191
Mas um gateway cache no o nico tipo de cache. Na verdade, os cabealhos de cache HTTP enviados pela sua
aplicao so consumidos e interpretados por trs tipos diferentes de cache:
192
Captulo 2. Livro
Caches de Navegador: Todo navegador vem com seu prprio cache local que til principalmente quando voc
aperta o voltar ou para imagens e outros assets. O cache do navegador um cache privado assim os recursos
cacheados no so compartilhados com ningum mais.
Caches de Proxy: Um proxy um cache compartilhado assim muitas pessoas podem utilizar um nico deles.
Ele geralmente instalado por grandes empresas e ISPs para reduzir a latncia e o trfego na rede.
Caches Gateway: Como um proxy, ele tambm um cache compartilhado mas no lado do servidor. Instalado
por administradores de rede, ele torna os sites mais escalveis, confiveis e performticos.
Dica: Caches gateway algumas vezes so referenciados como caches de proxy reverso, surrogate caches ou at
aceleradores HTTP.
Nota: A diferena entre os caches privados e os compartilhados se torna mais bvia a medida que comeamos a falar
sobre fazer cache de respostas com contedo que especfico para exatamente um usurio (e.g. informao de uma
conta).
Toda resposta da sua aplicao ir provavelmente passar por um ou ambos os dois primeiros tipos de cache. Esses
caches esto fora de seu controle mas eles seguem o direcionamento do cache HTTP definido na resposta.
Proxy Reverso do Symfony2
O Symfony2 vem com um proxy reverso (tambm chamado de gateway cache) escrito em PHP. s habilit-lo e as
respostas cacheveis da sua aplicao comearam a ser cacheadas no mesmo momento. Sua instalao bem simples.
Toda nova aplicao Symfony2 vem com um kernel de cache pr-configurado (AppCache) que encapsula o kernel
padro (AppKernel). O Kernel de cache o proxy reverso.
Para habilitar o cache, altere o cdigo do front controller para utilizar o kernel de cache:
// web/app.php
require_once __DIR__./../app/bootstrap.php.cache;
require_once __DIR__./../app/AppKernel.php;
require_once __DIR__./../app/AppCache.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(prod, false);
$kernel->loadClassCache();
// wrap the default AppKernel with the AppCache one
$kernel = new AppCache($kernel);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
O kernel de cache funcionar imediatamente como um proxy reverso - fazendo cache das respostas da sua aplicao e
retornando-as para o cliente.
Dica: O kernel de cache tem um mtodo especial getLog() que retorna uma representao em texto do que ocorreu
na camada de cache. No ambiente de desenvolvimento, utilize-o para depurar e validar sua estratgia de cache:
error_log($kernel->getLog());
O objeto AppCache tem uma configurao padro razovel, mas ela pode receber um ajuste fino por meio de um
conjunto de opes que podem ser definidas sobrescrevendo o mtodo getOptions():
2.1. Livro
193
// app/AppCache.php
class AppCache extends Cache
{
protected function getOptions()
{
return array(
debug
default_ttl
private_headers
allow_reload
allow_revalidate
stale_while_revalidate
stale_if_error
);
}
}
=>
=>
=>
=>
=>
=>
=>
false,
0,
array(Authorization, Cookie),
false,
false,
2,
60,
Dica: A menos que seja sobrescrita em getOptions(), a opo debug ser definida como o valor padro de
depurao no AppKernel envolvido.
Aqui vai uma lista das opes principais:
default_ttl: O nmero de segundos que uma entrada do cache deve ser considerada como atual quando
nenhuma informao de atualizao for passada na resposta. Os cabealhos explcitos Cache-Control e
Expires sobrescrevem esse valor (padro: 0);
private_headers:
Conjunto de cabealhos de requisio que acionam o comportamento
Cache-Control privado nas respostas que no declaram explicitamente se a resposta public ou
private por meio de uma diretiva Cache-Control. (padro: Authorization e Cookie);
allow_reload: Diz se o cliente pode forar um recarregamento do cache incluindo uma diretiva
Cache-Control no-cache na requisio. Defina ele como true para seguir a RFC 2616 (padro: false);
allow_revalidate: Diz se o cliente pode forar uma revalidao do cache incluindo uma diretiva
Cache-Control max-age=0 na requisio. Defina ele como true para seguir a RFC 2616 (padro: false);
stale_while_revalidate: Diz o nmero padro de segundos (a granularidade o segundo como na
preciso da Resposta TTL) durante o qual o cache pode retornar imediatamente uma resposta antiga enquanto ele faz a revalidao dela no segundo plano (padro: 2); essa configurao sobrescrita pela extenso
stale-while-revalidate do Cache-Control HTTP (veja RFC 5861);
stale_if_error: Diz o nmero padro de segundos (a granularidade o segundo) durante o qual o cache
pode fornecer uma resposta antiga quando um erro for encontrado (padro: 60). Essa configurao sobrescrita
pela extenso stale-if-error do Cache-Control HTTP (veja RFC 5861).
Se debug for true, o Symfony2 adiciona automaticamente um cabealho X-Symfony-Cache na resposta contendo informaes teis sobre o que o cache serviu ou deixou passar.
194
Captulo 2. Livro
O Cabealho Cache-Control
O cabealho Cache-Control nico pois ele contm no um, mas vrios pedaos de informao sobre a possibilidade de cache de uma resposta. Cada pedao de informao separada por uma vrgula:
Cache-Control: private, max-age=0, must-revalidate
Cache-Control: max-age=3600, must-revalidate
O Symfony fornece uma abstrao em volta do cabealho Cache-Control para deixar sua criao mais gerencivel:
$response = new Response();
// mark the response as either public or private
2.1. Livro
195
$response->setPublic();
$response->setPrivate();
// set the private or shared max age
$response->setMaxAge(600);
$response->setSharedMaxAge(600);
// set a custom Cache-Control directive
$response->headers->addCacheControlDirective(must-revalidate, true);
Tanto o gateway cache quando o proxy cache so considerados caches compartilhados pois o contedo cacheado
compartilhado por mais de um usurio. Se uma resposta especfica de um usurio for incorretamente armazenada por
um cache compartilhado, ela poderia ser retornada posteriormente para um nmero incontvel de usurios diferentes.
Imagine se a informao da sua conta fosse cacheada e depois retornada para todos os usurios que em seguida
solicitassem a pgina da conta deles!
Para lidar com essa situao, cada resposta precisa ser configurada para ser pblica ou privada:
public: Indica que a resposta pode ser cacheada tanto por caches privados quanto pelos compartilhados;
private: Indica que a mensagem toda ou parte da resposta destinada para um nico usurio e no deve ser
cacheada por um cache compartilhado.
O Symfony tem como padro conservador definir toda resposta como privada. Para se beneficiar dos caches compartilhados (como o proxy reverso do Symfony2), a resposta precisa ser definida como pblica explicitamente.
Mtodos Seguros
O cache HTTP s funciona para os mtodos HTTP seguros (como o GET e o HEAD). Ser seguro significa que voc
no consegue alterar o estado da aplicao no servidor quando estiver respondendo a requisio ( claro que voc pode
logar a informao, fazer cache dos dados etc). Isso tem duas consequncias importantes:
Voc nunca deve alterar o estado de sua aplicao quando estiver respondendo uma requisio GET ou HEAD.
Mesmo se voc no usar um gateway cache, a presena de caches proxy faz com que qualquer requisio GET
ou HEAD possa atingir ou no seu servidor.
No espere que os mtodos PUT, POST ou DELETE sejam cacheados. Esses mtodos so destinados para serem
utilizados quando se quer alterar o estado da sua aplicao (e.g. excluir uma postagem de um blog). Fazer cache
desses mtodos poderia fazer com que certas requisies no chegassem na sua aplicao e a alterasse.
Regras e Padres de Cache
O HTTP 1.1 permite por padro fazer o cache de qualquer coisa a menos que seja explcito que no num cabealho
Cache-Control. Na prtica, a maioria dos caches no faz nada quando as requisies tem um cookie, um cabealho
de autorizao, usam um mtodo inseguro (i.e PUT, POST, DELETE) ou quando as respostas tem cdigo de estado
para redirecionamento.
O Symfony2 define automaticamente um cabealho Cache-Control conservador quando o desenvolvedor no
definir nada diferente seguindo essas regras:
Se no for definido cabealho de cache (Cache-Control, Expires, ETag or Last-Modified),
Cache-Control configurado como no-cache, indicando que no ser feito cache da resposta;
196
Captulo 2. Livro
Se Cache-Control estiver vazio (mas outros cabealhos de cache estiverem presentes), seu valor configurado como private, must-revalidate;
Mas se pelo menos uma diretivaCache-Control estiver definida, e nenhuma diretiva public ou privada tiver sido adicionada explicitamente, o Symfony2 adiciona automaticamente a diretiva private (exceto quando
s-maxage estiver definido).
Expirao e Validao HTTP
A especificao HTTP define dois modelos de cache:
Com o expiration model, voc especifica simplesmente quanto tempo uma resposta deve ser considerada atual
incluindo um cabealho Cache-Control e/ou um Expires. Os caches que entendem a expirao no faro
a mesma requisio at que a verso cacheada atinja o tempo de expirao e se torne antiga.
Quando as pginas so realmente dinmicas (i.e. sua representao muda constantemente), o validation model
frequentemente necessrio. Com esse modelo, o cache armazena a resposta, mas pergunta ao servidor a cada
requisio se a resposta cacheada continua vlida ou no. A aplicao utiliza um identificador nico da resposta
(o cabealho Etag) e/ou um timestamp (o cabealho Last-Modified) para verificar se a pgina mudou
desde quando tinha sido cacheada.
O objetivo de ambos os modelos nunca ter que gerar a mesma resposta duas vezes contando com o cache para guardar
e retornar respostas atuais.
Lendo a Especificao HTTP
A especificao HTTP define uma linguagem simples mas poderosa com a qual clientes e servidores podem se
comunicar. Como um desenvolvedor web, o modelo requisio-resposta da especificao domina o seu trabalho.
Infelizmente o documento real da especificao - RFC 2616 - pode ser difcil de ler.
Existe um trabalho em andamento (HTTP Bis) para reescrever a RFC 2616. Ele no descreve uma nova verso
do HTTP, mas principalmente esclarece a especificao HTTP original. A organizao tambm melhorou pois a
especificao foi dividida em sete partes; tudo que for relacionado ao cache HTTP pode ser encontrado em duas
partes dedicadas ( P4 - Conditional Requests e P6 - Caching: Browser and intermediary caches)
Como desenvolvedor web, ns recomendamos fortemente que voc leia a especificao. Sua clareza e poder ainda mais depois de dez anos da sua criao - incalculvel. No se engane com a aparncia da especificao
- o contedo dela muito mais bonito que sua capa.
Expirao
O modelo de expirao o mais eficiente e simples dos dois modelos de cache e deve ser usado sempre que possvel.
Quando uma resposta cacheada com uma expirao, o cache ir armazenar a resposta e retorn-la diretamente sem
acessar a aplicao at que a resposta expire.
O modelo de expirao pode ser aplicado usando um desses dois, quase idnticos, cabealhos HTTP: Expires ou
Cache-Control.
Expirao com o Cabealho Expires
De acordo com a especificao HTTP, o campo do cabealho Expires diz a data/horrio a partir do qual a resposta
considerada antiga. O cabealho Expires pode ser definido com o mtodo setExpires() Response. Ele
recebe uma instncia de DateTime como argumento:
2.1. Livro
197
Nota: O mtodo setExpires() converte automaticamente a data para o fuso horrio GMT como exigido na
especificao.
O cabealho Expires sofre com duas limitaes. Primeiro, o relgio no servidor web e o cache (e.g. o navegador)
precisam estar sincronizados. A outra que a especificao define que servidores HTTP/1.1 no devem mandar datas
Expires com mais de um ano no futuro.
Expirao com o Cabealho Cache-Control
Devido s limitaes do cabealho Expires, na maioria das vezes, voc deve usar no lugar dele o cabealho
Cache-Control. Lembre-se que o cabealho Cache-Control usado para especificar vrias diretivas de cache
diferentes. Para expirao, existem duas diretivas: max-age e s-maxage. A primeira usada para todos os caches
enquanto a segunda somente utilizada por caches compartilhados:
// Define o nmero de segundos aps o qual a resposta
// no ser mais considerada atual
$response->setMaxAge(600);
// O mesmo que acima, mas apenas para caches compartilhados
$response->setSharedMaxAge(600);
O cabealho Cache-Control deve ter o seguinte formato (ele pode ter diretivas adicionais):
Cache-Control: max-age=600, s-maxage=600
Validao
Quando um recurso precisa ser atualizado logo que uma mudana for feita em dados relacionados, o modelo de
expirao insuficiente. Com o modelo de expirao, a aplicao no ser acionada para retornar a resposta atualizada
at que o cache finalmente se torne antigo.
O modelo de validao resolve esse problema. Nesse modelo, o cache continua a armazenar as respostas. A diferena
que, para cada requisio, o cache pede para a aplicao verificar se a respostas cacheada continua vlida ou no.
Se o cache ainda for vlido, sua aplicao deve retornar um cdigo de estado 304 e nenhum contedo. Isso diz para o
cache que ele pode retornar a resposta cacheada.
Nesse modelo, voc economiza principalmente banda pois a representao no enviada duas vezes para o mesmo
cliente (uma resposta 304 mandada no lugar). Mas se projetar sua aplicao com cuidado, voc pode ser capaz
de pegar o mnimo de dados necessrio para enviar uma resposta 304 e tambm economizar CPU (veja abaixo um
exemplo de uma implementao).
Dica: O cdigo de estado 304 significa Not Modified. Isso importante pois com esse cdigo de estado no
colocado o contedo real que est sendo requisitado. Em vez disso, a resposta simplesmente um conjunto leve de
direes que dizem ao cache para ele utilizar uma verso armazenada.
198
Captulo 2. Livro
Assim como com a expirao, existem dois cabealhos HTTP diferentes que podem ser utilizados para implementar o
modelo de validao: ETag e Last-Modified.
Validao com o Cabealho ETag
O cabealhoETag um cabealho em texto (chamado de entity-tag) que identifica de forma nica uma representao do recurso alvo. Ele totalmente gerado e configurado pela sua aplicao, dessa forma voc pode dizer,
por exemplo, se o recurso /about que foi armazenado pelo cache est atualizado com o que sua aplicao poderia
retornar. Uma ETag como uma impresso digital e utilizada para comparar rapidamente se duas verses diferentes
de um recurso so equivalentes. Como as impresses digitais, cada ETag precisa ser nica em todos as representaes
do mesmo recurso.
Vamos analisar uma implementao simples que gera a ETag como o hash md5 do contedo:
public function indexAction()
{
$response = $this->render(MyBundle:Main:index.html.twig);
$response->setETag(md5($response->getContent()));
$response->isNotModified($this->getRequest());
return $response;
}
O mtodo Response::isNotModified() compara o ETag enviado na Request com o que est definido na
Response. Se os dois combinarem, o mtodo define automaticamente o cdigo de estado da Response como 304.
Esse algoritmo simples o suficiente e bem genrico, mas voc precisa criar a Response inteira antes de ser capaz
de calcular a Etag, o que no o melhor possvel. Em outras palavras, isso economiza banda de rede mas no faz o
mesmo com os ciclos de CPU.
Na seo Otimizando seu Cdigo com Validao, ns mostraremos como a validao pode ser utilizada de forma mais
inteligente para determinar a validade de um cache sem muito trabalho.
Dica:
O Symfony2 tambm suporta ETags fracas passando true como segundo argumento para o mtodo
setETag().
O cabealho Last-Modified a segunda forma de validao. De acordo com a especificao HTTP, O campo
do cabealho Last-Modified indica a data e o horrio que o servidor de origem acredita que a representao foi
modificada pela ltima vez. Em outras palavras, a aplicao decide se o contedo cacheado foi atualizado ou no
tendo como base se ele foi atualizado desde que a resposta foi cacheada.
Por exemplo, voc pode usar a ltima data de atualizao de todos os objetos necessrios para calcular a representao
do recurso como o valor para o cabealho Last-Modified:
public function showAction($articleSlug)
{
// ...
$articleDate = new \DateTime($article->getUpdatedAt());
$authorDate = new \DateTime($author->getUpdatedAt());
$date = $authorDate > $articleDate ? $authorDate : $articleDate;
$response->setLastModified($date);
2.1. Livro
199
$response->isNotModified($this->getRequest());
return $response;
}
O mtodo Response::isNotModified() compara o cabealho If-Modified-Since mandado pela requisio com o cabealho Last-Modified definido na resposta. Se eles forem equivalentes, a Response ser configurada com um cdigo de estado 304.
Nota: O cabealho da requisio If-Modified-Since igual ao cabealho Last-Modified de uma resposta
enviada ao cliente para um recurso
especfico. Essa a forma como o cliente e o servidor se comunicam entre si e decidem se o recurso foi
ou no atualizado desde que ele foi
cacheado.
O objetivo principal de qualquer estratgia de cache aliviar a carga sobre a aplicao. Colocando de outra
forma, quanto menos coisas voc fizer na sua aplicao para retornar uma resposta 304, melhor. O mtodo
Response::isNotModified() faz exatamente isso expondo um padro simples e eficiente:
public
{
//
//
//
//
function showAction($articleSlug)
Pega a informao mnima para calcular
a ETag ou o valor Last-Modified
(baseado na Requisio, o dado recuperado
de um banco de dados ou de um armazenamento chave-valor)
$article = // ...
// cria uma Resposta com uma ETag e/ou um cabealho Last-Modified
$response = new Response();
$response->setETag($article->computeETag());
$response->setLastModified($article->getPublishedAt());
// Verifica se a Resposta no diferente da Requisio
if ($response->isNotModified($this->getRequest())) {
// retorna imediatamente a Resposta 304
return $response;
} else {
// faa mais algumas coisas aqui - como buscar mais dados
$comments = // ...
// ou renderize um template com a $response que voc j
// inicializou
return $this->render(
MyBundle:MyController:article.html.twig,
array(article => $article, comments => $comments),
$response
);
}
}
Quando a Response no tiver sido modificada, isNotModified() automaticamente define o cdigo de estado
para 304, remove o contedo e remove alguns cabealhos que no podem estar presentes em respostas 304 (veja
200
Captulo 2. Livro
setNotModified()).
Variando a Resposta
At agora assumimos que cada URI tem exatamente uma representao do recurso alvo. Por padro, o cache HTTP
feito usando a URI do recurso como a chave do cache. Se duas pessoas requisitarem a mesma URI de um recurso
passvel de cache, a segunda pessoa receber a verso cacheada.
Algumas vezes isso no suficiente e diferentes verses da mesma URI precisam ser cacheadas baseando-se em um
ou mais valores dos cabealhos de requisio. Por exemplo, se voc comprimir a pgina quando o cliente suportar
compresso, cada URI ter duas representaes: uma quando o cliente suportar compresso e uma quando o cliente
no suportar. Essa deciso feita usando o valor do cabealho Accept-Encoding da requisio.
Nesse caso, precisamos que o cache armazene ambas as verses da requisio, para uma determinada URI, comprimida
e no, e retorne-as se baseando no valor Accept-Encoding da requisio. Isso feito utilizando o cabealho Vary
da resposta, que uma lista separada por vrgulas dos diferentes cabealhos cujos valores acionam um representao
diferente do recurso requisitado:
Vary: Accept-Encoding, User-Agent
Dica: Esse cabealho Vary especfico pode fazer o cache de diferentes verses de cada recurso baseado na URI e no
valor dos cabealhos Accept-Encoding e User-Agent da requisio.
O objeto Response fornece um interface limpa para gerenciar o cabealho Vary:
// define um cabealho vary
$response->setVary(Accept-Encoding);
// define mltiplos cabealhos vary
$response->setVary(array(Accept-Encoding, User-Agent));
O mtodo setVary() recebe o nome de um cabealho ou um array de nomes de cabealhos para os quais a resposta
varia.
Expirao e Validao
claro que voc pode usar ambas a validao e a expirao dentro da mesma Response. Como a expirao mais
importante que a validao, voc pode se beneficiar facilmente do melhor dos dois mundos. Em outras palavras,
utilizando ambas a expirao e a validao, voc pode ordenar que o cache sirva o contedo cacheado, ao mesmo
tempo que verifica em algum intervalo de tempo (a expirao) para ver se o contedo continua vlido.
Mais Mtodos Response
A classe Response fornece muitos outros mtodos relacionados ao cache. Aqui seguem os mais teis:
// Marca a Resposta como antiga
$response->expire();
// Fora a resposta para retornar um 304 apropriado sem contedo
$response->setNotModified();
Adicionalmente, a maioria dos cabealhos HTTP relacionados ao cache podem ser definidos por meio do mtodo
setCache() apenas:
2.1. Livro
201
Nota: Perceba pelo exemplo que cada tag ESI tem uma URL completamente vlida. Uma tag ESI representa um
fragmento de pgina que pode ser recuperado pela URL informada.
Quando uma requisio tratada, o gateway cache busca a pgina inteira do cache ou faz a requisio no backend da
aplicao. Se a resposta contiver uma ou mais tags ESI, elas so processadas da mesma forma. Em outras palavras, o
cache gateway pega o fragmento da pgina inserido ou do seu cache ou faz a requisio novamente para o backend da
aplicao. Quado todas as tags ESI forem resolvidas, o gateway cache mescla cada delas na pgina principal e envia
o contedo finalizado para o cliente.
Tudo isso acontece de forma transparente no nvel do gateway cache (i.e. fora de sua aplicao). Como voc pode ver,
se voc escolher tirar proveito das tags ESI, o Symfony2 faz com que o processo de inclu-las seja quase sem esforo.
Usando ESI no Symfony2
Primeiro, para usar ESI, tenha certeza de ter feito sua habilitao na configurao da sua aplicao:
YAML
# app/config/config.yml
framework:
# ...
esi: { enabled: true }
202
Captulo 2. Livro
XML
<!-- app/config/config.xml -->
<framework:config ...>
<!-- ... -->
<framework:esi enabled="true" />
</framework:config>
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(
// ...
esi
=> array(enabled => true),
));
Agora, suponha que temos uma pgina que seja relativamente esttica, exceto por um atualizador de notcias na parte
inferior do contedo. Com o ESI, podemos fazer o cache do atualizador de notcias de forma independente do resto
da pgina.
public function indexAction()
{
$response = $this->render(MyBundle:MyController:index.html.twig);
$response->setSharedMaxAge(600);
return $response;
}
Nesse exemplo, informamos o tempo de vida para o cache da pgina inicial como dez minutos. Em seguida, vamos incluir o atualizador de notcias no template embutindo uma action. Isso feito pelo helper render (Veja Incorporao
de Controllers para mais detalhes).
Como o contedo embutido vem de outra pgina (ou controller nesse caso), o Symfony2 usa o helper padro render
para configurar as tags ESI:
Twig
{% render ...:news with {}, {standalone: true} %}
PHP
<?php echo $view[actions]->render(...:news, array(), array(standalone => true)) ?>
Definindo standalone como true, voc diz ao Symfony2 que a action deve ser renderizada como uma tag ESI.
Voc pode estar imaginando porque voc iria querer utilizar um helper em vez de escrever a tag ESI voc mesmo. Isso
acontece porque a utilizao de um helper faz a sua aplicao funcionar mesmo se no existir nenhum gateway cache
instalado. Vamos ver como isso funciona.
Quando standalone est false (o padro), o Symfony2 mescla o contedo da pgina includa com a principal antes
de mandar a resposta para o cliente. Mas quando standalone est true, e se o Symfony detectar que ele est falando
com um gateway cache que suporta ESI, ele gera uma tag ESI de incluso. Mas se no houver um gateway cache ou
se ele no suportar ESI, o Symfony2 apenas mesclar o contedo da pgina includa com a principal como se tudo
tivesse sido feito com o standalone definido como false.
Nota: O Symfony2 detecta se um gateway cache suporta ESI por meio de outra especificao da Akamai que
suportada nativamente pelo proxy reverso do Symfony2.
A action embutida agora pode especificar suas prprias regras de cache, de forma totalmente independente da pgina
principal.
2.1. Livro
203
Com o ESI, o cache da pgina completa pode ficar vlido por 600 segundos, mas o cache do componente de notcias
ser vlido apenas nos ltimos 60 segundos.
Um requisito do ESI, no entanto, que a action embutida seja acessvel por uma URL dessa forma o gateway cache
pode acess-la independentemente do restante da pgina. claro que uma action no pode ser acessada pela URL
a menos que exista uma rota que aponte para ela. O Symfony2 trata disso por meio de uma rota e um controller
genricos. Para que a tag ESI de incluso funcione adequadamente, voc precisa definir a rota _internal:
YAML
# app/config/routing.yml
_internal:
resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
prefix:
/_internal
XML
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection->addCollection($loader->import(@FrameworkBundle/Resources/config/routing/internal.x
return $collection;
Dica: Como essa rota permite que todas as actions sejam acessadas por uma URL, talvez voc queira proteg-la
utilizando a funcionalidade de firewall do Symfony2 (restringindo o acesso para a faixa de IP do seu proxy reverso).
Veja a seo Securing by IP do Security Chapter para mais informaes de como fazer isso.
Uma grande vantagem dessa estratgia de cache que voc pode fazer com que sua aplicao seja to dinmica quanto
for necessrio e ao mesmo tempo, acessar a aplicao o mnimo possvel.
Nota: Assim que voc comear a utilizar ESI, lembre-se de sempre usar a diretiva s-maxage em vez de max-age.
Como o navegador somente recebe o recurso agregado, ele no tem cincia dos sub-componentes e assim ele ir
obedecer a diretiva max-age e fazer o cache da pgina inteira. E voc no quer isso.
O helper render suporta duas outras opes teis:
204
Captulo 2. Livro
alt: usada como o atributo alt na tag ESI, que permite que voc especifique uma URL alternativa para ser
usada se o src no for encontrado;
ignore_errors: se for configurado como true, um atributo onerror ser adicionado ao ESI com um valor
continue indicando que, em caso de falha, o gateway cache ir simplesmente remover a tag ESI silenciosamente.
Invalidao do Cache
S tem duas coisas difceis em Cincia da Computao: invalidao de cache e nomear coisas. Phil
Karlton
Voc nunca deveria ter que invalidar dados em cache porque a invalidao j feita nativamente nos modelos de cache
HTTP. Se voc usar validao, por definio voc nunca precisaria invalidar algo; e se voc usar expirao e precisar
invalidar um recurso, isso significa que voc definiu a data de expirao com um valor muito longe.
Nota: Como invalidao um tpico especfico para cada tipo de proxy reverso, se voc no se preocupa com
invalidao, voc pode alternar entre proxys reversos sem alterar nada no cdigo da sua aplicao.
Na verdade, todos os proxys reversos fornecem maneiras de expurgar dados do cache, mas voc deveria evit-los o
mximo possvel. A forma mais padronizada expurgar o cache de uma determinada URL requisitando-a com o
mtodo HTTP especial PURGE.
Aqui vai como voc pode configurar o proxy reverso do Symfony2 para suportar o mtodo HTTP PURGE:
// app/AppCache.php
class AppCache extends Cache
{
protected function invalidate(Request $request)
{
if (PURGE !== $request->getMethod()) {
return parent::invalidate($request);
}
$response = new Response();
if (!$this->getStore()->purge($request->getUri())) {
$response->setStatusCode(404, Not purged);
} else {
$response->setStatusCode(200, Purged);
}
return $response;
}
}
Cuidado: Voc tem que proteger o mtodo HTTP PURGE de alguma forma para evitar que pessoas aleatrias
expurguem seus dados cacheados.
Sumrio
O Symfony2 foi desenhado para seguir as regras j testadas: HTTP. O cache no exceo. Dominar o sistema de
cache do Symfony2 significa se familiarizar com os modelos de cache HTTP e utiliz-los de forma efetiva. Para isso,
em vez de depender apenas da documentao do Symfony2 e exemplos de cdigo, voc deveria buscar mais contedo
relacionado com o cache HTTP e caches gateway como o Varnish.
2.1. Livro
205
2.1.13 Tradues
O termo internacionalizao se refere ao processo de abstrair strings e outras peas com localidades especficas para
fora de sua aplicao e dentro de uma camada onde eles podem ser traduzidos e convertidos baseados na localizao
do usurio (em outras palavras, idioma e pis). Para o texto, isso significa englobar cada um com uma funo capaz
de traduzir o texto (ou messagem) dentro do idioma do usurio:
// text will *always* print out in English
echo Hello World;
// text can be translated into the end-users language or default to English
echo $translator->trans(Hello World);
Nota: O termo localidade se refere rigorosamente ao idioma e pas do usurio. Isto pode ser qualquer string que
sua aplicao usa ento para gerenciar tradues e outras diferenas de formato (ex: formato de moeda). Ns recomendamos o cdigo de linguagem ISO639-1 , um underscore (_), ento o cdigo de pas ISO3166 (ex: fr_FR para
Francs/Frana).
Nesse captulo, ns iremos aprender como preparar uma aplicao para suportar mltiplas localidades e ento como
criar tradues para localidade e ento como criar tradues para mltiplas localidades. No geral, o processo tem
vrios passos comuns:
1. Abilitar e configurar o componente Translation do Symfony;
2. Abstrair strings (em outras palavras, messagens) por englob-las em chamadas para o Translator;
3. Criar translation resources para cada localidade suportada que traduza cada mensagem na aplicao;
4. Determinar, definir e gerenciar a localidade do usurio para o pedido e opcionalmente em toda a sesso do
usurio.
Configurao
Tradues so suportadas por um Translator service que usa o localidadedo usurio para observar e retornar
mensagens traduzidas. Antes de usar isto, abilite o Translator na sua configurao:
YAML
# app/config/config.yml
framework:
translator: { fallback: en }
XML
<!-- app/config/config.xml -->
<framework:config>
<framework:translator fallback="en" />
</framework:config>
PHP
206
Captulo 2. Livro
// app/config/config.php
$container->loadFromExtension(framework, array(
translator => array(fallback => en),
));
A opo fallback define a localidade alternativa quando uma traduo no existe no localidadedo usurio.
Dica: Quando a traduo no existe para uma localidade, o tradutor primeiro tenta encontrar a traduo para o
idioma (fr se o localidade fr_FR por exemplo). Se isto tambm falhar, procura uma traduo usando a localidade
alternativa.
A localidade usada em tradues a que est armazenada no pedido. Isto tipicamente definido atravs do atributo
_locale em suas rotas (veja A localidade e a URL).
Traduo bsica
Traduo do texto feita done atravs do servio translator (Translator). Para traduzir um bloco de texto
(chamado de messagem), use o mtodo trans(). Suponhamos, por exemplo, que estamos traduzindo uma simples
mensagem de dentro do controller:
public function indexAction()
{
$t = $this->get(translator)->trans(Symfony2 is great);
return new Response($t);
}
Quando esse cdigo executado, Symfony2 ir tentar traduzir a mensagem Symfony2 is great baseada na
localidade do usurio. Para isto funcionar, ns precisamos informar o Symfony2 como traduzir a mensagem
por um translation resource, que uma coleo de tradues de mensagens para um localidade especificada. Esse
dicionrio de tradues pode ser criado em diferentes formatos variados, sendo XLIFF o formato recomendado:
XML
<!-- messages.fr.xliff -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>Symfony2 is great</source>
<target>Jaime Symfony2</target>
</trans-unit>
</body>
</file>
</xliff>
PHP
// messages.fr.php
return array(
Symfony2 is great => J\aime Symfony2,
);
YAML
2.1. Livro
207
# messages.fr.yml
Symfony2 is great: Jaime Symfony2
Agora, se o idioma do localidade do usurio Francs (ex: fr_FR ou fr_BE), a mensagem ir ser traduzida para
Jaime Symfony2.
O processo de traduo
Entretanto criar uma traduo para este string impossvel visto que o tradutor ir tentar achar a mensagem exata,
incluindo pores da varivel (ex: Hello Ryan ou Hello Fabien). Ao invs escrever uma traduo para toda
interao possvel da mesma varivel $name , podemos substituir a varivel com um espao reservado:
public function indexAction($name)
{
$t = $this->get(translator)->trans(Hello %name%, array(%name% => $name));
new Response($t);
}
Symfony2 ir procurar uma traduo da mensagem pura (Hello %name%) e ento substitui o espao reservado com
os valores deles. Criar uma traduo exatamente como foi feito anteriormente:
XML
<!-- messages.fr.xliff -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>Hello %name%</source>
208
Captulo 2. Livro
<target>Bonjour %name%</target>
</trans-unit>
</body>
</file>
</xliff>
PHP
// messages.fr.php
return array(
Hello %name% => Bonjour %name%,
);
YAML
# messages.fr.yml
Hello %name%: Hello %name%
Nota: Os espaos reservados podem suportar qualquer outro forma j que a mensagem inteira reconstruda usando
a funo PHP strtr function. Entretanto, a notao %var% requerida quando traduzir em templates Twig, e no geral
uma conveno sensata a seguit.
Como podemos ver, criar uma traduo um processo de dois passos:
1. Abstrair a mensagem que precisa ser traduzida por processamento atravs do Translator.
2. Criar uma traduo para a mensagem em cada localidade que voc escolha dar suporte.
O segundo passo feito mediante criar catlogos de mensagem que definem as tradues para qualquer nmero de
localidades diferentes.
Catlogo de Mensagens
Quando uma mensagem traduzida, Symfony2 compila um catlogo de mensagem para a localidade do usurio e
investiga por uma traduo da mensagem. Um catlogo de mensagens como um dicionrio de tradues para uma
localidade especfica. Por exemplo, o catlogo para a localidadefr_FR poderia conter a seguinte traduo:
Symfony2 is Great => Jaime Symfony2
responsabilidade do desenvolvedor (ou tradutor) de uma aplicao internacionalizada criar essas tradues. Tradues so armazenadas no sistema de arquivos e descoberta pelo Symfony, graas a algumas convenes.
Dica: Cada vez que voc criar um novo translation resource (ou instalar um pacote que inclua o translation resource),
tenha certeza de limpar o cache ento aquele Symfony poder detectar o novo translation resource:
php app/console cache:clear
O Symfony2 procura por arquivos de mensagem (em outras palavras, tradues) nas seguintes localizaes:
o diretrio <kernel root directory>/Resources/translations;
o diretrio <kernel root directory>/Resources/<bundle name>/translations;
o diretrio Resources/translations/ do bundle.
2.1. Livro
209
Os locais so apresentados com a prioridade mais alta em primeiro lugar. Isso significa que voc pode sobrescrever as
mensagens de traduo de um bundle em qualquer um dos 2 diretrios no topo.
O mecanismo de substituio funciona em um nvel chave: apenas as chaves sobrescritas precisam ser listadas em um
arquivo de mensagem de maior prioridade. Quando a chave no encontrada em um arquivo de mensagem, o tradutor
automaticamente alternar para os arquivos de mensagem menos prioritrios.
O nome de arquivo das tradues tambm importante, j que Symfony2 usa uma conveno para determinar
detalhes sobre as tradues. Cada arquivo de messagem deve ser nomeado de acordo com o seguinte padro:
domnio.localidade.carregador:
domnio: Uma forma opcional de organizar mensagens em grupos (ex: admin, navigation ou o padro
messages) - veja Usando Domnios de Mensagem;
localidade: A localidade para a qual a traduo feita (ex: en_GB, en, etc);
carregador: Como Symfony2 deveria carregar e analisar o arquivo (ex: xliff, php or yml).
O carregador poder ser o nome de qualquer carregador registrado. Por padro, Symfony providencia os seguintes
carregadores:
xliff: arquivo XLIFF;
php: arquivo PHP;
yml: arquivo YAML.
A escolha de qual carregador inteiramente tua e uma questo de gosto.
Nota: Voc tambm pode armazenar tradues em um banco de dados, ou outro armazenamento ao providenciar uma
classe personalizada implementando a interface LoaderInterface. Veja Custom Translation Loaders
abaixo para aprender como registrar carregadores personalizados.
Criando tradues
Cada arquivo consiste de uma srie de pares de traduo de id para um dado domnio e locale. A id o identificador
para a traduo individual, e pode ser a mensagem da localidade principal (ex: Symfony is great) de sua aplicap
ou um identificador nico (ex: symfony2.great - veja a barra lateral abaixo):
XML
<!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xliff -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>Symfony2 is great</source>
<target>Jaime Symfony2</target>
</trans-unit>
<trans-unit id="2">
<source>symfony2.great</source>
<target>Jaime Symfony2</target>
</trans-unit>
</body>
</file>
</xliff>
PHP
210
Captulo 2. Livro
// src/Acme/DemoBundle/Resources/translations/messages.fr.php
return array(
Symfony2 is great => J\aime Symfony2,
symfony2.great
=> J\aime Symfony2,
);
YAML
# src/Acme/DemoBundle/Resources/translations/messages.fr.yml
Symfony2 is great: Jaime Symfony2
symfony2.great:
Jaime Symfony2
Symfony2 ir descobrir esses arquivos e us-los quando ou traduzir Symfony2 is great ou symfony2.great no
localidade do idioma Francs (ex: fr_FR ou fr_BE).
2.1. Livro
211
No primeiro mtodo, mensagens so escritas no idioma do localidade padro (Ingls neste caso). Aquela mensagem ento usada como id quando criar tradues.
No segundo mtodo, mensagens so realmente palavras-chave que contm a idia da mensagem. A mensagem
de palavras-chave ento usada como o id de qualquer traduo. Neste caso, tradues devem ser feitas para
o locale padro (em outras palavras, traduzir symfony2.great para Symfony2 is great).
O segundo mtodo prtico porque a chave da mensagem no precisar ser mudada em cada arquivo de traduo
se decidirmos que a mensagem realmente deveria ler Symfony2 is really great no localidade padro.
A escolha de qual mtodo usar inteiramente sua, mas o formato de palavra-chave frequentemente recomendada.
Adicionalmente, os formatos de arquivo php e yaml suportam ids encaixadas para que voc evite repeties se
voc usar palavras-chave ao invs do texto real para suas ids:
YAML
symfony2:
is:
great: Symfony2 is great
amazing: Symfony2 is amazing
has:
bundles: Symfony2 has bundles
user:
login: Login
PHP
return array(
symfony2 => array(
is => array(
great => Symfony2 is great,
amazing => Symfony2 is amazing,
),
has => array(
bundles => Symfony2 has bundles,
),
),
user => array(
login => Login,
),
);
Os nveis mltiplos so achatados em id unitria / pares de traduo ao adicionar um ponto (.) entre cada nvel,
portanto os exemplos acima so equivalentes ao seguinte:
YAML
symfony2.is.great: Symfony2 is great
symfony2.is.amazing: Symfony2 is amazing
symfony2.has.bundles: Symfony2 has bundles
user.login: Login
PHP
212
return array(
symfony2.is.great => Symfony2 is great,
symfony2.is.amazing => Symfony2 is amazing,
symfony2.has.bundles => Symfony2 has bundles,
user.login => Login,
);
Captulo 2. Livro
Tambm possvel armazenar a localidade na sesso em vez do pedido. Se voc fizer isso, cada pedido posterior ter
esta localidade.
$this->get(session)->set(_locale, en_US);
Veja a seo A localidade e a URL abaixo sobre como setar a localidade atravs de roteamento.
Localidade padro e alternativa
XML
<!-- app/config/config.xml -->
<framework:config>
<framework:default-locale>en</framework:default-locale>
</framework:config>
2.1. Livro
213
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(
default_locale => en,
));
Novo na verso 2.1: O parmetro default_locale foi definido debaixo da chave session originalmente, entretanto,
com o 2.1 isto foi movido. Foi movido porque a localidade agora definida no pedido ao invs da sesso.
A localidade e a URL
Uma vez que voc pode armazenar a localidade do usurio na sesso, pode ser tentador usar a mesma
URL para mostrar o recurso em muitos idiomas diferentes baseados na localidade do usurio.Por exemplo,
http://www.example.com/contact poderia mostrar contedo em Ingls para um usurio e Francs para
outro usurio. Infelizmente, isso viola uma regra fundamental da Web: que um URL particular retorne o mesmo recurso independente do usurio. Para complicar ainda o problema, qual verso do contedo deveria ser indexado pelas
ferramentas de pesquisa ?
Uma melhor poltica incluir a localidade na URL. Isso totalmente suportado pelo sistema de roteamnto usando o
parmetro especial _locale:
YAML
contact:
pattern:
/{_locale}/contact
defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
requirements:
_locale: en|fr|de
XML
<route id="contact" pattern="/{_locale}/contact">
<default key="_controller">AcmeDemoBundle:Contact:index</default>
<default key="_locale">en</default>
<requirement key="_locale">en|fr|de</requirement>
</route>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(contact, new Route(/{_locale}/contact, array(
_controller => AcmeDemoBundle:Contact:index,
_locale
=> en,
), array(
_locale
=> en|fr|de
)));
return $collection;
Quando usar o parmetro especial _locale numa rota, a localidade encontrada ser automaticamente estabelecida na
sesso do usurio. Em outras palavras, se um usurio visita a URI /fr/contact, a localidadefr ser automaticamente estabelecida como a localidade para a sesso do usurio.
Voc pode agora usar a localidade do usurio para criar rotas para outras pginas traduzidas na sua aplicao.
214
Captulo 2. Livro
Pluralizao
Pluralizao de mensagem um tpico difcil j que as regras podem ser bem complexas. Por conveno, aqui est a
representao matemtica das regrad de pluralizao Russa:
(($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) &&
Como voc viu, em Russo, voc pode ter trs formas diferentes de plural, cada uma com index de 0, 1 ou 2. Para cada
forma, o plural diferente, e ento a traduo tambm diferente.
Quando uma traduo tem formas diferentes devido pluralizao, voc pode providenciar todas as formas como
string separadas por barra vertical (|):
There is one apple|There are %count% apples
O segundo argumento (10 neste exemplo), o nmero de objetos sendo descritos e usado para determinar qual
traduo usar e tambm para preencher o espao reservado %count%.
Baseado em certo nmero, o tradutor escolhe a forma correta do plural. Em Ingls, muitas palavras tem uma forma
singular quando existe exatamente um objeto e uma forma no plural para todos os outros nmeros (0, 2, 3...). Ento,
se count 1, o tradutor usar a primeira string (There is one apple) como traduo. Seno ir usar There
are %count% apples.
Aqui est a traduo Francesa:
Il y a %count% pomme|Il y a %count% pommes
Mesmo se a string parecer similar ( feita de duas substrings separadas por barra vertical), as regras Francesas so
diferentes: a primeira forma (sem plural) usada quando count is 0 or 1. Ento, o tradutor ir automaticamente usar
a primeira string (Il y a %count% pomme) quando count 0 ou 1.
Cada localidade tem sua prpria lista de regras, com algumas tendo tanto quanto seis formas diferentes de plural com
regras complexas por trs de quais nmeros de mapas de quais formas no plural. As regras so bem simples para
Ingls e Francs, mas para Russo, voc poderia querer um palpite para conhecer quais regras combinam com qual
string. Para ajudar tradutores, voc pode opcionalmente atribuir uma tag a cada string:
one: There is one apple|some: There are %count% apples
none_or_one: Il y a %count% pomme|some: Il y a %count% pommes
As tags so realmente as nicas pistas para tradutores e no afetam a lgica usada para determinar qual forma plural
usar. As tags podem ser qualquer string descritiva que termine com dois pontos (:). As tags traduzidas tambm no
so necessariamente a mesma que as da mensagem original.
Pluralizao de Intervalo Explcito
A maneira mais fcil de pluralizar uma mensagem deixar o Symfony2 usar lgica interna para escolher qual string
usar, baseando em um nmero fornecido. s vezes, voc ir precisar de mais controle ou querer uma traduo diferente
para casos especficos (para 0, ou quando o contador negativo, por exemplo). Para certos casos, voc pode usar
intervalos matemticos explcitos:
2.1. Livro
215
{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are ma
Os intervalos seguem a notao ISO 31-11. A string acima especifica quatro intervalos diferentes: exatamente 0,
exatamente 1, 2-19, e 20 e mais altos.
Voc tambm pode misturar regras matemticas explcitas e regras padro. Nesse caso, se o contador no combinar
com um intervalo especfico, as regras padro, tero efeito aps remover as regras explcitas:
{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% a
Por exemplo, para 1 ma, a regra padro There is one apple ser usada. Para 2-19 apples, a segunda regra
padro There are %count% apples ser selecionada.
Uma classe Interval pode representar um conjunto finito de nmeros:
{1,2,3,4}
O delimitador esquerdo pode ser [ (inclusivo) or ] (exclusivo). O delimitador direito pode ser [ (exclusivo) or ]
(inclusivo). Alm de nmeros, voc pode usar -Inf e +Inf para infinito.
Tradues em Templates
A maior parte do tempo, tradues ocorrem em templates. Symfony2 providencia suporte nativo para ambos os
templates PHP e Twig.
Templates Twig
Symfony2 providencia tags Twig especializadas (trans e transchoice) para ajudar com traduo de mensagem
de blocos estticos de texto:
{% trans %}Hello %name%{% endtrans %}
{% transchoice count %}
{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
{% endtranschoice %}
A tag transchoice automaticamente obtm a varivel %count% do contexto atual e a passa para o tradutor. Esse
mecanismo s funciona quando voc usa um espao reservado seguindo o padro %var%.
Dica: Se voc precisa usar o caractere de percentual (%) em uma string, escape dela ao dobr-la: {% trans
%}Percent: %percent%%%{% endtrans %}
Voc tambm pode especificar o domnio da mensagem e passar algumas variveis adicionais:
{% trans with {%name%: Fabien} from "app" %}Hello %name%{% endtrans %}
{% trans with {%name%: Fabien} from "app" into "fr" %}Hello %name%{% endtrans %}
{% transchoice count with {%name%: Fabien} from "app" %}
{0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples
{% endtranschoice %}
216
Captulo 2. Livro
Os filtros trans e transchoice podem ser usados para traduzir textos de variveis e expresses complexas:
{{ message | trans }}
{{ message | transchoice(5) }}
{{ message | trans({%name%: Fabien}, "app") }}
{{ message | transchoice(5, {%name%: Fabien}, app) }}
Dica: Usando as tags de traduo ou filtros que tenham o mesmo efeito, mas com uma diferena sutil: sada para
escape automtico s aplicada para variveis traduzidas por utilizao de filtro. Em outras palavras, se voc precisar
estar certo que sua varivel traduzida no uma sada para escape, voc precisa aplicar o filtro bruto aps o filtro de
traduo.
{# text translated between tags is never escaped #}
{% trans %}
<h3>foo</h3>
{% endtrans %}
{% set message = <h3>foo</h3> %}
{# a variable translated via a filter is escaped by default #}
{{ message | trans | raw }}
{# but static strings are never escaped #}
{{ <h3>foo</h3> | trans }}
Novo na verso 2.1: Agora voc pode definir o domnio de traduo para um template Twig inteiro com uma nica
tag:
{% trans_default_domain "app" %}
Note que isso somente influencia o template atual, e no qualquer template includo (para evitar efeitos colaterais).
Templates PHP
2.1. Livro
217
messages,
fr_FR,
);
$this->get(translator)->trans(
{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples,
10,
array(%count% => 10),
messages,
fr_FR,
);
Captulo 2. Livro
funcionalidade especfica que ele fornece. Voc no precisa fazer nada de especial para construir um servio: basta
escrever uma classe PHP com algum cdigo que realiza uma tarefa especfica. Parabns, voc acabou de criar um
servio!
Nota: Como regra geral, um objeto PHP um servio se ele usado globalmente em sua aplicao. Um nico
servio Mailer utilizado globalmente para enviar mensagens de e-mail, enquanto os muitos objetos Message que
ele entrega no so servios. Do mesmo modo, um objeto Product no um servio, mas um objeto que persiste os
objetos Product para um banco de dados um servio.
Ento, porque ele especial? A vantagem de pensar em servios que voc comea a pensar em separar cada
pedao de funcionalidade de sua aplicao em uma srie de servios. Uma vez que cada servio realiza apenas um
trabalho, voc pode facilmente acessar cada servio e usar a sua funcionalidade, sempre que voc precisar. Cada
servio pode tambm ser mais facilmente testado e configurado j que ele est separado das outras funcionalidades
em sua aplicao. Esta idia chamada de arquitetura orientada servios e no exclusiva do Symfony2 ou at
mesmo do PHP. Estruturar a sua aplicao em torno de um conjunto de classes de servios independentes uma das
melhores prticas de orientao objeto bem conhecida e confivel. Essas habilidades so a chave para ser um bom
desenvolvedor em praticamente qualquer linguagem.
O que um Service Container?
Um Container de Servio (ou container de injeo de dependncia) simplesmente um objeto PHP que gerencia a
instanciao de servios (ex. objetos). Por exemplo, imagine que temos uma classe PHP simples que envia mensagens
de e-mail. Sem um container de servio, precisamos criar manualmente o objeto sempre que precisarmos dele:
use Acme\HelloBundle\Mailer;
$mailer = new Mailer(sendmail);
$mailer->send(ryan@foobar.net, ... );
Isso bastante fcil. A classe imaginria Mailer nos permite configurar o mtodo utilizado para entregar as mensagens de e-mail (por exemplo: sendmail, smtp, etc). Mas, e se quisssemos usar o servio de mailer em outro lugar?
Ns certamente no desejamos repetir a configurao do mailer sempre que ns precisamos usar o objeto Mailer.
E se precisarmos mudar o transport de sendmail para smtp em toda a aplicao? Ns precisaremos localizar
cada lugar em que adicionamos um servio Mailer e alter-lo.
Criando/Configurando Servios no Container
Uma resposta melhor deixar o container de servio criar o objeto Mailer para voc. Para que isso funcione,
preciso ensinar o container como criar o servio Mailer. Isto feito atravs de configurao, que pode ser
especificada em YAML, XML ou PHP:
YAML
# app/config/config.yml
services:
my_mailer:
class:
Acme\HelloBundle\Mailer
arguments:
[sendmail]
XML
<!-- app/config/config.xml -->
<services>
<service id="my_mailer" class="Acme\HelloBundle\Mailer">
<argument>sendmail</argument>
2.1. Livro
219
</service>
</services>
PHP
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$container->setDefinition(my_mailer, new Definition(
Acme\HelloBundle\Mailer,
array(sendmail)
));
Nota: Quando o Symfony2 inicializado, ele constri o container de servio usando a configurao da aplicao (por padro (app/config/config.yml). O arquivo exato que carregado ditado pelo mtodo
AppKernel::registerContainerConfiguration() , que carrega um arquivo de configurao do ambiente especfico (por exemplo config_dev.yml para o ambiente dev ou config_prod.yml para o prod).
Uma instncia do objeto Acme\HelloBundle\Mailer est agora disponvel atravs do container de servio. O
container est disponvel em qualquer controlador tradicional do Symfony2 onde voc pode acessar os servios do
container atravs do mtodo de atalho get():
class HelloController extends Controller
{
// ...
public function sendEmailAction()
{
// ...
$mailer = $this->get(my_mailer);
$mailer->send(ryan@foobar.net, ... );
}
}
Quando requisitamos o servio my_mailer a partir do container, o container constri o objeto e o retorna. Esta
outra vantagem importante do uso do container de servio. Ou seja, um servio nunca construdo at que ele seja
necessrio. Se voc definir um servio e nunca us-lo em um pedido, o servio nunca ser criado. Isso economiza
memria e aumenta a velocidade de sua aplicao. Isto tambm significa que no h nenhuma perda ou apenas uma
perda insignificante de desempenho ao definir muitos servios. Servios que nunca so usados, nunca so construdos.
Como um bnus adicional, o servio Mailer criado apenas uma vez e a mesma instncia retornada cada vez que
voc requisitar o servio. Isso quase sempre o comportamento que voc precisa ( mais flexvel e poderoso), mas
vamos aprender, mais tarde, como voc pode configurar um servio que possui vrias instncias.
Parmetros do Servio
A criao de novos servios (objetos, por exemplo) atravs do container bastante simples. Os parmetros tornam a
definio dos servios mais organizada e flexvel:
YAML
# app/config/config.yml
parameters:
my_mailer.class:
my_mailer.transport:
Acme\HelloBundle\Mailer
sendmail
services:
220
Captulo 2. Livro
my_mailer:
class:
arguments:
%my_mailer.class%
[%my_mailer.transport%]
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
<parameter key="my_mailer.transport">sendmail</parameter>
</parameters>
<services>
<service id="my_mailer" class="%my_mailer.class%">
<argument>%my_mailer.transport%</argument>
</service>
</services>
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 diferena est apenas em como definimos o servio. Quando
as strings my_mailer.class e my_mailer.transport esto envolvidas por sinais de porcentagem (%), o
container sabe que deve procurar por parmetros com esses nomes. Quando o container construdo, ele procura o
valor de cada parmetro e utiliza-o na definio do servio.
Nota: O sinal de porcentagem dentro de um parmetro ou argumento, como parte da string, deve ser escapado com
um outro sinal de porcentagem:
<argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument>
A finalidade dos parmetros alimentar informao para os servios. Naturalmente, no h nada de errado em definir
o servio sem o uso de quaisquer parmetros. Os parmetros, no entanto, possuem vrias vantagens:
separao e organizao de todas as opes dos servios sob uma nica chave parameters;
os valores do parmetro podem ser usados em mltiplas definies de servios;
ao criar um servio em um bundle (vamos mostrar isso em breve), o uso de parmetros permite que o servio
seja facilmente customizado em sua aplicao.
A escolha de usar ou no os parmetros com voc. Bundles de terceiros de alta qualidade sempre utilizaro parmetros, pois, eles tornam mais configurvel o servio armazenado no container. Para os servios em sua aplicao,
entretanto, voc pode no precisar da flexibilidade dos parmetros.
2.1. Livro
221
Parmetros Array
Os parmetros no precisam ser strings, eles tambm podem ser arrays. Para o formato XML , voc precisar usar o
atributo type=collection em todos os parmetros que so arrays.
YAML
# app/config/config.yml
parameters:
my_mailer.gateways:
- mail1
- mail2
- mail3
my_multilang.language_fallback:
en:
- en
- fr
fr:
- fr
- en
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="my_mailer.gateways" type="collection">
<parameter>mail1</parameter>
<parameter>mail2</parameter>
<parameter>mail3</parameter>
</parameter>
<parameter key="my_multilang.language_fallback" type="collection">
<parameter key="en" type="collection">
<parameter>en</parameter>
<parameter>fr</parameter>
</parameter>
<parameter key="fr" type="collection">
<parameter>fr</parameter>
<parameter>en</parameter>
</parameter>
</parameter>
</parameters>
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),
));
222
Captulo 2. Livro
Symfony2 to flexvel que a configurao pode ser carregada de qualquer lugar (por exemplo, de um banco de dados
ou at mesmo atravs de um web service externo).
O service container construdo usando um nico recurso de configurao (por padro
app/config/config.yml). Todas as outras configuraes de servio (incluindo o ncleo do Symfony2 e
configuraes de bundles de terceiros) devem ser importadas dentro desse arquivo de uma forma ou de outra. Isso lhe
d absoluta flexibilidade sobre os servios em sua aplicao.
Configuraes de servios externos podem ser importadas de duas maneiras distintas. Primeiro, vamos falar sobre o
mtodo que voc vai usar mais frequentemente na sua aplicao: a diretiva imports. Na seo seguinte, vamos falar
sobre o segundo mtodo, que mais flexvel e preferido para a importao de configurao de servios dos bundles de
terceiros.
Importando configurao com imports
At agora, adicionamos a nossa definio do container de servio my_mailer diretamente no arquivo de configurao
da aplicao (por exemplo: app/config/config.yml). Naturalmente, j que a prpria classe Mailer reside
dentro do AcmeHelloBundle, faz mais sentido colocar a definio do container my_mailer dentro do bundle
tambm.
Primeiro, mova a definio do container my_mailer dentro de um novo arquivo container de recurso dentro do
AcmeHelloBundle. Se os diretrios Resources ou Resources/config no existirem, adicione eles.
YAML
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
my_mailer.class:
Acme\HelloBundle\Mailer
my_mailer.transport: sendmail
services:
my_mailer:
class:
arguments:
%my_mailer.class%
[%my_mailer.transport%]
XML
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<parameters>
<parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
<parameter key="my_mailer.transport">sendmail</parameter>
</parameters>
<services>
<service id="my_mailer" class="%my_mailer.class%">
<argument>%my_mailer.transport%</argument>
</service>
</services>
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(
2.1. Livro
223
%my_mailer.class%,
array(%my_mailer.transport%)
));
A definio em si no mudou, apenas a sua localizao. Claro que o container de servio no sabe sobre o novo
arquivo de recurso. Felizmente, ns podemos facilmente importar o arquivo de recurso usando a chave imports na
configurao da aplicao.
YAML
# app/config/config.yml
imports:
- { resource: @AcmeHelloBundle/Resources/config/services.yml }
XML
<!-- app/config/config.xml -->
<imports>
<import resource="@AcmeHelloBundle/Resources/config/services.xml"/>
</imports>
PHP
// app/config/config.php
$this->import(@AcmeHelloBundle/Resources/config/services.php);
A diretiva imports permite sua aplicao incluir os recursos de configurao do container de servio de qualquer outra localizao (mais comumente, de bundles). A localizao do resource, para arquivos, o caminho
absoluto para o arquivo de recurso. A sintaxe especial @AcmeHello resolve o caminho do diretrio do bundle
AcmeHelloBundle. Isso ajuda voc a especificar o caminho para o recurso sem preocupar-se mais tarde caso
mover o AcmeHelloBundle para um diretrio diferente.
Importando Configurao atravs de Extenses do Container
Ao desenvolver no Symfony2, voc vai mais comumente usar a diretiva imports para importar a configurao do
container dos bundles que voc criou especificamente para a sua aplicao. As configuraes de container de bundles
de terceiros, incluindo os servios do ncleo do Symfony2, so geralmente carregadas usando um outro mtodo que
mais flexvel e fcil de configurar em sua aplicao.
Veja como ele funciona. Internamente, cada bundle define os seus servios de forma semelhante a que vimos at agora.
Ou seja, um bundle utiliza um ou mais arquivos de configuraes de recurso (normalmente XML) para especificar os
parmetros e servios para aquele bundle. No entanto, em vez de importar cada um desses recursos diretamente a
partir da sua configurao da aplicao usando a diretiva imports, voc pode simplesmente invocar uma extenso
do container de servio dentro do bundle que faz o trabalho para voc. Uma extenso do container de servio uma
classe PHP criada pelo autor do bundle para realizar duas coisas:
importar todos os recursos de container de servio necessrios para configurar os servios para o bundle;
fornecer configurao simples e semntica para que o bundle possa ser configurado sem interagir com os parmetros da configurao do container de servio.
Em outras palavras, uma extenso do container de servio configura os servios para um bundle em seu nome. E,
como veremos em breve, a extenso fornece uma interface sensvel e de alto nvel para configurar o bundle.
Considere o FrameworkBundle - o bundle do ncleo do framework Symfony2 - como um exemplo. A presena do cdigo a seguir na configurao da sua aplicao invoca a extenso do container de servio dentro do
FrameworkBundle:
YAML
224
Captulo 2. Livro
# app/config/config.yml
framework:
secret:
xxxxxxxxxx
form:
true
csrf_protection: true
router:
{ resource: "%kernel.root_dir%/config/routing.yml" }
# ...
XML
<!-- app/config/config.xml -->
<framework:config secret="xxxxxxxxxx">
<framework:form />
<framework:csrf-protection />
<framework:router resource="%kernel.root_dir%/config/routing.xml" />
<!-- ... -->
</framework>
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 configurao, o container procura uma extenso que pode lidar com a diretiva de
configurao framework. A extenso em questo, que reside no FrameworkBundle, chamada e a configurao
do servio para o FrameworkBundle carregada. Se voc remover totalmente a chave framework de seu arquivo
de configurao da aplicao, os servios do ncleo do Symfony2 no sero carregados. O ponto que voc est no
controle: o framework Symfony2 no contm qualquer tipo de magia ou realiza quaisquer aes que voc no tenha
controle.
Claro que voc pode fazer muito mais do que simplesmente ativar a extenso do container de servio do
FrameworkBundle. Cada extenso permite que voc facilmente personalize o bundle, sem se preocupar com a
forma como os servios internos so definidos.
Neste caso, a extenso permite que voc personalize as configuraes error_handler, csrf_protection,
router e muito mais. Internamente, o FrameworkBundle usa as opes especificadas aqui para definir e configurar os servios especficos para ele. O bundle se encarrega de criar todos os parameters e services necessrios
para o container de servio, permitindo ainda que grande parte da configurao seja facilmente personalizada. Como
um bnus adicional, a maioria das extenses do container de servio tambm so inteligentes o suficiente para executar
a validao - notificando as opes que esto faltando ou que esto com o tipo de dados incorreto.
Ao instalar ou configurar um bundle, consulte a documentao do bundle para verificar como os servios para o
bundle devem ser instalados e configurados. As opes disponveis para os bundles do ncleo podem ser encontradas
no Reference Guide.
Nota: Nativamente, o container de servio somente reconhece as diretivas parameters, services e imports.
Quaisquer outras diretivas so manipuladas pela extenso do container de servio.
Se voc deseja expor configurao amigvel em seus prprios bundles, leia o Como expor uma Configurao Semntica para um Bundle do cookbook.
2.1. Livro
225
Sem usar o container de servio, 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 cdigo 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
servio nos d uma opo muito mais atraente:
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]
XML
226
Captulo 2. Livro
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\NewsletterMana
$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 servio chamado my_mailer
e passar esse objeto para o construtor do NewsletterManager. Neste caso, entretanto, o servio especificado
my_mailer deve existir. Caso ele no existir, ser gerada uma exceo. Voc pode marcar suas dependncias como
opcionais - o que ser discutido na prxima seo.
O uso de referncias uma ferramenta muito poderosa que lhe permite criar classes de servios independentes com
dependncias bem definidas. Neste exemplo, o servio newsletter_manager precisa do servio my_mailer
para funcionar. Quando voc define essa dependncia no container de servio, o container cuida de todo o trabalho de
instanciar os objetos.
Dependncias Opcionais: Injeo do Setter
Injetando dependncias no construtor dessa maneira uma excelente forma de assegurar que a dependncia estar
disponvel para o uso. No entanto, se voc possuir dependncias opcionais para uma classe, a injeo do setter pode
ser uma opo melhor. Isto significa injetar a dependncia usando uma chamada de mtodo ao invs do construtor. A
classe ficaria assim:
namespace Acme\HelloBundle\Newsletter;
use Acme\HelloBundle\Mailer;
class NewsletterManager
{
protected $mailer;
public function setMailer(Mailer $mailer)
{
2.1. Livro
227
$this->mailer = $mailer;
}
// ...
}
Para injetar a dependncia pelo mtodo setter somente necessria uma mudana da sintaxe:
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 ] ]
XML
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\NewsletterMana
$container->setDefinition(my_mailer, ... );
$container->setDefinition(newsletter_manager, new Definition(
%newsletter_manager.class%
))->addMethodCall(setMailer, array(
new Reference(my_mailer)
));
Nota: As abordagens apresentadas nesta seo so chamadas de injeo de construtor e injeo de setter. O
228
Captulo 2. Livro
XML
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<services>
<service id="my_mailer" ... >
<!-- ... -->
</service>
<service id="newsletter_manager" class="%newsletter_manager.class%">
<argument type="service" id="my_mailer" on-invalid="ignore" />
</service>
</services>
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\NewsletterMana
$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 servio que a dependncia opcional. Naturalmente, o
NewsletterManager tambm deve ser escrito para permitir uma dependncia opcional:
public function __construct(Mailer $mailer = null)
{
// ...
}
2.1. Livro
229
No Symfony2, voc vai usar constantemente os servios fornecidos pelo ncleo do Symfony ou outros bundles de
terceiros para executar tarefas como renderizao de templates (templating), envio de e-mails (mailer), ou
acessar informaes sobre o pedido(request).
Podemos levar isto um passo adiante, usando esses servios dentro dos servios que voc criou para a sua aplicao. Vamos modificar o NewsletterManager para utilizar o servio de mailer real do Symfony2 (no lugar
do my_mailer). Vamos tambm passar o servio de templating engine para o NewsletterManager ento ele
poder gerar o contedo de e-mail atravs 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;
}
// ...
}
XML
<service id="newsletter_manager" class="%newsletter_manager.class%">
<argument type="service" id="mailer"/>
<argument type="service" id="templating"/>
</service>
230
Captulo 2. Livro
PHP
$container->setDefinition(newsletter_manager, new Definition(
%newsletter_manager.class%,
array(
new Reference(mailer),
new Reference(templating)
)
));
O servio newsletter_manager agora tem acesso aos servios do ncleo mailer and templating. Esta
uma forma comum de criar servios especficos para sua aplicao que aproveitam o poder de diferentes servios
dentro do framework.
Dica: Certifique-se de que a entrada swiftmailer aparece na configurao da sua aplicao. Como mencionamos
em Importando Configurao atravs de Extenses do Container, a chave swiftmailer invoca a extenso de
servio do SwiftmailerBundle, que registra o servio mailer.
Ao definir os servios, normalmente voc vai desejar poder acessar essas definies dentro do cdigo da sua aplicao.
Esses servios so chamados publicos. Por exemplo, o servio doctrine registrado com o container quando se
utiliza o DoctrineBundle um servio pblico, j que voc pode acess-lo via:
$doctrine = $container->get(doctrine);
No entanto, existem casos de uso em que voc no deseja que um servio seja pblico. Isto comum quando um
servio definido apenas porque poderia ser usado como um argumento para um outro servio.
Nota: Se voc usar um servio privado como um argumento para mais de um servio, isto ir resultar em duas
instncias diferentes sendo usadas, j que, a instanciao do servio privado realizada inline (por exemplo: new
PrivateFooBar()).
Simplesmente falando: Um servio ser privado quando voc no quiser acess-lo diretamente em seu cdigo.
Aqui est um exemplo:
YAML
services:
foo:
class: Acme\HelloBundle\Foo
public: false
XML
2.1. Livro
231
PHP
$definition = new Definition(Acme\HelloBundle\Foo);
$definition->setPublic(false);
$container->setDefinition(foo, $definition);
No entanto, se o servio foi marcado como privado, voc ainda pode adicionar um alias para ele (veja abaixo) para
acessar este servio (atravs do alias).
Nota: Os servios so pblicos por padro.
Definindo Alias
Ao usar bundles do ncleo ou de terceiros dentro de sua aplicao, voc pode desejar usar atalhos para acessar alguns
servios. Isto possvel adicionando alias eles e, alm disso, voc pode at mesmo adicionar alias para servios que
no so pblicos.
YAML
services:
foo:
class: Acme\HelloBundle\Foo
bar:
alias: foo
XML
<service id="foo" class="Acme\HelloBundle\Foo"/>
<service id="bar" alias="foo" />
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 servio foo pedindo pelo servio bar como
segue:
$container->get(bar); // retorna o servio foo
Incluindo Arquivos
Podem haver casos de uso quando necessrio incluir outro arquivo antes do servio em si ser carregado. Para fazer
isso, voc pode usar a diretiva file.
YAML
232
Captulo 2. Livro
services:
foo:
class: Acme\HelloBundle\Foo\Bar
file: %kernel.root_dir%/src/path/to/file/foo.php
XML
<service id="foo" class="Acme\HelloBundle\Foo\Bar">
<file>%kernel.root_dir%/src/path/to/file/foo.php</file>
</service>
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 funo PHP require_once o que significa que seu arquivo ser
includo apenas uma vez por pedido.
Tags (tags)
Da mesma forma que podem ser definidas tags para um post de um blog na web com palavras como Symfony ou
PHP, tambm podem ser definidas tags para os servios configurados em seu container. No container de servio, a
presena de uma tag significa que o servio destina-se ao uso para um fim especfico. Veja o seguinte exemplo:
YAML
services:
foo.twig.extension:
class: Acme\HelloBundle\Extension\FooExtension
tags:
- { name: twig.extension }
XML
<service id="foo.twig.extension" class="Acme\HelloBundle\Extension\FooExtension">
<tag name="twig.extension" />
</service>
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 configurao. Ao definir a tag
twig.extension para este servio, o bundle saber que o servio foo.twig.extension deve ser registrado como uma extenso Twig com o Twig. Em outras palavras, o Twig encontra todos os servios com a tag
twig.extension e automaticamente registra-os como extenses.
Tags, ento, so uma forma de dizer ao Symfony2 ou outros bundles de terceiros que o seu servio deve ser registrado
ou usado de alguma forma especial pelo bundle.
Segue abaixo uma lista das tags disponveis com os bundles do ncleo do Symfony2. Cada uma delas tem um efeito
diferente no seu servio e muitas tags requerem argumentos adicionais (alm do parmetro name).
assetic.filter
assetic.templating.php
2.1. Livro
233
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
/components/dependency_injection/factories
/components/dependency_injection/parentservices
Como definir Controladores como Servios
2.1.15 Performance
O Symfony2 rpido, logo aps a sua instalao. Claro, se voc realmente precisa de mais velocidade, h muitas
maneiras para tornar o Symfony ainda mais rpido. Neste captulo, voc vai explorar muitas das formas mais comuns
e poderosas para tornar a sua aplicao Symfony ainda mais rpida.
Use um Cache de Cdigo Byte (ex.: APC)
Uma das melhores (e mais fceis) coisas que voc deve fazer para melhorar a sua performance usar um cache de
cdigo byte. A idia de um cache de cdigo byte remover a necessidade de constantemente recompilar o cdigo
fonte PHP. H uma srie de Caches de cdigo byte _ disponveis, alguns dos quais so de cdigo aberto. O cache de
cdigo byte mais amplamente usado , provavelmente, o APC
Usar um cache de cdigo byte no tem um lado negativo, e o Symfony2 foi arquitetado para executar realmente bem
neste tipo de ambiente.
Mais otimizaes
Caches de cdigo byte normalmente monitoram as alteraes nos arquivos fonte. Isso garante que, se o fonte de um
arquivo for alterado, o cdigo byte recompilado automaticamente. Isto realmente conveniente, mas, obviamente,
adiciona uma sobrecarga.
Por este motivo, alguns caches de cdigo byte oferecem uma opo para desabilitar estas verificaes. Obviamente,
quando desabilitar estas verificaes, caber ao administrador do servidor garantir que o cache seja limpo sempre que
houver qualquer alterao nos arquivos fonte. Caso contrrio, as atualizaes que voc fizer nunca sero vistas.
234
Captulo 2. Livro
Por exemplo, para desativar estas verificaes no APC, basta adicionar apc.stat=0 ao seu arquivo de configurao
php.ini.
Utilize um Autoloader com cache (ex.: ApcUniversalClassLoader)
Por padro, a edio standard do Symfony2 usa o UniversalClassLoader no arquivo autoloader.php . Este autoloader fcil de usar, pois ele encontra automaticamente as novas classes que voc colocou nos diretrios registrados.
Infelizmente, isso tem um custo, devido ao carregador iterar sobre todos os namespaces configurados para encontrar um determinado arquivo, fazendo chamadas file_exists at que, finalmente, encontre o arquivo que estiver
procurando.
A soluo mais simples armazenar em cache a localizao de cada classe aps ter sido localizada pela primeira
vez. O Symfony vem com um carregador de classe - ApcClassLoader - que faz exatamente isso. Para us-lo,
basta adaptar seu arquivo front controller. Se voc estiver usando a Distribuio Standard, este cdigo j deve estar
disponvel com comentrios neste arquivo:
// app.php
// ...
$loader = require_once __DIR__./../app/bootstrap.php.cache;
// Use APC for autoloading to improve performance
// Change sf2 by the prefix you want in order to prevent key conflict with another application
/*
$loader = new ApcClassLoader(sf2, $loader);
$loader->register(true);
*/
// ...
Nota: Ao usar o autoloader APC, se voc adicionar novas classes, elas sero encontradas automaticamente e tudo
vai funcionar da mesma forma como antes (ou seja, sem razo para limpar o cache). No entanto, se voc alterar a
localizao de um namespace ou prefixo em particular, voc vai precisar limpar o cache do APC. Caso contrrio, o
autoloader ainda estar procurando pelo local antigo para todas as classes dentro desse namespace.
2.1. Livro
235
Quando estiver debugando, necessrio colocar break points dentro do arquivo de inicializao.
Se voc estiver usando a Edio Standard do Symfony2, o arquivo de inicializao automaticamente reconstrudo
aps a atualizao das bibliotecas vendor atravs do comando php composer.phar install .
Arquivos de inicializao e caches de cdigo byte
Mesmo quando se utiliza um cache de cdigo byte, o desempenho ir melhorar quando se utiliza um arquivo de
inicializao, pois, haver menos arquivos para monitorar as mudanas. Claro, se este recurso est desativado no
cache de cdigo byte (ex.: apc.stat=0 no APC), no h mais motivo para usar um arquivo de inicializao.
236
Captulo 2. Livro
Validator
Yaml
Fundamentos de Symfony e HTTP
Symfony2 versus o PHP puro
Instalando e Configurando o Symfony
Controlador
Roteamento
Criando e usando Templates
Bancos de Dados e Doctrine
Testes
Validao
Formulrios
Segurana
HTTP Cache
Tradues
Container de Servio
Performance
API estvel do Symfony2
Fundamentos de Symfony e HTTP
Symfony2 versus o PHP puro
Instalando e Configurando o Symfony
Controlador
Roteamento
Criando e usando Templates
Bancos de Dados e Doctrine
Testes
Validao
Formulrios
Segurana
HTTP Cache
Tradues
Container de Servio
Performance
API estvel do Symfony2
2.1. Livro
237
238
Captulo 2. Livro
CAPTULO 3
Cookbook
3.1 Cookbook
3.1.1 Workflow
Como Criar e Armazenar um Projeto Symfony2 no git
Dica: Embora este artigo seja especificamente sobre git, os mesmos princpios genricos aplicam-se caso voc estiver
armazenando o seu projeto no Subversion.
Uma vez que voc leu /book/page_creation e familiarizou-se com o Symfony, j est, sem dvida, pronto para
comear o seu prprio projeto. Neste artigo do cookbook, voc aprender a melhor maneira de iniciar um novo projeto
Symfony2 que ser armazenado usando o sistema gerenciador de controle de verso git.
Configurao Inicial do Projeto
Para comear, voc precisa baixar o Symfony e inicializar o seu repositrio git local:
1. Baixe a Edio Standard do Symfony2 sem vendors.
2. Descompacte a distribuio. Ser criado um diretrio chamado Symfony com a sua nova estrutura do projeto,
arquivos de configurao, etc. Renomeie-o como desejar.
3. Crie um novo arquivo chamado .gitignore no raiz de seu novo projeto (Ex.: junto com o arquivo
composer.json) e cole o contedo seguinte nele. Os arquivos correspondentes a estes padres sero ignorados pelo git:
/web/bundles/
/app/bootstrap*
/app/cache/*
/app/logs/*
/vendor/
/app/config/parameters.yml
239
8. Finalmente, baixe todas as bibliotecas vendor de terceiros executando o composer. Para detalhes, veja Atualizando os Vendors.
Neste ponto, voc tem um projeto Symfony2 totalmente funcional que est corretamente comitado com o git. Voc
pode iniciar imediatamente o desenvolvimento, comitando as novas alteraes em seu repositrio git.
Voc pode continuar acompanhando o captulo /book/page_creation para saber mais sobre como configurar e
desenvolver dentro da sua aplicao.
Dica: A Edio Standard do Symfony2 vem com algumas funcionalidades exemplo. Para remover o cdigo de
exemplo, siga as instrues contidas no Readme da Edio Standard.
240
Captulo 3. Cookbook
Vendors e sub-mdulos Em vez de usar o sistema composer.json para gerenciar suas bibliotecas vendor,
voc pode optar por usar o git submodules nativo. No h nada de errado com esta abordagem, embora o sistema
composer.json a forma oficial de resolver este problema e provavelmente mais fcil de lidar. Ao contrrio do
git submodules, o Composer esperto o suficiente para calcular quais bibliotecas dependem de outras bibliotecas.
Armazenando o seu Projeto em um Servidor Remoto
Agora, voc tem um projeto Symfony2 totalmente funcional armazenado com o git. Entretanto, na maioria dos casos,
voc tambm vai querer guardar o seu projeto em um servidor remoto tanto para fins de backup quanto para que outros
desenvolvedores possam colaborar com o projeto.
A maneira mais fcil para armazenar o seu projeto em um servidor remoto via GitHub. Repositrios pblicos so
gratuitos, porm, voc ter que pagar uma taxa mensal para hospedar repositrios privados.
Alternativamente, voc pode armazenar seu repositrio git em qualquer servidor, criando um repositrio barebones e,
ento, envi-lo. Uma biblioteca que ajuda neste gerenciamento a Gitolite.
3.1.2 Controlador
Como personalizar as pginas de erro
Quando qualquer exceo lanada no Symfony2, ela capturada dentro da classe Kernel e, eventualmente, encaminhada para um controlador especial, TwigBundle:Exception:show, para o tratamento. Este controlador,
que reside no interior do ncleo do TwigBundle, determina qual template de erro ser exibido e o cdigo de status
que deve ser definido para a exceo.
Pginas de erro pode ser personalizadas de duas maneiras diferentes, dependendo da quantidade de controle que voc
precisa:
1. Personalizar os templates de erro das diferentes pginas de erro (explicado abaixo);
2. Substituir o controlador de exceo padro TwigBundle::Exception:show pelo seu prprio controlador
e manipul-lo como quiser (veja exception_controller in the Twig reference);
Dica: A personalizao da manipulao de exceo , na verdade, muito mais poderosa do que o que est escrito
aqui. Um evento interno lanado, kernel.exception, que permite controle completo sobre o tratamento de
exceo. Para mais informaes, veja kernel-kernel.exception.
Todos os templates de erro residem dentro do TwigBundle. Para sobrescrever os templates, simplesmente contamos
com o mtodo padro para sobrescrever templates que reside dentro de um bundle. Para maiores informaes, visite
Sobrepondo Templates de Pacote.
Por exemplo, para sobrescrever o template padro de erro que exibido ao usurio final, adicione um novo template
em app/Resources/TwigBundle/views/Exception/error.html.twig:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>An Error Occurred: {{ status_text }}</title>
</head>
<body>
<h1>Oops! An Error Occurred</h1>
<h2>The server returned a "{{ status_code }} {{ status_text }}".</h2>
3.1. Cookbook
241
</body>
</html>
Dica: Se voc no estiver familiarizado com o Twig, no se preocupe. O Twig uma templating engine simples,
poderosa e opcional que integra o Symfony2. Para mais informaes sobre o Twig, consulte Criando e usando
Templates.
Alm da pgina HTML de erro padro, o Symfony fornece uma pgina padro de erro para muitos dos formatos de
resposta mais comuns, incluindo JSON (error.json.twig), XML (error.xml.twig) e at mesmo JavaScript
(error.js.twig), somente para citar alguns. Para sobrescrever qualquer um destes templates, apenas adicione um
novo arquivo com o mesmo nome no diretrio app/Resources/TwigBundle/views/Exception. Esta a
forma padro de sobrescrever qualquer template que reside dentro de um bundle.
Personalizando a Pgina 404 e outras Pginas de Erro
Voc tambm pode personalizar os templates de erro especficos de acordo com o cdigo de status HTTP. Por exemplo,
crie um template app/Resources/TwigBundle/views/Exception/error404.html.twig para exibir
uma pgina especial para erros 404 (pgina no encontrada).
O Symfony usa o seguinte algoritmo para determinar qual o template que deve usar:
Primeiro, ele procura por um template para o formato e cdigo de status especificado (como
error404.json.twig);
Se no existir, ele procura um template para o formato especificado (como error.json.twig);
Se ainda no existir, ele volta para o template HTML (como error.html.twig).
Dica: Para ver a lista completa de templates de erro padro, consulte o diretrio Resources/views/Exception
do TwigBundle.
Na instalao Standard do Symfony2, o TwigBundle pode ser encontrado em vendor/symfony/src/Symfony/Bundle/TwigBundle.
Muitas vezes,
a maneira mais fcil para personalizar uma pgina de erro copi-la do TwigBundle para o
app/Resources/TwigBundle/views/Exception e ento modific-la.
Nota: As pginas de exceo amigveis de depurao, mostradas para o desenvolvedor, podem ser personalizadas
da mesma forma, criando templates como exception.html.twig para a pgina de exceo padro HTML ou
exception.json.twig para a pgina de exceo JSON.
242
Captulo 3. Cookbook
Para utilizar um controlador desta forma, ele deve estar definido na configurao do container de servio. Para mais
informaes, consulte o captulo Service Container
Ao usar um controlador definido como um servio, ele provavelmente no estender a classe base Controller.
Em vez de contar com seus mtodos de atalho, voc vai interagir diretamente com os servios que voc precisa.
Felizmente, isto normalmente muito fcil e a classe base Controller em si uma grande fonte sobre a forma de
realizar muitas das tarefas comuns.
Nota: Especificar um controlador como um servio leva um pouco mais de trabalho. A principal vantagem que
todo o controlador ou quaisquer servios passados para o controlador podem ser modificados atravs da configurao
do container de servio. Isto especialmente til no desenvolvimento de um bundle open-source ou qualquer bundle
que ser utilizado em vrios projetos diferentes. Assim, mesmo se voc no especificar os seus controladores como
servios, provavelmente ver isto feito em alguns bundles open-source do Symfony2.
Ao usar anotaes para configurar as rotas em um controlador definido como um servio, necessrio especificar o
servio da seguinte forma:
/**
* @Route("/blog", service="my_bundle.annot_controller")
* @Cache(expires="tomorrow")
*/
class AnnotController extends Controller
{
}
Neste
exemplo,
my_bundle.annot_controller
deve
ser
AnnotController definida no container de servio.
Isto est
/bundles/SensioFrameworkExtraBundle/annotations/routing .
o
id
da
instncia
documentado no captulo
3.1.3 Roteamento
Como forar as rotas a usar sempre HTTPS ou HTTP
s vezes, voc deseja proteger algumas rotas e ter certeza de que elas sero sempre acessadas atravs do protocolo
HTTPS. O componente de Roteamento permite que voc aplique o esquema URI atravs da condio _scheme:
YAML
secure:
pattern: /secure
defaults: { _controller: AcmeDemoBundle:Main:secure }
requirements:
_scheme: https
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="secure" pattern="/secure">
3.1. Cookbook
243
<default key="_controller">AcmeDemoBundle:Main:secure</default>
<requirement key="_scheme">https</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(secure, new Route(/secure, array(
_controller => AcmeDemoBundle:Main:secure,
), array(
_scheme => https,
)));
return $collection;
A condio tambm aplicada para as solicitaes de entrada. Se voc tentar acessar o caminho /secure com
HTTP, voc ser automaticamente redirecionado para a mesma URL, mas com o esquema HTTPS.
O exemplo acima utiliza https para o _scheme, mas voc tambm pode forar uma URL sempre utilizar http.
Nota:
O componente Security fornece outra forma de aplicar HTTP ou HTTPs atravs da configurao
requires_channel. Este mtodo alternativo mais adequado para proteger uma rea do seu site (todas as
URLs sob o /admin) ou quando voc quiser proteger URLs definidas em um bundle de terceiros.
Por padro, o componente de roteamento do Symfony requer que os parmetros correspondam com o seguinte path de
expresso regular: [^/]+. Isso significa que todos os caracteres so permitidos, exceto /.
244
Captulo 3. Cookbook
Voc deve explicitamente permitir que a / faa parte de seu parmetro, especificando um path regex mais permissivo.
YAML
_hello:
path: /hello/{name}
defaults: { _controller: AcmeDemoBundle:Demo:hello }
requirements:
name: ".+"
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="_hello" path="/hello/{name}">
<default key="_controller">AcmeDemoBundle:Demo:hello</default>
<requirement key="name">.+</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(_hello, new Route(/hello/{name}, array(
_controller => AcmeDemoBundle:Demo:hello,
), array(
name => .+,
)));
return $collection;
Annotations
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DemoController
{
/**
* @Route("/hello/{name}", name="_hello", requirements={"name" = ".+"})
*/
public function helloAction($name)
{
// ...
}
}
3.1. Cookbook
245
Suponha que no h nenhum controlador padro til para o caminho / da sua aplicao e voc quer redirecionar os
pedidos para /app.
Sua configurao ser parecida com a seguinte:
AppBundle:
resource: "@App/Controller/"
type:
annotation
prefix:
/app
root:
pattern: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /app
permanent: true
Neste
exemplo,
voc
configura
uma
rota
para
o
caminho
class:Symfony\Bundle\FrameworkBundle\Controller\RedirectController lidar com ela.
com o Symfony e oferece duas aes para redirecionar o pedido:
/
e
deixa
o
Este controlador vem
urlRedirect redireciona para outro caminho. Voc deve fornecer o parmetro path contendo o caminho
do recurso para o qual deseja redirecionar.
redirect (no mostrado aqui) redireciona para outra rota. Voc deve fornecer o parmetro route com o
nome da rota para a qual voc quer redirecionar.
O permanent informa ambos os mtodos para emitir um cdigo de status HTTP 301 em vez do cdigo de status
padro 302.
Como usar mtodos HTTP alm do GET e POST em Rotas
O mtodo de um pedido HTTP um dos requisitos que pode ser verificado ao ver se ele corresponde a uma rota. Isto
introduzido no captulo de roteamento do livro Roteamento com exemplos usando GET e POST. Voc tambm pode
usar outros verbos HTTP desta forma. Por exemplo, se voc tem um post de blog, ento, voc pode usar o mesmo
padro de URL para mostr-lo, fazer alteraes e remov-lo pela correspondncia nos mtodos GET, PUT e DELETE.
YAML
blog_show:
pattern: /blog/{slug}
defaults: { _controller: AcmeDemoBundle:Blog:show }
requirements:
_method: GET
blog_update:
pattern: /blog/{slug}
defaults: { _controller: AcmeDemoBundle:Blog:update }
requirements:
_method: PUT
blog_delete:
pattern: /blog/{slug}
defaults: { _controller: AcmeDemoBundle:Blog:delete }
requirements:
_method: DELETE
XML
246
Captulo 3. Cookbook
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="blog_show" pattern="/blog/{slug}">
<default key="_controller">AcmeDemoBundle:Blog:show</default>
<requirement key="_method">GET</requirement>
</route>
<route id="blog_update" pattern="/blog/{slug}">
<default key="_controller">AcmeDemoBundle:Blog:update</default>
<requirement key="_method">PUT</requirement>
</route>
<route id="blog_delete" pattern="/blog/{slug}">
<default key="_controller">AcmeDemoBundle:Blog:delete</default>
<requirement key="_method">DELETE</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(blog_show, new Route(/blog/{slug}, array(
_controller => AcmeDemoBundle:Blog:show,
), array(
_method => GET,
)));
$collection->add(blog_update, new Route(/blog/{slug}, array(
_controller => AcmeDemoBundle:Blog:update,
), array(
_method => PUT,
)));
$collection->add(blog_delete, new Route(/blog/{slug}, array(
_controller => AcmeDemoBundle:Blog:delete,
), array(
_method => DELETE,
)));
return $collection;
Infelizmente, a vida no to simples assim, j que a maioria dos navegadores no suporta o envio de solicitaes
PUT e DELETE. Felizmente o Symfony2 fornece uma maneira simples de trabalhar com esta limitao. Ao incluir um
parmetro _method na query string ou nos parmetros de um pedido HTTP, o Symfony2 ir us-lo como o mtodo
ao fazer a correspondncia de rotas. Isto pode ser feito facilmente em formulrios com um campo oculto. Suponha
que voc tenha um formulrio para editar um post no blog:
<form action="{{ path(blog_update, {slug: blog.slug}) }}" method="post">
<input type="hidden" name="_method" value="PUT" />
{{ form_widget(form) }}
<input type="submit" value="Update" />
</form>
3.1. Cookbook
247
O pedido submetido agora vai corresponder rota blog_update e a updateAction ser utilizada para processar
o formulrio.
Do mesmo modo, o formulrio de excluso pode ser alterado para parecer com o seguinte:
<form action="{{ path(blog_delete, {slug: blog.slug}) }}" method="post">
<input type="hidden" name="_method" value="DELETE" />
{{ form_widget(delete_form) }}
<input type="submit" value="Delete" />
</form>
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="contact" path="/{_locale}/contact">
<default key="_controller">AcmeDemoBundle:Main:contact</default>
<requirement key="_locale">%acme_demo.locales%</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(contact, new Route(/{_locale}/contact, array(
_controller => AcmeDemoBundle:Main:contact,
), array(
_locale => %acme_demo.locales%,
)));
248
Captulo 3. Cookbook
return $collection;
Agora voc pode controlar e definir o parmetro acme_demo.locales em algum lugar no seu container:
YAML
# app/config/config.yml
parameters:
acme_demo.locales: en|es
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="acme_demo.locales">en|es</parameter>
</parameters>
PHP
// app/config/config.php
$container->setParameter(acme_demo.locales, en|es);
Voc tambm pode usar um parmetro para definir o seu path da rota (ou parte do seu path):
YAML
some_route:
path: /%acme_demo.route_prefix%/contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout
<route id="some_route" path="/%acme_demo.route_prefix%/contact">
<default key="_controller">AcmeDemoBundle:Main:contact</default>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(some_route, new Route(/%acme_demo.route_prefix%/contact, array(
_controller => AcmeDemoBundle:Main:contact,
)));
return $collection;
Nota: Assim como em arquivos normais de configurao do container de servio, se voc precisar de um % na sua
rota, voc pode escapar o sinal de porcentagem duplicando ele , por exemplo, /score-50%%, ir converter para
/score-50%.
No entanto, como os caracteres % includos em qualquer URL so automaticamente codificados, a URL resultante
desse exemplo seria /score-50%25 (%25 o resultado da codificao do caractere %).
3.1. Cookbook
249
Carregando Rotas
As rotas em uma aplicao Symfony so carregadas pelo DelegatingLoader. Este loader usa vrios outros
loaders (delegados) para carregar recursos de diferentes tipos, por exemplo, arquivos YAML ou anotaes @Route e
@Method em arquivos de controlador. Os loaders especializados implementam a LoaderInterface e, portanto,
tem dois mtodos importantes: supports() e load().
Considere essas linhas do routing.yml:
_demo:
resource: "@AcmeDemoBundle/Controller/DemoController.php"
type:
annotation
prefix:
/demo
Quando o loader principal realiza o parse, ele tenta todos os loaders delegados e chama seu mtodo supports() com
o determinado recurso (@AcmeDemoBundle/Controller/DemoController.php) e tipo (annotation)
como argumentos. Quando um dos loaders retorna true, seu mtodo load() ser chamado, que deve retornar um
RouteCollection contendo objetos Route.
Criando um Loader Personalizado
Para carregar rotas de alguma fonte personalizada (ou seja, de algo diferente de anotaes, arquivos YAML ou XML),
voc precisa criar um loader de rota personalizado. Este loader deve implementar a LoaderInterface.
O loader de exemplo abaixo, suporta o carregamento de recursos de roteamento com um tipo extra. O tipo extra
no importante - voc pode simplesmente inventar qualquer tipo de recurso que desejar. O prprio nome do recurso
no realmente usado no exemplo:
namespace Acme\DemoBundle\Routing;
use
use
use
use
Symfony\Component\Config\Loader\LoaderInterface;
Symfony\Component\Config\Loader\LoaderResolverInterface;
Symfony\Component\Routing\Route;
Symfony\Component\Routing\RouteCollection;
250
Captulo 3. Cookbook
XML
3.1. Cookbook
251
PHP
use Symfony\Component\DependencyInjection\Definition;
$container
->setDefinition(
acme_demo.routing_loader,
new Definition(Acme\DemoBundle\Routing\ExtraLoader)
)
->addTag(routing.loader)
;
Observe a tag routing.loader. Todos os servios com esta tag sero marcados como potenciais loaders de rota e
adicionados como roteadores especializados para o DelegatingLoader.
Usando o Loader Personalizado Se voc no fez nada mais, seu loader de roteamento personalizado no ser
chamado. Em vez disso, voc s precisa adicionar algumas linhas extras para a configurao de roteamento:
YAML
# app/config/routing.yml
AcmeDemoBundle_Extra:
resource: .
type: extra
XML
PHP
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
$collection = new RouteCollection();
$collection->addCollection($loader->import(., extra));
return $collection;
A parte importante aqui a chave type. Seu valor deve ser extra. Este o tipo que nosso ExtraLoader suporta e
que ir certificar-se que seu mtodo load() chamado. A chave resource insignificante para o ExtraLoader,
252
Captulo 3. Cookbook
Na maioria dos casos melhor no implementar a LoaderInterface voc mesmo, mas estender do Loader.
Esta classe sabe como usar um LoaderResolver para carregar os recursos de roteamento secundrios.
Claro que voc ainda precisa implementar supports() e load(). Sempre que quiser carregar outro recurso - por
exemplo, um arquivo de configurao Yaml - voc pode chamar o import() method:
namespace Acme\DemoBundle\Routing;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\RouteCollection;
class AdvancedLoader extends Loader
{
public function load($resource, $type = null)
{
$collection = new RouteCollection();
$resource = @AcmeDemoBundle/Resources/config/import_routing.yml;
$type = yaml;
$importedRoutes = $this->import($resource, $type);
$collection->addCollection($importedRoutes);
return $collection;
}
public function supports($resource, $type = null)
{
return $type === advanced_extra;
}
}
Nota: O nome e o tipo do recurso da configurao de roteamento importada pode ser qualquer coisa que normalmente suportada pelo loader de configurao de roteamento (YAML, XML, PHP, anotao, etc.)
3.1. Cookbook
253
use Symfony\Component\HttpFoundation\Request;
class RedirectingController extends Controller
{
public function removeTrailingSlashAction(Request $request)
{
$pathInfo = $request->getPathInfo();
$requestUri = $request->getRequestUri();
$url = str_replace($pathInfo, rtrim($pathInfo, /), $requestUri);
return $this->redirect($url, 301);
}
}
Depois disso, crie uma rota para este controlador que corresponde sempre que uma URL com uma barra no final
solicitada. No se esquea de colocar esta rota por ltimo no seu sistema, como explicado a seguir:
YAML
remove_trailing_slash:
path: /{url}
defaults: { _controller: AcmeDemoBundle:Redirecting:removeTrailingSlash }
requirements:
url: .*/$
_method: GET
XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing">
<route id="remove_trailing_slash" path="/{url}">
<default key="_controller">AcmeDemoBundle:Redirecting:removeTrailingSlash</default>
<requirement key="url">.*/$</requirement>
<requirement key="_method">GET</requirement>
</route>
</routes>
PHP
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(
remove_trailing_slash,
new Route(
/{url},
array(
_controller => AcmeDemoBundle:Redirecting:removeTrailingSlash,
),
array(
url => .*/$,
_method => GET,
)
)
);
Nota: Redirecionar uma solicitao POST no funciona bem em navegadores antigos. Um 302 em uma solicitao
254
Captulo 3. Cookbook
POST iria enviar uma solicitao GET aps o redirecionamento por motivos legados. Por essa razo, a rota aqui
corresponde apenas solicitaes GET.
Cuidado: Certifique-se de incluir esta rota em sua configurao de roteamento no final de sua lista de rotas. Caso
contrrio, voc corre o risco de redirecionar rotas reais (incluindo as rotas principais do Symfony2) que realmente
tm uma barra no final do seu caminho.
3.1.4 Assetic
Como usar o Assetic para o Gerenciamento de Assets
O Assetic combina duas idias principais: assets e filtros. Os assets so arquivos CSS, JavaScript e arquivos de
imagem. Os filtros so coisas que podem ser aplicadas esses arquivos antes deles serem servidos ao navegador.
Isto permite uma separao entre os arquivos asset armazenados na aplicao e os arquivos que so efetivamente
apresentados ao usurio.
Sem o Assetic, voc somente poderia servir os arquivos que so armazenados diretamente na aplicao:
Twig
<script src="{{ asset(js/script.js) }}" type="text/javascript" />
PHP
<script src="<?php echo $view[assets]->getUrl(js/script.js) ?>" type="text/javascript" />
Mas com o Assetic, voc pode manipular esses assets da forma que desejar (ou carreg-los de qualquer lugar) antes de
serv-los. Isto significa que voc pode:
Minificar e combinar todos os seus arquivos CSS e JS
Executar todos (ou apenas alguns) dos seus arquivos CSS ou JS atravs de algum tipo de compilador, como o
LESS, SASS ou CoffeeScript
Executar otimizaes em suas imagens
Assets
O uso do Assetic oferece muitas vantagens sobre servir diretamente os arquivos. Os arquivos no precisam ser armazenados onde eles so servidos e podem ser buscados a partir de vrias fontes, como, por exemplo, a partir de um
bundle:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* %}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*)) as $url): ?>
<script type="text/javascript" src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
3.1. Cookbook
255
Dica: Para buscar folhas de estilo CSS, voc pode usar as mesmas metodologias vistas aqui, exceto com a tag
stylesheets :
Twig
{% stylesheets @AcmeFooBundle/Resources/public/css/* %}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
PHP
<?php foreach ($view[assetic]->stylesheets(
array(@AcmeFooBundle/Resources/public/css/*)
) as $url): ?>
<link rel="stylesheet" href="<?php echo $view->escape($url) ?>" />
<?php endforeach; ?>
Nota: Este um ponto-chave: uma vez que voc deixar o Assetic lidar com seus assets, os arquivos so servidos
a partir de um local diferente. Isto pode causar problemas com os arquivos CSS que referenciam imagens pelo seu
caminho relativo. No entanto, isso pode ser corrigido usando o filtro cssrewrite, que atualiza os caminhos nos
arquivos CSS para refletir a sua nova localizao.
Combinando Assets Voc tambm pode combinar vrios arquivos em um nico. Isto ajuda a reduzir o nmero de
solicitaes HTTP, o que timo para o desempenho front-end. Tambm permite que voc mantenha os arquivos mais
facilmente, dividindo-os em partes gerenciveis. Isso pode ajudar com a possibilidade de reutilizao, uma vez que
voc pode facilmente dividir os arquivos especficos do projeto daqueles que podem ser usados em outras aplicaes,
mas ainda serv-los como um nico arquivo:
Twig
{% javascripts
@AcmeFooBundle/Resources/public/js/*
@AcmeBarBundle/Resources/public/js/form.js
@AcmeBarBundle/Resources/public/js/calendar.js %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*,
@AcmeBarBundle/Resources/public/js/form.js,
@AcmeBarBundle/Resources/public/js/calendar.js)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
No ambiente dev, cada arquivo ainda servido individualmente, de modo que voc pode depurar problemas mais
facilmente. No entanto, no ambiente prod, sero processados como uma nica tag script.
Dica: Se voc novo no Assetic e tentar usar a sua aplicao no ambiente prod (utilizando o controlador app.php),
256
Captulo 3. Cookbook
voc provavelmente ver que todos os seus CSS e JS esto corrompidos. No se preocupe! Isso de propsito. Para
detalhes sobre a utilizao do Assetic no ambiente prod, consulte Dump dos arquivos de asset.
E a combinao de arquivos no se aplica apenas para seus arquivos. Voc tambm pode usar o Assetic para combinar
assets de terceiros, tais como jQuery, como seu prprio em um nico arquivo:
Twig
{% javascripts
@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js
@AcmeFooBundle/Resources/public/js/* %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js,
@AcmeFooBundle/Resources/public/js/*)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
Filtros
Uma vez que so gerenciados pelo Assetic, voc pode aplicar filtros em seus assets antes deles serem servidos. Isso
inclui filtros que comprimem a sada de seus assets para tamanhos de arquivos menores (e melhor otimizao do frontend). Outros filtros podem compilar os arquivos JavaScript a partir de arquivos CoffeeScript e processar SASS em
CSS. Na verdade, o Assetic tem uma longa lista de filtros disponveis.
Muitos dos filtros no fazem o trabalho diretamente, mas usam bibliotecas existentes de terceiros para fazer o trabalho
pesado. Isto significa que muitas vezes voc vai precisar instalar uma biblioteca de terceiro para usar um filtro. A
grande vantagem de usar o Assetic para chamar estas bibliotecas (em oposio a us-las diretamente) que, em vez
de ter que execut-las manualmente depois de trabalhar nos arquivos, o Assetic ir cuidar disto para voc e remover
completamente esta etapa do seu processo de desenvolvimento e implantao.
Para usar um filtro, primeiro voc precisa especific-lo na configurao do Assetic. Adicionar um filtro aqui no
significa que ele est sendo usado - apenas significa que est disponvel para uso (vamos usar o filtro abaixo).
Por exemplo, para usar o JavaScript YUI Compressor, a configurao seguinte deve ser acrescentada:
YAML
# app/config/config.yml
assetic:
filters:
yui_js:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="yui_js"
jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" />
</assetic:config>
PHP
3.1. Cookbook
257
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
yui_js => array(
jar => %kernel.root_dir%/Resources/java/yuicompressor.jar,
),
),
));
Agora, para efetivamente usar o filtro em um grupo de arquivos JavaScript, adicione-o em seu template:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* filter=yui_js %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*),
array(yui_js)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
Um guia mais detalhado sobre a configurao e uso dos filtros Assetic, bem como detalhes do modo de depurao do
Assetic pode ser encontrado em Como Minificar JavaScripts e Folhas de Estilo com o YUI Compressor.
Controlando a URL usada
Se desejar, voc pode controlar as URLs que o Assetic produz. Isto feito a partir do template e relativo raiz do
documento pblico:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* output=js/compiled/main.js %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*),
array(),
array(output => js/compiled/main.js)
) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
Nota: O Symfony tambm contm um mtodo de busting de cache, onde a URL final gerada pelo Assetic contm um
parmetro de query, que pode ser incrementado atravs de configurao em cada implantao. Para mais informaes,
consulte a opo de configurao ref-framework-assets-version.
258
Captulo 3. Cookbook
No ambiente dev, o Assetic gera caminhos para os arquivos CSS e JavaScript que no existem fisicamente em seu
computador. Mas, eles renderizam mesmo assim porque um controlador interno do Symfony abre os arquivos e serve
de volta o contedo (aps a execuo de quaisquer filtros).
Este tipo de publicao dinmica dos assets processados tima porque significa que voc pode ver imediatamente o
novo estado de quaisquer arquivos de assets que foram alterados. Tambm ruim, porque pode ser muito lento. Se
voc estiver usando uma srie de filtros, pode ser francamente frustrante.
Felizmente, o Assetic fornece uma forma de fazer o dump de seus assets para arquivos reais, em vez de ser gerado
dinamicamente.
Dump dos arquivos asset no ambiente prod No ambiente prod, seus JS e CSS so representados por uma nica
tag cada. Em outras palavras, em vez de ver cada arquivo JavaScript que voc est incluindo no seu cdigo fonte,
provvel que voc s veja algo semelhante a:
<script src="/app_dev.php/js/abcd123.js"></script>
Alm disso, esse arquivo no existe realmente, nem renderizado de forma dinmica pelo Symfony (pois os arquivos
de asset esto no ambiente dev). Isto de propsito - deixar o Symfony gerar esses arquivos dinamicamente em um
ambiente de produo muito lento.
Em vez disso, cada vez que voc usar a sua aplicao no ambiente prod (e, portanto, cada vez que voc implantar),
voc deve executar o seguinte comando:
$ php app/console assetic:dump --env=prod --no-debug
Isso vai gerar fisicamente e escrever cada arquivo que voc precisa (por exemplo, /js/abcd123.js). Se voc
atualizar qualquer um de seus assets, necessrio executar o comando novamente para gerar o novo arquivo.
Dump dos arquivos de asset no ambiente dev Por padro, cada caminho de asset gerado no ambiente dev gerenciado dinamicamente pelo Symfony. Isso no tem desvantagem (voc pode ver as suas alteraes imediatamente),
com exceo de que os assets podem visivelmente carregar mais lentos. Se voc sentir que seus assets esto carregando
muito lentamente, siga este guia.
Primeiro, diga ao Symfony para parar de tentar processar estes arquivos dinamicamente. Faa a seguinte alterao em
seu arquivo config_dev.yml:
YAML
# app/config/config_dev.yml
assetic:
use_controller: false
XML
<!-- app/config/config_dev.xml -->
<assetic:config use-controller="false" />
PHP
// app/config/config_dev.php
$container->loadFromExtension(assetic, array(
use_controller => false,
));
3.1. Cookbook
259
Em seguida, uma vez que o Symfony no est mais gerando esses assets para voc, voc vai precisar fazer o dump
deles manualmente. Para isso, execute o seguinte:
$ php app/console assetic:dump
Esta fisicamente grava todos os arquivos ativos que voc precisa para seu dev produo. A grande desvantagem
que voc precisa executar este cada vez voc atualizar um ativo. Felizmente, passando o - assistir opo , o
comando automaticamente regenerar ativos * como eles mudam *:
$ php app/console assetic:dump --watch
Uma vez que executar este comando no ambiente dev pode gerar vrios arquivos, geralmente uma boa idia apontar
os seus arquivos assets gerados para algum diretrio isolado (por exemplo, /js/compiled), para manter as coisas
organizadas:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* output=js/compiled/main.js %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*),
array(),
array(output => js/compiled/main.js)
) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
O YUI Compressor escrito em Java e distribudo como um JAR. Faa o download do JAR no site da Yahoo! e salve-o
em app/Resources/java/yuicompressor.jar.
Configure os Filtros do YUI
Agora voc precisa configurar dois filtros Assetic em sua aplicao, um para minificar os JavaScripts com o YUI
Compressor e um para minificar as folhas de estilo:
YAML
# app/config/config.yml
assetic:
filters:
yui_css:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
yui_js:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
260
Captulo 3. Cookbook
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="yui_css"
jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" />
<assetic:filter
name="yui_js"
jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" />
</assetic:config>
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
yui_css => array(
jar => %kernel.root_dir%/Resources/java/yuicompressor.jar,
),
yui_js => array(
jar => %kernel.root_dir%/Resources/java/yuicompressor.jar,
),
),
));
Voc agora tem acesso a dois novos filtros Assetic em sua aplicao: yui_css e yui_js. Eles utilizaro o YUI
Compressor para minificar as folhas de estilo e JavaScripts, respectivamente.
Minifique os seus Assets
Voc agora tem o YUI Compressor configurado, mas nada vai acontecer at aplicar um desses filtros para um asset.
Uma vez que os seus assets fazem parte da camada de viso, este trabalho feito em seus templates:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* filter=yui_js %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*),
array(yui_js)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
Nota: O exemplo acima assume que voc tem um bundle chamado AcmeFooBundle e os seus arquivos JavaScript
esto no diretrioResources/public/js sob o seu bundle. Entretante, isso no importante - voc pode incluir os
seus arquivos JavaScript, no importa onde eles estiverem.
Com a adio do filtro yui_js para as tags asset acima, voc deve agora ver os JavaScripts minificados sendo
carregados muito mais rpido. O mesmo processo pode ser repetido para minificar as suas folhas de estilo.
Twig
3.1. Cookbook
261
PHP
Os JavaScripts e as folhas de estilo minificados so muito difceis de ler, e muito menos depurar. Devido a isso, o
Assetic permite desabilitar um certo filtro quando a sua aplicao est no modo de depurao. Voc pode fazer isso
prefixando o nome do filtro em seu template com um ponto de interrogao: ?. Isto diz ao Assetic para apenas aplicar
esse filtro quando o modo de depurao est desligado.
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* filter=?yui_js %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/*),
array(?yui_js)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>
Jpegoptim um utilitrio para otimizar arquivos JPEG. Para us-lo com o Assetic, adicione o seguinte na configurao
do Assetic:
YAML
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
XML
262
Captulo 3. Cookbook
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
jpegoptim => array(
bin => path/to/jpegoptim,
),
),
));
Nota: Observe que, para usar o jpegoptim, voc deve instal-lo em seu sistema. A opo bin aponta para a
localizao do binrio compilado.
Ele agora pode ser usado em um template:
Twig
{% image @AcmeFooBundle/Resources/public/images/example.jpg
filter=jpegoptim output=/images/example.jpg
%}
<img src="{{ asset_url }}" alt="Example"/>
{% endimage %}
PHP
<?php foreach ($view[assetic]->images(
array(@AcmeFooBundle/Resources/public/images/example.jpg),
array(jpegoptim)) as $url): ?>
<img src="<?php echo $view->escape($url) ?>" alt="Example"/>
<?php endforeach; ?>
Removendo todos os dados EXIF Por padro, a execuo desse filtro remove apenas algumas das informaes
meta armazenadas no arquivo. Os dados EXIF e comentrios no so removidos, mas voc pode remov-los usando a
opo strip_all:
YAML
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
strip_all: true
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="jpegoptim"
3.1. Cookbook
263
bin="path/to/jpegoptim"
strip_all="true" />
</assetic:config>
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
jpegoptim => array(
bin => path/to/jpegoptim,
strip_all => true,
),
),
));
Diminuindo a qualidade mxima Por padro, o nvel de qualidade do JPEG no afetado. Voc pode ganhar
redues adicionais no tamanho dos arquivos ao ajustar a configurao de qualidade mxima para um valor inferior ao
nvel atual das imagens. Isto ir, claro, custar a qualidade de imagem:
YAML
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
max: 70
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="jpegoptim"
bin="path/to/jpegoptim"
max="70" />
</assetic:config>
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
jpegoptim => array(
bin => path/to/jpegoptim,
max => 70,
),
),
));
Se voc estiver usando o Twig, possvel conseguir tudo isso com uma sintaxe curta, ao habilitar e usar uma funo
especial do Twig. Comece adicionando a seguinte configurao:
YAML
264
Captulo 3. Cookbook
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
twig:
functions:
jpegoptim: ~
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="jpegoptim"
bin="path/to/jpegoptim" />
<assetic:twig>
<assetic:twig_function
name="jpegoptim" />
</assetic:twig>
</assetic:config>
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
jpegoptim => array(
bin => path/to/jpegoptim,
),
),
twig => array(
functions => array(jpegoptim),
),
),
));
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
3.1. Cookbook
265
name="jpegoptim"
bin="path/to/jpegoptim" />
<assetic:twig>
<assetic:twig_function
name="jpegoptim"
output="images/*.jpg" />
</assetic:twig>
</assetic:config>
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
jpegoptim => array(
bin => path/to/jpegoptim,
),
),
twig => array(
functions => array(
jpegoptim => array(
output => images/*.jpg
),
),
),
));
YAML
# app/config/config.yml
assetic:
filters:
coffee:
bin: /usr/bin/coffee
node: /usr/bin/node
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="coffee"
bin="/usr/bin/coffee"
node="/usr/bin/node" />
</assetic:config>
PHP
266
Captulo 3. Cookbook
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
coffee => array(
bin => /usr/bin/coffee,
node => /usr/bin/node,
),
),
));
Agora, voc pode servir um nico arquivo CoffeeScript como JavaScript a partir de seus templates:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/example.coffee
filter=coffee
%}
<script src="{{ asset_url }} type="text/javascript"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/example.coffee),
array(coffee)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>" type="text/javascript"></script>
<?php endforeach; ?>
Isso tudo o que necessrio para compilar este arquivo CoffeeScript e servir ele como JavaScript compilado.
Filtrando vrios arquivos
Voc tambm pode combinar vrios arquivos CoffeeScript em um nico arquivo de sada:
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/example.coffee
@AcmeFooBundle/Resources/public/js/another.coffee
filter=coffee
%}
<script src="{{ asset_url }} type="text/javascript"></script>
{% endjavascripts %}
PHP
<?php foreach ($view[assetic]->javascripts(
array(@AcmeFooBundle/Resources/public/js/example.coffee,
@AcmeFooBundle/Resources/public/js/another.coffee),
array(coffee)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>" type="text/javascript"></script>
<?php endforeach; ?>
Ambos os arquivos agora sero servidos como um nico arquivo compilado em JavaScript regular.
3.1. Cookbook
267
Uma das grandes vantagens de usar o Assetic minimizar o nmero de arquivos asset para reduzir as solicitaes
HTTP. A fim de fazer seu pleno uso, seria bom combinar todos os seus arquivos JavaScript e CoffeeScript juntos,
uma vez que, todos sero servidos como JavaScript. Infelizmente, apenas adicionar os arquivos JavaScript aos arquivos
combinados como acima no funcionar, pois, os arquivos JavaScript regulares no sobrevivero a compilao do
CoffeeScript.
Este problema pode ser evitado usando a opo apply_to na configurao, que permite especificar que filtro dever
ser sempre aplicado determinadas extenses de arquivo. Neste caso, voc pode especificar que o filtro Coffee ser
aplicado todos os arquivos .coffee:
YAML
# app/config/config.yml
assetic:
filters:
coffee:
bin: /usr/bin/coffee
node: /usr/bin/node
apply_to: "\.coffee$"
XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="coffee"
bin="/usr/bin/coffee"
node="/usr/bin/node"
apply_to="\.coffee$" />
</assetic:config>
PHP
// app/config/config.php
$container->loadFromExtension(assetic, array(
filters => array(
coffee => array(
bin => /usr/bin/coffee,
node => /usr/bin/node,
apply_to => \.coffee$,
),
),
));
Com isso, voc no precisa especificar o filtro coffee no template. Voc tambm pode listar os arquivos JavaScript
regulares, todos os quais sero combinados e renderizados como um nico arquivo JavaScript (apenas com os arquivos
.coffee sendo executados atravs do filtro CoffeeScript):
Twig
{% javascripts @AcmeFooBundle/Resources/public/js/example.coffee
@AcmeFooBundle/Resources/public/js/another.coffee
@AcmeFooBundle/Resources/public/js/regular.js
%}
<script src="{{ asset_url }} type="text/javascript"></script>
{% endjavascripts %}
PHP
268
Captulo 3. Cookbook
3.1.5 Doctrine
Como Manipular o Upload de Arquivos com o Doctrine
Gerenciar o upload de arquivos utilizando entidades do Doctrine no diferente de manusear qualquer outro upload
de arquivo. Em outras palavras, voc livre para mover o arquivo em seu controlador aps a manipulao do envio de
um formulrio. Para exemplos de como fazer isso, veja a pgina de referncia do tipo arquivo.
Se voc quiser, tambm pode integrar o upload de arquivo no ciclo de vida de sua entidade (ou seja, criao, atualizao
e remoo). Neste caso, como a sua entidade criada, atualizada e removida pelo Doctrine, o tratamento do upload e
da remoo de arquivos ser realizado automaticamente (sem precisar fazer nada em seu controlador);
Para fazer este trabalho, voc precisa cuidar de uma srie de detalhes, que sero abordados neste artigo do cookbook.
Configurao Bsica
Primeiro, crie uma classe Entity simples do Doctrine para voc trabalhar:
// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
*/
class Document
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank
*/
public $name;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
3.1. Cookbook
269
A entidade Document tem um nome e ele associado a um arquivo. A propriedade path armazena o caminho
relativo para o arquivo e persistida no banco de dados. O getAbsolutePath() um mtodo de convenincia
que retorna o caminho absoluto para o arquivo enquanto o getWebPath() um mtodo de convenincia que retorna
o caminho web, que podem ser utilizados em um template para obter o link do arquivo que foi feito o upload.
Dica: Se no tiver feito isso, voc deve ler primeiro a documentao sobre o tipo arquivo file para entender como
funciona o processo bsico de upload.
Nota: Se voc estiver usando anotaes para especificar as suas regras de validao (como mostrado neste exemplo),
certifique-se de que tenha ativado a validao por anotao (veja configurao de validao).
Para lidar com o upload do arquivo no formulrio, use um campo virtual file. Por exemplo, se voc est construindo o seu formulrio diretamente em um controlador, ele poderia parecer com o seguinte:
public function uploadAction()
{
// ...
$form = $this->createFormBuilder($document)
->add(name)
->add(file)
->getForm();
// ...
}
Em seguida, crie essa propriedade em sua classe Document e adicione algumas regras de validao:
270
Captulo 3. Cookbook
// src/Acme/DemoBundle/Entity/Document.php
// ...
class Document
{
/**
* @Assert\File(maxSize="6000000")
*/
public $file;
// ...
}
Nota: Como voc est usando a constraint File, o Symfony2 ir adivinhar automaticamente que o campo do
formulrio do tipo para upload de arquivos. por isso que voc no tem que defini-lo explicitamente ao criar o
formulrio acima (->add(file)).
O controlador a seguir mostra como lidar com todo o processo:
use Acme\DemoBundle\Entity\Document;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
// ...
/**
* @Template()
*/
public function uploadAction()
{
$document = new Document();
$form = $this->createFormBuilder($document)
->add(name)
->add(file)
->getForm()
;
if ($this->getRequest()->isMethod(POST)) {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($document);
$em->flush();
$this->redirect($this->generateUrl(...));
}
}
return array(form => $form->createView());
}
3.1. Cookbook
271
</form>
O controlador anterior ir persistir automaticamente a entidade Document com o nome submetido, mas ele no far
nada a respeito do arquivo e a propriedade path ficar em branco.
Uma maneira fcil de lidar com o upload do arquivo mov-lo pouco antes da entidade ser persistida e, em seguida,
definir a propriedade path de acordo. Comece chamando o novo mtodo upload() na classe Document, que
voc vai criar no momento para lidar com o upload do arquivo:
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$document->upload();
$em->persist($document);
$em->flush();
$this->redirect(...);
}
O mtodo upload() ir aproveitar o objeto UploadedFile , que o retornado aps um campo file ser submetido:
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->file) {
return;
}
// use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->file->move(
$this->getUploadRootDir(),
$this->file->getClientOriginalName()
);
// set the path property to the filename where youve saved the file
$this->path = $this->file->getClientOriginalName();
// clean up the file property as you wont need it anymore
$this->file = null;
}
Mesmo esta aplicao funcionando, ela sofre de uma grande falha: E se houver um problema quando a entidade for
persistida? O arquivo j teria sido movido para seu local definitivo, apesar da propriedade path da entidade no ter
sido persistida corretamente.
Para evitar esses problemas, voc deve alterar a implementao de forma que as operaes do banco de dados e a cpia
do arquivo tornem-se atmicas: se h um problema persistindo a entidade ou se o arquivo no pode ser movido, ento
nada deve ser feito.
272
Captulo 3. Cookbook
Para fazer isso, voc precisa mover o arquivo no mesmo momento em que o Doctrine persistir a entidade no banco de
dados. Isto pode ser feito lifecycle da entidade:
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Document
{
}
3.1. Cookbook
273
}
}
}
A classe agora faz tudo o que voc precisa: ela gera um nome de arquivo nico antes de persistir, move o arquivo
depois de persistir e remove o arquivo sempre que a entidade for excluda.
Agora que a cpia do arquivo tratada atomicamente pela entidade, a chamada $document->upload() deve ser
removida do controlador:
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($document);
$em->flush();
$this->redirect(...);
}
Nota: Os callbacks dos eventos @ORM\PrePersist() e @ORM\PostPersist() so acionados antes e depois da entidade ser persistida no banco de dados. Por outro lado, a callback dos eventos @ORM\PreUpdate() e
@ORM\PostUpdate() so chamadas quando a entidade atualizada.
Cuidado: As callbacks PreUpdate e PostUpdate so acionadas somente se houver uma alterao em um dos
campos de uma entidade que persistida. Isto significa que, por padro, se voc modificar apenas a propriedade
$file, esses eventos no sero disparados, pois a propriedade no diretamente persistida via Doctrine. Uma
soluo seria a utilizao de um campo updated que persistido pelo Doctrine e modific-lo manualmente
quando alterar o arquivo.
Se voc quiser usar o id como nome do arquivo, a implementao ligeiramente diferente pois voc precisa salvar a
extenso na propriedade path, em vez do nome real:
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Document
{
// a property used temporarily while deleting
private $filenameForRemove;
/**
* @ORM\PrePersist()
* @ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
$this->path = $this->file->guessExtension();
}
}
274
Captulo 3. Cookbook
/**
* @ORM\PostPersist()
* @ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
// you must throw an exception here if the file cannot be moved
// so that the entity is not persisted to the database
// which the UploadedFile move() method does
$this->file->move(
$this->getUploadRootDir(),
$this->id...$this->file->guessExtension()
);
unset($this->file);
}
/**
* @ORM\PreRemove()
*/
public function storeFilenameForRemove()
{
$this->filenameForRemove = $this->getAbsolutePath();
}
/**
* @ORM\PostRemove()
*/
public function removeUpload()
{
if ($this->filenameForRemove) {
unlink($this->filenameForRemove);
}
}
public function getAbsolutePath()
{
return null === $this->path
? null
: $this->getUploadRootDir()./.$this->id...$this->path;
}
}
Voc vai notar que, neste caso, necessrio um pouco mais de trabalho a fim de remover o arquivo. Antes que seja
removido, voc deve armazenar o caminho do arquivo (pois ele depende do id). Ento, uma vez que o objeto foi
totalmente removido do banco de dados, voc pode apagar o arquivo com segurana (em PostRemove).
Como usar as extens?es do Doctrine: Timestampable, Sluggable, Translatable, etc.
O Doctrine2 ? muito flex?vel e a comunidade j? criou um s?rie de extens?es do Doctrine ?teis para ajudar voc? com
tarefas comuns relacionadas a entidades.
Uma biblioteca em particular - a biblioteca DoctrineExtensions - fornece funcionalidade de integra??o para os com3.1. Cookbook
275
Para registrar um servio para agir como um ouvinte ou assinante de evento voc s tem que usar a tag com o nome
apropriado. Dependendo de seu caso de uso, voc pode ligar um ouvinte em cada conexo DBAL e gerenciador de
entidade ORM ou apenas em uma conexo especfica DBAL e todos os gerenciadores de entidades que usam esta
conexo.
YAML
doctrine:
dbal:
default_connection: default
connections:
default:
driver: pdo_sqlite
memory: true
services:
my.listener:
class: Acme\SearchBundle\EventListener\SearchIndexer
tags:
- { name: doctrine.event_listener, event: postPersist }
my.listener2:
class: Acme\SearchBundle\EventListener\SearchIndexer2
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
my.subscriber:
class: Acme\SearchBundle\EventListener\SearchIndexerSubscriber
tags:
- { name: doctrine.event_subscriber, connection: default }
XML
276
Captulo 3. Cookbook
<services>
<service id="my.listener" class="Acme\SearchBundle\EventListener\SearchIndexer">
<tag name="doctrine.event_listener" event="postPersist" />
</service>
<service id="my.listener2" class="Acme\SearchBundle\EventListener\SearchIndexer2">
<tag name="doctrine.event_listener" event="postPersist" connection="default" />
</service>
<service id="my.subscriber" class="Acme\SearchBundle\EventListener\SearchIndexerSubscrib
<tag name="doctrine.event_subscriber" connection="default" />
</service>
</services>
</container>
No exemplo anterior, um servio my.listener foi configurado como um ouvinte Doctrine no evento
postPersist. A classe atrs desse servio deve ter um mtodo postPersist, que ser chamado quando o
evento lanado:
// src/Acme/SearchBundle/EventListener/SearchIndexer.php
namespace Acme\SearchBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\StoreBundle\Entity\Product;
class SearchIndexer
{
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
}
Em cada evento, voc tem acesso a um objeto LifecycleEventArgs, que d acesso tanto ao objeto entidade do
evento quanto ao gerenciador de entidade em si.
Algo importante a notar que um ouvinte estar ouvindo todas as entidades em sua aplicao. Ento, se voc est
interessado apenas em lidar com um tipo especfico de entidade (por exemplo, uma entidade Product, mas no
uma entidade BlogPost), voc deve verificar o nome da classe da entidade em seu mtodo (como mostrado
acima).
3.1. Cookbook
277
XML
// app/config/config.xml
<doctrine:config>
<doctrine:dbal
name="default"
dbname="Symfony2"
user="root"
password="null"
driver="pdo_mysql"
/>
</doctrine:config>
PHP
// app/config/config.php
$container->loadFromExtension(doctrine, array(
dbal => array(
driver
=> pdo_mysql,
dbname
=> Symfony2,
user
=> root,
password => null,
),
));
278
Captulo 3. Cookbook
$conn = $this->get(database_connection);
$users = $conn->fetchAll(SELECT * FROM users);
// ...
}
}
Voc pode registrar tipos de mapeamento personalizados atravs de configurao do symfony. Eles sero adicionados
todas as conexes configuradas. Para mais informaes sobre tipos de mapeamento personalizados, leia a seo
Tipos de Mapeamento Personalizados na documentao do Doctrine.
YAML
# app/config/config.yml
doctrine:
dbal:
types:
custom_first: Acme\HelloBundle\Type\CustomFirst
custom_second: Acme\HelloBundle\Type\CustomSecond
XML
PHP
// app/config/config.php
$container->loadFromExtension(doctrine, array(
dbal => array(
types => array(
custom_first => Acme\HelloBundle\Type\CustomFirst,
custom_second => Acme\HelloBundle\Type\CustomSecond,
),
),
));
O SchemaTool usado para inspecionar o banco de dados para comparar o esquema. Para realizar esta tarefa, ele
precisa saber que tipo de mapeamento precisa ser usado para cada um dos tipos do banco de dados. O registro de
novos pode ser feito atravs de configurao.
3.1. Cookbook
279
Vamos mapear o tipo ENUM (por padro no suportado pelo DBAL) para um tipo de mapeamento string:
YAML
# app/config/config.yml
doctrine:
dbal:
connections:
default:
// Other connections parameters
mapping_types:
enum: string
XML
PHP
// app/config/config.php
$container->loadFromExtension(doctrine, array(
dbal => array(
connections => array(
default => array(
mapping_types => array(
enum => string,
),
),
),
),
));
280
Captulo 3. Cookbook
de ciclo de vida. Alguns ajustes adicionais nas entidades geradas so necessrios posteriormente para adequar as
especificidades de cada modelo de domnio.
Este tutorial asssume que voc est utilizando uma simples aplicao de blog com as seguintes duas tabelas:
blog_post e blog_comment. Um comentrio gravado ligado a um post gravado graas a uma chave estrangeira.
CREATE TABLE blog_post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(100) COLLATE utf8_unicode_ci NOT NULL,
content longtext COLLATE utf8_unicode_ci NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE blog_comment (
id bigint(20) NOT NULL AUTO_INCREMENT,
post_id bigint(20) NOT NULL,
author varchar(20) COLLATE utf8_unicode_ci NOT NULL,
content longtext COLLATE utf8_unicode_ci NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY blog_comment_post_id_idx (post_id),
CONSTRAINT blog_post_id FOREIGN KEY (post_id) REFERENCES blog_post (id) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Antes de comear a receita, verifique se seus parmetros de conexo com banco de dados esto configurados corretamente no arquivo app/config/parameters.yml (ou onde quer que sua configurao de banco de dados
mantida) e que voc tenha inicializado um bundle que ir receber sua futura classe de entidade. NEste tutorial, vamos
supor que um AcmeBlogBundle existe e est localizado na pasta src/Acme/BlogBundle.
O primeiro passo para construir as classes de entidade de uma base da dados solicitar que o Doctrine faa a introspeco do banco de dados e gere os arquivos de metadados. Os arquivos de metadados descrevem a classe de entidade
baseados nos campos da tabela.
Esta ferramenta de linha de comando pede para o Doctrine fazer a introspeco do banco de dados e gerar os arquivos
de metadados na pasta src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm do seu
bundle.
Dica: Tambm possivel gerar a classe de metadados no formato YAML alterando o primeiro argumento para yml.
O arquivo de metadados gerado BlogPost.dcm.xml semelhante a isto:
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping>
<entity name="BlogPost" table="blog_post">
<change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy>
<id name="id" type="bigint" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="title" type="string" column="title" length="100"/>
<field name="content" type="text" column="content"/>
<field name="isPublished" type="boolean" column="is_published"/>
<field name="createdAt" type="datetime" column="created_at"/>
<field name="updatedAt" type="datetime" column="updated_at"/>
<field name="slug" type="string" column="slug" length="255"/>
<lifecycle-callbacks/>
3.1. Cookbook
281
</entity>
</doctrine-mapping>
Uma vez que os arquivos de metados foram gerados, voc pode pedir para Doctrine importar o esquema e construir as
classes de entidade relacionadas com a execuo dos dois comandos a seguir.
php app/console doctrine:mapping:import AcmeBlogBundle annotation
php app/console doctrine:generate:entities AcmeBlogBundle
O primeiro comando gera as classes de entidade com um mapeamento de anotaes, msa voc poder alterar o argumento annotationpara xml ou yml. A classe de entidade BlogComment recm-criada semelhante a
isto:
<?php
// src/Acme/BlogBundle/Entity/BlogComment.php
namespace Acme\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\BlogBundle\Entity\BlogComment
*
* @ORM\Table(name="blog_comment")
* @ORM\Entity
*/
class BlogComment
{
/**
* @var bigint $id
*
* @ORM\Column(name="id", type="bigint", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string $author
*
* @ORM\Column(name="author", type="string", length=100, nullable=false)
*/
private $author;
/**
* @var text $content
*
* @ORM\Column(name="content", type="text", nullable=false)
*/
private $content;
/**
* @var datetime $createdAt
*
* @ORM\Column(name="created_at", type="datetime", nullable=false)
*/
private $createdAt;
/**
282
Captulo 3. Cookbook
* @var BlogPost
*
* @ORM\ManyToOne(targetEntity="BlogPost")
* @ORM\JoinColumn(name="post_id", referencedColumnName="id")
*/
private $post;
}
Como voc pode ver, Doctrine converte todos os campos da tabela para propriedades privadas e anotadas da classe. O
mais impressionante que ele descobre o relacionamento com a classe de entidade BlogPost baseada na restrio
de chave estrangeira. Conseqentemente, voc pode encontrar uma propriedade privada $post mapeada com por
uma entidade BlogPost na classe de entidade BlogComment.
O ltimo comando gerou todos os getters e setters para todas as propriedades das duas classes de entidade BlogPost
e BlogComment. As entidades geradas agora esto prontas para serem usadas. Divirta-se!
Como trabalhar com Mltiplos Gerenciadores de Entidade
Voc pode usar mltiplos gerenciadores de entidades em uma aplicao Symfony2. Isto necessrio se voc est
usando bancos de dados diferentes ou algum vendor com conjuntos de entidades completamente diferentes. Em outras
palavras, um gerenciador de entidades que conecta em um banco de dados manipular algumas entidades enquanto
um outro gerenciador de entidades que conecta a um outro banco de dados ir manupular as entidades restantes.
Nota: Usar mltiplos gerenciadores de entidade muito fcil, mas mais avanado e geralmente no necessrio.
Certifique se voc realmente precisa de mltiplos gerenciadores de entidades antes de adicionar esta camada de complexibilidade.
O cdigo de configurao seguinte mostra como configurar dois gerenciadores de entidade:
YAML
doctrine:
orm:
default_entity_manager:
default
entity_managers:
default:
connection:
default
mappings:
AcmeDemoBundle: ~
AcmeStoreBundle: ~
customer:
connection:
customer
mappings:
AcmeCustomerBundle: ~
Neste caso, voc deve definir dois gerenciadores de entidade e cham-los de default e customer. O gerenciador
de entidade default manipula as entidades em AcmeDemoBundle e AcmeStoreBundle, enquanto o gerenciador de entidades customer manipula as entidades AcmeCustomerBundle.
Quando estiver trabalhando com mltiplos gerenciadores de entidade, voc deve ser explcito sobre qual gerenciador
de entidade voc quer. Se voc omitir o nome do gerenciador de entidade quando voc atualizar o seu schema, ser
usado o padro (ou seja, default):
# Play only with "default" mappings
php app/console doctrine:schema:update --force
3.1. Cookbook
283
Se voc omitir o nome do gerenciador de entidade ao solicitar ele, o gerenciador de entidade padro (ou seja,
default) retornado:
class UserController extends Controller
{
public function indexAction()
{
// both return the "default" em
$em = $this->get(doctrine)->getManager();
$em = $this->get(doctrine)->getManager(default);
$customerEm =
$this->get(doctrine)->getManager(customer);
}
}
Agora voc pode usar Doctrine exatamente da mesma forma que voc fez antes - usando o gerenciador de entidade
default para persistir e buscar as entidades que ele gerencia, e o gerenciador de entidade customer para persistir
e buscar suas entidades.
O mesmo se aplica para chamadas de repositrio:
class UserController extends Controller
{
public function indexAction()
{
// Retrieves a repository managed by the "default" em
$products = $this->get(doctrine)
->getRepository(AcmeStoreBundle:Product)
->findAll();
// Explicit way to deal with the "default" em
$products = $this->get(doctrine)
->getRepository(AcmeStoreBundle:Product, default)
->findAll();
// Retrieves a repository managed by the "customer" em
$customers = $this->get(doctrine)
->getRepository(AcmeCustomerBundle:Customer, customer)
->findAll();
}
}
284
Captulo 3. Cookbook
entity_managers:
default:
# ...
dql:
string_functions:
test_string: Acme\HelloBundle\DQL\StringFunction
second_string: Acme\HelloBundle\DQL\SecondStringFunction
numeric_functions:
test_numeric: Acme\HelloBundle\DQL\NumericFunction
datetime_functions:
test_datetime: Acme\HelloBundle\DQL\DatetimeFunction
XML
<doctrine:config>
<doctrine:orm>
<!-- ... -->
<doctrine:entity-manager name="default">
<!-- ... -->
<doctrine:dql>
<doctrine:string-function name="test_string>Acme\HelloBundle\DQL\StringFunct
<doctrine:string-function name="second_string>Acme\HelloBundle\DQL\SecondStr
<doctrine:numeric-function name="test_numeric>Acme\HelloBundle\DQL\NumericFu
<doctrine:datetime-function name="test_datetime>Acme\HelloBundle\DQL\Datetim
</doctrine:dql>
</doctrine:entity-manager>
</doctrine:orm>
</doctrine:config>
</container>
PHP
// app/config/config.php
$container->loadFromExtension(doctrine, array(
orm => array(
...,
entity_managers => array(
default => array(
...,
dql => array(
string_functions => array(
test_string
=> Acme\HelloBundle\DQL\StringFunction,
second_string => Acme\HelloBundle\DQL\SecondStringFunction,
),
numeric_functions => array(
test_numeric => Acme\HelloBundle\DQL\NumericFunction,
),
datetime_functions => array(
test_datetime => Acme\HelloBundle\DQL\DatetimeFunction,
),
),
),
3.1. Cookbook
285
),
),
));
Voc tem uma entidade simples User mapeada para o banco de dados:
// src/Acme/AccountBundle/Entity/User.php
namespace Acme\AccountBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity
* @UniqueEntity(fields="email", message="Email already taken")
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank()
* @Assert\Email()
*/
protected $email;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank()
*/
protected $plainPassword;
public function getId()
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
286
Captulo 3. Cookbook
Esta entidade User contm trs campos, e dois deles (email e plainPassword) devem ser exibos no formulrio.
A propriedade e-mail deve ser nica no banco de dados, isto aplicado atravs da adio da validao no topo da
classe.
Nota: Se voc quiser integrar este User com o sistema de segurana, voc precisa implementar a UserInterface do
componente de segurana.
3.1. Cookbook
287
}
}
A opo
O formulrio que voc vai usar para a pgina de registo no ser o mesmo usado para modificar o User (ou seja,
UserType). O formulrio de registro conter novos campos como o aceitar os termos, cujo valor no ser armazenado no banco de dados.
Comece criando uma classe simples que representa o registro:
// src/Acme/AccountBundle/Form/Model/Registration.php
namespace Acme\AccountBundle\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
use Acme\AccountBundle\Entity\User;
class Registration
{
/**
* @Assert\Type(type="Acme\AccountBundle\Entity\User")
*/
protected $user;
/**
* @Assert\NotBlank()
* @Assert\True()
*/
protected $termsAccepted;
public function setUser(User $user)
{
$this->user = $user;
}
public function getUser()
{
return $this->user;
}
public function getTermsAccepted()
{
return $this->termsAccepted;
}
public function setTermsAccepted($termsAccepted)
{
$this->termsAccepted = (Boolean) $termsAccepted;
}
}
288
Captulo 3. Cookbook
Voc no precisa usar um mtodo especial para incorporar o formulrio UserType. Um formulrio tambm
um campo - logo, voc pode adicionar ele como qualquer outro campo, com a certeza de que a propriedade
Registration.user ir manter uma instncia da classe User.
Manuseando a Submisso do Formulrio
Em seguida, voc precisa de um controlador para lidar com o formulrio. Comece criando um controlador simples
para exibir o formulrio de registro:
// src/Acme/AccountBundle/Controller/AccountController.php
namespace Acme\AccountBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Acme\AccountBundle\Form\Type\RegistrationType;
use Acme\AccountBundle\Form\Model\Registration;
class AccountController extends Controller
{
public function registerAction()
{
$form = $this->createForm(
new RegistrationType(),
new Registration()
);
return $this->render(
AcmeAccountBundle:Account:register.html.twig,
array(form => $form->createView())
);
3.1. Cookbook
289
}
}
e o seu template:
{# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #}
<form action="{{ path(create)}}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
Por fim, adicione o controlador que lida com a submisso do formulrio. Ele realiza a validao e salva os dados no
banco de dados:
public function createAction()
{
$em = $this->getDoctrine()->getEntityManager();
$form = $this->createForm(new RegistrationType(), new Registration());
$form->bind($this->getRequest());
if ($form->isValid()) {
$registration = $form->getData();
$em->persist($registration->getUser());
$em->flush();
return $this->redirect(...);
}
return $this->render(
AcmeAccountBundle:Account:register.html.twig,
array(form => $form->createView())
);
}
Pronto! O seu formulrio agora valida e permite que voc salve o objeto User no banco de dados. O checkbox extra
terms na classe de modelo Registration utilizado durante a validao, mas no utilizado posteriormente
quando salvamos o usurio no banco de dados.
3.1.6 Formulrio
Como personalizar a Renderizao de Formulrios
O Symfony dispe de uma grande variedade de maneiras para personalizar como um formulrio renderizado. Neste
guia, voc vai aprender a personalizar cada parte possvel do seu formulrio com o menor esforo, se voc usa o Twig
ou PHP como sua templating engine.
Noes Bsicas sobre a Renderizao de Formulrios
Lembre-se que o widget HTML, a label e o erro de um campo do formulrio podem ser facilmente renderizados
usando a funo Twig form_row ou o mtodo helper PHP row:
Twig
290
Captulo 3. Cookbook
{{ form_row(form.age) }}
PHP
<?php echo $view[form]->row($form[age]) }} ?>
Voc tambm pode renderizar cada uma das trs partes do campo individualmente:
Twig
<div>
{{ form_label(form.age) }}
{{ form_errors(form.age) }}
{{ form_widget(form.age) }}
</div>
PHP
<div>
<?php echo $view[form]->label($form[age]) }} ?>
<?php echo $view[form]->errors($form[age]) }} ?>
<?php echo $view[form]->widget($form[age]) }} ?>
</div>
Em ambos os casos, a label do formulrio, os erros e o widget HTML so renderizados usando um conjunto de
marcao que vem por padro com o symfony. Por exemplo, ambos os templates acima seriam renderizados da
seguinte forma:
<div>
<label for="form_age">Age</label>
<ul>
<li>This field is required</li>
</ul>
<input type="number" id="form_age" name="form[age]" />
</div>
Para prottipos rpidos e para testar um formulrio, voc pode renderizar todo o formulrio com apenas uma linha:
Twig
{{ form_widget(form) }}
PHP
<?php echo $view[form]->widget($form) }} ?>
O restante desta receita ir explicar como cada parte de marcao do formulrio pode ser modificada em vrios nveis
diferentes. Para mais informaes sobre a renderizao do formulrio em geral, consulte Renderizando um formulrio
em um Template.
O que so Temas de Formulrio?
O Symfony utiliza fragmentos de formulrio - um pequeno pedao de um template que renderiza apenas uma parte
de um formulrio - para renderizar cada parte de um formulrio - labels de campo, os erros, campos de texto input,
tags select, etc
Os fragmentos so definidos como blocos no Twig e como arquivos de template no PHP.
3.1. Cookbook
291
Um tema nada mais do que um conjunto de fragmentos que voc deseja usar quando est renderizando um formulrio.
Em outras palavras, se voc quiser personalizar uma parte de como um formulrio processado, voc vai importar um
tema que contm uma personalizao dos fragmentos apropriados do formulrio.
O Symfony vem com um tema padro (form_div_layout.html.twig no Twig e FrameworkBundle:Form no PHP)
que define cada fragmento necessrio para renderizar cada parte de um formulrio.
Na seo seguinte, voc vai aprender a personalizar um tema, sobrescrevendo alguns ou todos os seus fragmentos.
Por exemplo, quando o widget de um campo do tipo integer renderizado, um campo input number gerado
Twig
{{ form_widget(form.age) }}
PHP
<?php echo $view[form]->widget($form[age]) ?>
renderiza:
<input type="number" id="form_age" name="form[age]" required="required" value="33" />
Internamente, o symfony usa o fragmento integer_widget para renderizar o campo. Isso ocorre porque o tipo de
campo integer e voc est renderizando seu widget (em oposio a sua label ou errors).
No Twig ele seria por padro o bloco integer_widget do template form_div_layout.html.twig.
No
PHP
ele
seria
o
arquivo
integer_widget.html.php
FrameworkBundle/Resources/views/Form.
localizado
no
diretrio
PHP
Como voc pode ver, este prprio fragmento renderiza outro fragmento - field_widget:
Twig
{# form_div_layout.html.twig #}
{% block field_widget %}
{% set type = type|default(text) %}
<input type="{{ type }}" {{ block(widget_attributes) }} value="{{ value }}" />
{% endblock field_widget %}
PHP
<!-- FrameworkBundle/Resources/views/Form/field_widget.html.php -->
<input
type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
value="<?php echo $view->escape($value) ?>"
<?php echo $view[form]->renderBlock(attributes) ?>
/>
292
Captulo 3. Cookbook
O ponto , os fragmentos ditam a sada HTML de cada parte de um formulrio. Para personalizar a sada do formulrio,
voc s precisa identificar e substituir o fragmento apropriado. O conjunto dessas personalizaes de fragmentos de
formulrio conhecida como tema de formulrio. Ao renderizar um formulrio, voc pode escolher qual(ais)
tema(s) de formulrio deseja aplicar.
No Twig, um tema um nico arquivo de template e os fragmentos so os blocos definidos neste arquivo.
No PHP um tema um diretrio e os fragmentos so os arquivos de template individuais neste diretrio.
Sabendo qual bloco personalizar
Neste exemplo, o nome do fragmento personalizado integer_widget porque voc quis sobrescrever o
widget HTML para todos os tipos de campo integer. Se voc precisa personalizar campos textarea, voc
iria personalizar o textarea_widget.
Como voc pode ver, o nome do fragmento uma combinao do tipo do campo e de qual parte do campo est
sendo renderizada (ex.: widget, label, errors, row). Como tal, para personalizar a forma como os erros
so renderizados apenas para campos input text, voc deve personalizar o fragmento text_errors.
Mais comumente, no entanto, voc vai querer personalizar a forma como os erros so exibidos atravs de todos
os campos. Voc pode fazer isso personalizando o fragmento field_errors. Isso aproveita a herana do
tipo de campo. Especificamente, uma vez que o tipo text estende o tipo field, o componente de formulrio
vai procurar primeiro pelo fragmento de tipo especfico (ex., text_errors) antes de voltar ao seu nome de
fragmento pai, no caso dele no existir (ex., field_errors).
Para mais informaes sobre este tpico, consulte Nomeando os fragmentos do formulrio.
Tematizando os Formulrios
Para ver o poder da tematizao de formulrios, suponha que voc queira envolver cada campo input number com
uma tag div. A chave para fazer isso personalizar o fragmento integer_widget.
Tematizao de Formulrios no Twig
Ao personalizar o bloco de campo de formulrio no Twig, voc tem duas opes de onde o bloco de formulrio
personalizado pode residir:
Mtodo
Dentro do mesmo template que o
formulrio
Dentro de um template separado
Prs
Rpido e fcil
Pode ser reutilizado por muitos
templates
Contras
No pode ser reutilizado em outros
templates
Requer que um template extra seja
criado
3.1. Cookbook
293
</div>
{% endblock %}
{% block content %}
{# ... render the form #}
{{ form_row(form.age) }}
{% endblock %}
Ao usar a tag especial {% form_theme form _self %}, o Twig procura dentro do mesmo template por qualquer blocos de formulrios sobrescritos. Assumindo que o campo form.age um tipo de campo integer, quando
o widget renderizado, o bloco integer_widget personalizado ir ser utilizado.
A desvantagem deste mtodo que o bloco de formulrio personalizado no pode ser reutilizado ao renderizar outros
formulrios em outros templates. Em outras palavras, este mtodo mais til ao fazer personalizaes de formulrios
que so especficas para um nico formulrio em sua aplicao. Se voc quiser reutilizar uma personalizao em
vrios (ou todos) os formulrios de sua aplicao, leia a prxima seo.
Mtodo 2: Dentro de um Template Separado Voc tambm pode optar por colocar o bloco de formulrio
integer_widget personalizado em um template totalmente separado. O cdigo e o resultado final o mesmo,
mas agora voc pode reutilizar a personalizao de formulrio em muitos templates:
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default(number) %}
{{ block(field_widget) }}
</div>
{% endblock %}
Agora que voc j criou o bloco de formulrio personalizado, voc precisa dizer ao Symfony para us-lo. Dentro
do template onde voc est renderizando o seu formulrio, diga ao Symfony para usar o template atravs da tag
form_theme:
{% form_theme form AcmeDemoBundle:Form:fields.html.twig %}
{{ form_widget(form.age) }}
Quando o widget form.age renderizado, o Symfony usar o bloco integer_widget do novo template e a tag
input vai ser envolvida no elemento div especificado no bloco personalizado.
Tematizando Formulrios em PHP
Ao usar o PHP como templating engine, o nico mtodo de personalizar um fragmento criar um novo arquivo
template - semelhante ao segundo mtodo utilizado pelo Twig.
O arquivo de template deve ser nomeado aps o fragmento.
Voc deve criar um arquivo
integer_widget.html.php para personalizar o fragmento integer_widget.
Agora que voc criou o template de formulrio personalizado, voc precisa dizer ao Symfony para us-lo. Dentro do
template onde voc est renderizando o seu formulrio, diga ao Symfony para usar o tema atravs do mtodo helper
setTheme:
294
Captulo 3. Cookbook
Quando o widget form.age renderizado, o Symfony vai usar o template integer_widget.html.php personalizado e a tag input ser envolvida pelo elemento div.
Referenciando Blocos de Formulrio Base (especfico para o Twig)
At agora, para sobrescrever um bloco de formulrio em particular, o melhor mtodo copiar o bloco padro de
form_div_layout.html.twig, col-lo em um template diferente, e, em seguida, personaliz-lo. Em muitos casos, voc
pode evitar ter que fazer isso atravs da referncia ao bloco base quando for personaliz-lo.
Isso fcil de fazer, mas varia um pouco dependendo se as personalizaes de seu bloco de formulrio esto no mesmo
template que o formulrio ou em um template separado.
Referenciando Blocos no interior do mesmo Template que o Formulrio
use no template onde voc est renderizando o formulrio:
Nota: No possvel fazer referncia ao bloco base quando se utiliza o PHP como template engine. Voc tem que
copiar manualmente o contedo do bloco base para o seu novo arquivo de template.
Se voc deseja que uma certa personalizao de formulrio seja global para a sua aplicao, voc pode conseguir isso
fazendo as personalizaes de formulrio em um template externo e depois import-lo dentro da sua configurao da
aplicao:
3.1. Cookbook
295
Twig Usando a seguinte configurao, quaisquer blocos de formulrios personalizados dentro do template
AcmeDemoBundle:Form:fields.html.twig sero usados globalmente quando um formulrio renderizado.
YAML
# app/config/config.yml
twig:
form:
resources:
- AcmeDemoBundle:Form:fields.html.twig
# ...
XML
<!-- app/config/config.xml -->
<twig:config ...>
<twig:form>
<resource>AcmeDemoBundle:Form:fields.html.twig</resource>
</twig:form>
<!-- ... -->
</twig:config>
PHP
// app/config/config.php
$container->loadFromExtension(twig, array(
form => array(
resources => array(
AcmeDemoBundle:Form:fields.html.twig,
),
),
// ...
));
Por padro, o Twig usa um layout div ao renderizar os formulrios. Algumas pessoas, no entanto, podem preferir
renderizar formulrios em um layout de tabela. Para isso use o recurso form_table_layout.html.twig como
layout:
YAML
# app/config/config.yml
twig:
form:
resources: [form_table_layout.html.twig]
# ...
XML
<!-- app/config/config.xml -->
<twig:config ...>
<twig:form>
<resource>form_table_layout.html.twig</resource>
</twig:form>
<!-- ... -->
</twig:config>
PHP
// app/config/config.php
$container->loadFromExtension(twig, array(
296
Captulo 3. Cookbook
Se voc quer fazer a alterao somente em um template, adicione a seguinte linha em seu arquivo de template em vez
de adicionar o template como um recurso:
{% form_theme form form_table_layout.html.twig %}
Note que a varivel form no cdigo acima a varivel de viso do formulrio que voc passou para o seu template.
PHP Usando a seguinte configurao, quaisquer fragmentos de formulrios personalizados no interior do diretrio
src/Acme/DemoBundle/Resources/views/Form sero usados globalmente quando um formulrio renderizado.
YAML
# app/config/config.yml
framework:
templating:
form:
resources:
- AcmeDemoBundle:Form
# ...
XML
<!-- app/config/config.xml -->
<framework:config ...>
<framework:templating>
<framework:form>
<resource>AcmeDemoBundle:Form</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config>
PHP
// app/config/config.php
// PHP
$container->loadFromExtension(framework, array(
templating => array(
form => array(
resources => array(
AcmeDemoBundle:Form,
),
),
),
// ...
));
3.1. Cookbook
297
Por padro, a engine PHP usa um layout div ao renderizar formulrios. Algumas pessoas, no entanto, podem preferir
renderizar formulrios em um layout de tabela. Para isso use o recurso FrameworkBundle:FormTable como
layout:
YAML
# app/config/config.yml
framework:
templating:
form:
resources:
- FrameworkBundle:FormTable
XML
<!-- app/config/config.xml -->
<framework:config ...>
<framework:templating>
<framework:form>
<resource>FrameworkBundle:FormTable</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config>
PHP
// app/config/config.php
$container->loadFromExtension(framework, array(
templating => array(
form => array(
resources => array(
FrameworkBundle:FormTable,
),
),
),
// ...
));
Se voc quer fazer a alterao somente em um template, adicione a seguinte linha em seu arquivo de template em vez
de adicionar o template como um recurso:
<?php $view[form]->setTheme($form, array(FrameworkBundle:FormTable)); ?>
Note que a varivel $form no cdigo acima a varivel de viso do formulrio que voc passou para o seu template.
Como personalizar um campo individual
At agora, voc viu as diferentes maneiras que pode personalizar a sada dos widgets de todos os tipos de campo
texto. Voc tambm pode personalizar campos individuais. Por exemplo, supondo que voc tenha dois campos
text - first_name e last_name - mas voc s quer personalizar um dos campos. Isto pode ser feito pela
personalizao de um fragmento cujo nome uma combinao do atributo id do campo e qual parte do campo est
sendo personalizada. Por exemplo:
Twig
{% form_theme form _self %}
298
Captulo 3. Cookbook
{% block _product_name_widget %}
<div class="text_widget">
{{ block(field_widget) }}
</div>
{% endblock %}
{{ form_widget(form.name) }}
PHP
<!-- Main template -->
<?php echo $view[form]->setTheme($form, array(AcmeDemoBundle:Form)); ?>
<?php echo $view[form]->widget($form[name]); ?>
<!-- src/Acme/DemoBundle/Resources/views/Form/_product_name_widget.html.php -->
<div class="text_widget">
echo $view[form]->renderBlock(field_widget) ?>
</div>
Aqui, o fragmento _product_name_widget define o template a ser usado para o campo cujo id
product_name (e o nome product[name]).
Dica: A parte product do campo o nome do formulrio, que pode ser definido manualmente ou gerado automaticamente com base no nome do tipo de formulrio (por exemplo, ProductType equivale ao product). Se voc
no tem certeza de qual o nome de seu formulrio, apenas visualize o cdigo fonte do formulrio gerado.
Voc tambm pode sobrescrever a marcao de uma linha inteira de campo utilizando o mesmo mtodo:
Twig
{# _product_name_row.html.twig #}
{% form_theme form _self %}
{% block _product_name_row %}
<div class="name_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock %}
PHP
<!-- _product_name_row.html.php -->
<div class="name_row">
<?php echo $view[form]->label($form) ?>
<?php echo $view[form]->errors($form) ?>
<?php echo $view[form]->widget($form) ?>
</div>
At agora, esta receita tem demonstrado diversas maneiras de personalizar um nico pedao de como um formulrio
renderizado. A chave personalizar um fragmento especfico que corresponde parte do formulrio que voc deseja
3.1. Cookbook
299
PHP
<?php echo $view[form]->errors($form[age]); ?>
Para sobrecrever como os erros so renderizados para todos os campos, basta copiar, colar e personalizar o fragmento
field_errors.
Twig
{# fields_errors.html.twig #}
{% block field_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<ul class="error_list">
{% for error in errors %}
<li>{{ error.messageTemplate|trans(error.messageParameters, validators) }}</li
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock field_errors %}
PHP
<!-- fields_errors.html.php -->
<?php if ($errors): ?>
<ul class="error_list">
<?php foreach ($errors as $error): ?>
<li><?php echo $view[translator]->trans(
$error->getMessageTemplate(),
$error->getMessageParameters(),
validators
) ?></li>
<?php endforeach; ?>
</ul>
<?php endif ?>
300
Captulo 3. Cookbook
Dica: Veja Tematizando os Formulrios para saber como aplicar essa personalizao.
Voc tambm pode personalizar a sada de erro de apenas um tipo de campo especfico. Por exemplo, certos erros que
so mais globais para o seu formulrio (ou seja, no especfico para apenas um campo) so renderizados separadamente, geralmente no topo do seu formulrio:
Twig
{{ form_errors(form) }}
PHP
<?php echo $view[form]->render($form); ?>
Para personalizar apenas a marcao usada para esses erros, siga as mesmas instrues acima, mas agora chame o
bloco form_errors (Twig) / o arquivo form_errors.html.php (PHP). Agora, quando os erros para o tipo
form so renderizados, o seu fragmento personalizado ser usado em vez do padro field_errors.
Personalizando a Linha de Formulrio Quando voc pode gerenci-lo, a maneira mais fcil de renderizar um
campo de formulrio atravs da funo form_row, que renderiza a label, os erros e o widget HTML de um campo.
Para personalizar a marcao usada para renderizar todas as linhas de campo do formulrio, sobrescreva o fragmento
field_row. Por exemplo, suponha que voc deseja adicionar uma classe para o elemento div que envolve cada
linha:
Twig
{# field_row.html.twig #}
{% block field_row %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock field_row %}
PHP
<!-- field_row.html.php -->
<div class="form_row">
<?php echo $view[form]->label($form) ?>
<?php echo $view[form]->errors($form) ?>
<?php echo $view[form]->widget($form) ?>
</div>
Dica: Veja Tematizando os Formulrios para saber como aplicar essa personalizao.
Adicionando um Asterisco para as Labels de Campo Obrigatrias Se voc quiser indicar todos os seus campos
obrigatrios com um asterisco (*), voc pode fazer isso personalizando o fragmento field_label.
No Twig, se voc estiver fazendo a personalizao de formulrio dentro do mesmo template que o seu formulrio,
modifique a tag use e adicione o seguinte:
{% use form_div_layout.html.twig with field_label as base_field_label %}
{% block field_label %}
{{ block(base_field_label) }}
3.1. Cookbook
301
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %}
No Twig, se voc estiver fazendo a personalizao do formulrio dentro de um template separado, use o seguinte:
{% extends form_div_layout.html.twig %}
{% block field_label %}
{{ parent() }}
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %}
Ao usar o PHP como template engine voc tem que copiar o contedo do template original:
<!-- field_label.html.php -->
Dica: Veja Tematizando os Formulrios para saber como aplicar essa personalizao.
Adicionando mensagens de ajuda Voc tambm pode personalizar os widgets do formulrio para ter uma mensagem opcional de ajuda.
No Twig, se voc est fazendo a personalizao do formulrio dentro do mesmo template que o seu formulrio,
modifique a tag use e adicione o seguinte:
{% use form_div_layout.html.twig with field_widget as base_field_widget %}
{% block field_widget %}
{{ block(base_field_widget) }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
{% endblock %}
No Twig, se voc est fazendo a personalizao do formulrio dentro de um template separado, use o seguinte:
{% extends form_div_layout.html.twig %}
{% block field_widget %}
{{ parent() }}
{% if help is defined %}
<span class="help">{{ help }}</span>
302
Captulo 3. Cookbook
{% endif %}
{% endblock %}
Ao usar o PHP como template engine voc tem que copiar o contedo do template original:
<!-- field_widget.html.php -->
<!-- Original content -->
<input
type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
value="<?php echo $view->escape($value) ?>"
<?php echo $view[form]->renderBlock(attributes) ?>
/>
<!-- Customization -->
<?php if (isset($help)) : ?>
<span class="help"><?php echo $view->escape($help) ?></span>
<?php endif ?>
Para renderizar uma mensagem de ajuda abaixo de um campo, passe em uma varivel help:
Twig
{{ form_widget(form.title, {help: foobar}) }}
PHP
<?php echo $view[form]->widget($form[title], array(help => foobar)) ?>
A maioria das funes disponveis para renderizar diferentes partes de um formulrio (por exemplo, o widget de
formulrio, a label do formulrio, os erros de formulrio, etc) tambm permitem que voc faa certas personalizaes
diretamente. Veja o exemplo a seguir:
Twig
{# render a widget, but add a "foo" class to it #}
{{ form_widget(form.name, { attr: {class: foo} }) }}
PHP
<!-- render a widget, but add a "foo" class to it -->
<?php echo $view[form]->widget($form[name], array(
attr => array(
class => foo,
),
)) ?>
O array passado como segundo argumento contm variveis de formulrio. Para mais detalhes sobre este conceito
no Twig, veja twig-reference-form-variables.
3.1. Cookbook
303
Primeiro, crie uma classe IssueToNumberTransformer - esta classe ser responsvel por converter de e para o nmero
do issue e o objeto Issue:
// src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php
namespace Acme\TaskBundle\Form\DataTransformer;
use
use
use
use
Symfony\Component\Form\DataTransformerInterface;
Symfony\Component\Form\Exception\TransformationFailedException;
Doctrine\Common\Persistence\ObjectManager;
Acme\TaskBundle\Entity\Issue;
304
Captulo 3. Cookbook
/**
* Transforms a string (number) to an object (issue).
*
* @param string $number
* @return Issue|null
* @throws TransformationFailedException if object (issue) is not found.
*/
public function reverseTransform($number)
{
if (!$number) {
return null;
}
$issue = $this->om
->getRepository(AcmeTaskBundle:Issue)
->findOneBy(array(number => $number))
;
if (null === $issue) {
throw new TransformationFailedException(sprintf(
An issue with number "%s" does not exist!,
$number
));
}
return $issue;
}
}
Dica: Se voc deseja que um novo issue seja criado quando um nmero desconhecido informado, voc pode
instanci-lo ao invs de gerar uma TransformationFailedException.
Usando o Transformador
Agora que voc j tem o transformador construdo, voc s precisa adicion-lo ao seu campo issue de alguma forma.
Voc tambm pode usar transformadores sem criar um novo tipo de formulrio personalizado chamando
addModelTransformer (ou addViewTransformer - ver Transformadores de Modelo e Viso) )
em qualquer builder de campo:
use Symfony\Component\Form\FormBuilderInterface;
use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
// this assumes that the entity manager was passed in as an option
$entityManager = $options[em];
$transformer = new IssueToNumberTransformer($entityManager);
// add a normal text field, but add our transformer to it
$builder->add(
$builder->create(issue, text)
3.1. Cookbook
305
->addModelTransformer($transformer)
);
}
// ...
}
Este exemplo requer que voc passe no gerenciador de entidade como uma opo ao criar o seu formulrio. Mais tarde,
voc vai aprender como criar um tipo de campo issue personalizado para evitar ter de fazer isso no seu controlador:
$taskForm = $this->createForm(new TaskType(), $task, array(
em => $this->getDoctrine()->getEntityManager(),
));
Legal, est feito! O usurio poder informar um nmero de issue no campo texto e ele ser transformado novamente
em um objeto Issue. Isto significa que, aps um bind bem sucedido, o framework de Formulrio passar um objeto
Issue real para o Task::setIssue() em vez do nmero do issue.
Se o issue no for encontrado, um erro de formulrio ser criado para esse campo e sua mensagem de erro pode ser
controlada com a opo do campo invalid_message.
Cuidado: Note que a adio de um transformador exige a utilizao de uma sintaxe um pouco mais complicada
ao adicionar o campo. O cdigo seguinte est errado, j que o transformador ser aplicado todo o formulrio,
em vez de apenas este campo:
// ISTO EST ERRADO - O TRANSFORMADOR SER APLICADA A TODO O FORMULRIO
// Veja o exemplo acima para o cdigo correto
$builder->add(issue, text)
->addModelTransformer($transformer);
306
Captulo 3. Cookbook
No nosso exemplo, o campo um campo text, e ns sempre esperamos que um campo texto seja um formato escalar
simples, nos formatos normalizado e viso. Por esta razo, o transformador mais apropriado o transformador
de modelo (que converte de/para o formato normalizado - nmero de issue string - para o formato modelo - objeto
Issue).
A diferena entre os transformadores sutil e voc deve sempre pensar sobre o que o dado normalizado para um
campo deve realmente ser. Por exemplo, o dado normalizado para um campo text uma string, mas um objeto
DateTime para um campo date.
Usando Transformadores em um tipo de campo personalizado
No exemplo acima, voc aplicou o transformador para um campo text normal. Isto foi fcil, mas tem duas desvantagens:
1) Voc precisa lembrar de aplicar o transformador sempre que voc est adicionando um campo para nmeros de
issue
2) Voc precisa se preocupar em sempre passar a opo em quando voc est criando um formulrio que usa o transformador.
Devido isto, voc pode optar por criar um tipo de campo personalizado. Primeiro, crie a classe do tipo de campo
personalizado:
// src/Acme/TaskBundle/Form/Type/IssueSelectorType.php
namespace Acme\TaskBundle\Form\Type;
use
use
use
use
use
Symfony\Component\Form\AbstractType;
Symfony\Component\Form\FormBuilderInterface;
Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer;
Doctrine\Common\Persistence\ObjectManager;
Symfony\Component\OptionsResolver\OptionsResolverInterface;
3.1. Cookbook
307
{
$transformer = new IssueToNumberTransformer($this->om);
$builder->addModelTransformer($transformer);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
invalid_message => The selected issue does not exist,
));
}
public function getParent()
{
return text;
}
public function getName()
{
return issue_selector;
}
}
Em seguida, registre o seu tipo como um servio e use a tag form.type, para que ele seja reconhecido como um
tipo de campo personalizado:
YAML
services:
acme_demo.type.issue_selector:
class: Acme\TaskBundle\Form\Type\IssueSelectorType
arguments: ["@doctrine.orm.entity_manager"]
tags:
- { name: form.type, alias: issue_selector }
XML
Agora, sempre que voc precisa usar o seu tipo de campo especial issue_selector, muito fcil:
// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(task)
->add(dueDate, null, array(widget => single_text));
->add(issue, issue_selector);
}
308
Captulo 3. Cookbook
Nota: Se esta parte de cdigo em particular ainda no lhe familiar, voc provavelmente ter que dar um passo para
trs e primeiro rever o captulo Formulrios antes de prosseguir.
Vamos assumir, por um momento, que este formulrio utiliza uma classe imaginria Product que possui apenas duas
propriedades relevantes (name e price). O formulrio gerado desta classe ter exatamente a mesma aparncia,
independentemente se um novo Product est sendo criado ou se um produto j existente est sendo editado (por
exemplo, um produto obtido a partir do banco de dados).
Suponha agora, que voc no deseja que o usurio possa alterar o valor de name uma vez que o objeto foi criado. Para
fazer isso, voc pode contar com o sistema de Dispatcher de Evento do Symfony para analisar os dados sobre
o objeto e modificar o formulrio com base nos dados do objeto Product. Neste artigo, voc vai aprender como
adicionar este nvel de flexibilidade aos seus formulrios.
Adicionando um Assinante (Subscriber) de evento uma Classe de Formulrio
Assim, em vez de adicionar diretamente o widget name atravs da sua classe de formulrio ProductType, vamos
delegar a responsabilidade de criar esse campo especfico para um Assinante de Evento:
// src/Acme/DemoBundle/Form/Type/ProductType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType
use Symfony\Component\Form\FormBuilderInterface;
3.1. Cookbook
309
use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$subscriber = new AddNameFieldSubscriber($builder->getFormFactory());
$builder->addEventSubscriber($subscriber);
$builder->add(price);
}
public function getName()
{
return product;
}
}
passado o objeto FormFactory ao construtor do assinante de evento para que o seu novo assinante seja capaz de criar
o widget de formulrio, uma vez que ele notificado do evento despachado durante a criao do formulrio.
Dentro da Classe do Assinante de Evento
O objetivo criar um campo name apenas se o objeto Product subjacente novo (por exemplo, no tenha sido
persistido no banco de dados). Com base nisso, o assinante pode parecer com o seguinte:
// src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php
namespace Acme\DemoBundle\Form\EventListener;
use
use
use
use
Symfony\Component\Form\Event\DataEvent;
Symfony\Component\Form\FormFactoryInterface;
Symfony\Component\EventDispatcher\EventSubscriberInterface;
Symfony\Component\Form\FormEvents;
310
Captulo 3. Cookbook
Cuidado: fcil entender mal o propsito do segmento if (null === $data) deste assinante de evento.
Para entender plenamente o seu papel, voc pode considerar tambm verificar a classe Form e prestar ateno
especial onde o setData() chamado no final do construtor, bem como o mtodo setData() em si.
A linha FormEvents::PRE_SET_DATA resolve para a string form.pre_set_data. A classe FormEvents
serve para propsito organizacional. um local centralizado em que voc pode encontrar todos os vrios eventos
disponveis.
Enquanto este exemplo poderia ter usado o evento form.set_data ou at mesmo o form.post_set_data
com a mesma eficcia, usando o form.pre_set_data voc garante que os dados que esto sendo recuperados
do objeto Event no foram de modo algum modificados por quaisquer outros assinantes ou ouvintes. Isto porque o form.pre_set_data passa um objeto DataEvent em vez do objcto FilterDataEvent passado pelo evento
form.set_data. O DataEvent, ao contrrio de seu filho FilterDataEvent, no tem um mtodo setData().
Nota: Voc pode ver a lista completa de eventos de formulrio atravs da classe FormEvents, encontrada no bundle
de formulrio.
3.1. Cookbook
311
protected $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getTags()
{
return $this->tags;
}
public function setTags(ArrayCollection $tags)
{
$this->tags = $tags;
}
}
Nota: O ArrayCollection especfico do Doctrine e basicamente o mesmo que usar um array (mas deve
ser um ArrayCollection se voc est usando o Doctrine).
Agora, crie uma classe Tag. Como voc viu acima, uma Task pode ter muitos objetos Tag:
// src/Acme/TaskBundle/Entity/Tag.php
namespace Acme\TaskBundle\Entity;
class Tag
{
public $name;
}
Dica: A propriedade name pblica aqui, mas ela pode facilmente ser protegida ou privada (ento seriam necessrios
os mtodos getName e setName).
Agora, vamos para os formulrios. Crie uma classe de formulrio para que um objeto Tag possa ser modificado pelo
usurio:
// src/Acme/TaskBundle/Form/Type/TagType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
312
Captulo 3. Cookbook
{
$builder->add(name);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
data_class => Acme\TaskBundle\Entity\Tag,
));
}
public function getName()
{
return tag;
}
}
Com isso, voc tem o suficiente para renderizar um formulrio tag. Mas, uma vez que o objetivo final permitir que
as tags de uma Task sejam modificadas dentro do prprio formulrio da task, crie um formulrio para a classe Task.
Observe que voc embutiu uma coleo de formulrios TagType usando o tipo de campo collection:
// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(description);
$builder->add(tags, collection, array(type => new TagType()));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
data_class => Acme\TaskBundle\Entity\Task,
));
}
public function getName()
{
return task;
}
}
3.1. Cookbook
313
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class TaskController extends Controller
{
public function newAction(Request $request)
{
$task = new Task();
// dummy code - this is here just so that the Task has some tags
// otherwise, this isnt an interesting example
$tag1 = new Tag();
$tag1->name = tag1;
$task->getTags()->add($tag1);
$tag2 = new Tag();
$tag2->name = tag2;
$task->getTags()->add($tag2);
// end dummy code
$form = $this->createForm(new TaskType(), $task);
// process the form on POST
if ($request->isMethod(POST)) {
$form->bind($request);
if ($form->isValid()) {
// ... maybe do some form processing, like saving the Task and Tag objects
}
}
return $this->render(AcmeTaskBundle:Task:new.html.twig, array(
form => $form->createView(),
));
}
}
O template correspondente agora capaz de renderizar tanto o campo description para o formulrio da task,
quanto todos os formulrios TagType para quaisquer tags que j esto relacionadas com esta Task. No controlador
acima, foi adicionado algum cdigo fictcio para que voc possa ver isso em ao (uma vez que uma Task no tem
nenhuma tag quando ela criada pela primeira vez).
Twig
{# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #}
{# ... #}
<form action="..." method="POST" {{ form_enctype(form) }}>
{# render the tasks only field: description #}
{{ form_row(form.description) }}
<h3>Tags</h3>
<ul class="tags">
{# iterate over each existing tag and render its only field: name #}
{% for tag in form.tags %}
<li>{{ form_row(tag.name) }}</li>
{% endfor %}
</ul>
{{ form_rest(form) }}
314
Captulo 3. Cookbook
{# ... #}
</form>
PHP
<!-- src/Acme/TaskBundle/Resources/views/Task/new.html.php -->
<!-- ... -->
<form action="..." method="POST" ...>
<h3>Tags</h3>
<ul class="tags">
<?php foreach($form[tags] as $tag): ?>
<li><?php echo $view[form]->row($tag[name]) ?></li>
<?php endforeach; ?>
</ul>
<?php echo $view[form]->rest($form) ?>
</form>
<!-- ... -->
Quando o usurio submeter o formulrio, os dados submetidos para os campos Tags so usados para construir um
ArrayCollection de objetos Tag, o qual ento definido no campo tag da instncia Task.
A coleo Tags acessvel naturalmente via $task->getTags() e pode ser persistida no banco de dados ou
utilizada da forma que voc precisar.
At agora, isso funciona muito bem, mas no permite que voc adicione dinamicamente novas tags ou exclua as
tags existentes. Ento, enquanto a edio de tags existentes ir funcionar perfeitamente, o usurio no pode, ainda,
adicionar quaisquer tags novas.
Cuidado:
Neste artigo, voc embutiu apenas uma coleo, mas voc no est limitado a apenas isto.
Voc tambm pode incorporar coleo aninhada com a quantidade de nveis abaixo que desejar. Mas, se
voc usar o Xdebug em sua configurao de desenvolvimento, voc pode receber erro Maximum function
nesting level of 100 reached, aborting!.
Isto ocorre devido a configurao do PHP
xdebug.max_nesting_level, que tem como padro 100.
Esta diretiva limita recurso para 100 chamadas, o que pode no ser o suficiente para renderizar o formulrio
no template se voc renderizar todo o formulrio de uma vez (por exemplo, usando form_widget(form)).
Para corrigir isso, voc pode definir esta diretiva para um valor maior (atravs do arquivo ini do PHP ou via
ini_set, por exemplo em app/autoload.php) ou renderizar cada campo do formulrio manualmente
usando form_row.
Permitir ao usurio adicionar dinamicamente novas tags significa que voc vai precisar usar algum JavaScript. Anteriormente, voc adicionou duas tags ao seu formulrio no controlador. Agora, para permitir ao usurio adicionar a
quantidade de formulrios tag que precisar diretamente no navegador, vamos utilizar um pouco de JavaScript.
A primeira coisa que voc precisa fazer tornar a coleo de formulrio ciente de que ela vai receber um nmero
desconhecido de tags. At agora, voc adicionou duas tags e o tipo formulrio espera receber exatamente duas, caso
contrrio, um erro ser lanado: Este formulrio no deve conter campos extras. Para tornar isto
flexvel, adicione a opo allow_add no seu campo de coleo:
// src/Acme/TaskBundle/Form/Type/TaskType.php
3.1. Cookbook
315
// ...
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(description);
$builder->add(tags,
type
=>
allow_add
=>
by_reference =>
));
collection, array(
new TagType(),
true,
false,
Note que by_reference => false tambm foi adicionado. Normalmente, o framework de formulrio ir
modificar as tags em um objeto Task sem realmente nunca chamar setTags. Definindo by_reference para false, o
setTags ser chamado. Voc ver que isto ser importante mais tarde.
Alm de dizer ao campo para aceitar qualquer nmero de objetos submetidos, o allow_add tambm disponibiliza
para voc uma varivel prototype. Este prototype um template que contm todo o HTML para poder renderizar
quaisquer formulrios tag novos. Para renderiz-lo, faa a seguinte alterao no seu template:
Twig
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}">
...
</ul>
PHP
Nota: Se voc renderizar todo o seu sub-formulrio tags de uma vez (por exemplo form_row(form.tags)),
ento o prototype est automaticamente disponvel na div externa, no atributo data-prototype, semelhante ao
que voc v acima.
Dica: O form.tags.vars.prototype um elemento de formulrio com o aspecto semelhante aos elementos
individuais form_widget(tag) dentro do seu lao for. Isso significa que voc pode chamar form_widget,
form_row ou form_label nele. Voc pode at mesmo optar por renderizar apenas um de seus campos (por
exemplo, o campo name):
{{ form_widget(form.tags.vars.prototype.name)|e }}
O objetivo desta seo ser usar JavaScript para ler este atributo e dinamicamente adicionar novos formulrios tag
quando o usurio clicar no link Adicionar uma tag. Para tornar as coisas simples, este exemplo usa jQuery e assume
que voc o incluiu em algum lugar na sua pgina.
Adicione uma tag script em algum lugar na sua pgina para que voc possa comear a escrever um pouco de
JavaScript.
316
Captulo 3. Cookbook
Primeiro, adicione um link no final da lista tags via JavaScript. Segundo, faa o bind do evento click desse link
para que voc possa adicionar um novo formulrio de tag (addTagForm ser exibido em seguida):
// Get the ul that holds the collection of tags
var collectionHolder = $(ul.tags);
// setup an "add a tag" link
var $addTagLink = $(<a href="#" class="add_tag_link">Add a tag</a>);
var $newLinkLi = $(<li></li>).append($addTagLink);
jQuery(document).ready(function() {
// add the "add a tag" anchor and li to the tags ul
collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
collectionHolder.data(index, collectionHolder.find(:input).length);
$addTagLink.on(click, function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm(collectionHolder, $newLinkLi);
});
});
O trabalho da funo addTagForm ser usar o atributo data-prototype para adicionar dinamicamente um
novo formulrio quando clicado neste link. O HTML data-prototype contm o elemento de entrada text
com um nome de task[tags][__name__][name] e com o id task_tags___name___name. O nome
__name__ um pequeno placeholder, que voc vai substituir por um nmero nico, incrementado (por exemplo: task[tags][3][name]).
Novo na verso 2.1: O placeholder foi alterado de $$name$$ para __name__ no Symfony 2.1
O cdigo real necessrio para fazer todo este trabalho pode variar um pouco, mas aqui est um exemplo:
function addTagForm(collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = collectionHolder.data(prototype);
// get the new index
var index = collectionHolder.data(index);
// Replace __name__ in the prototypes HTML to
// instead be a number based on the current collections length.
var newForm = prototype.replace(/__name__/g, collectionHolder.children().length);
// increase the index with one for the next item
collectionHolder.data(index, index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $(<li></li>).append(newForm);
$newLinkLi.before($newFormLi);
}
Nota: melhor separar o seu javascript em arquivos JavaScript do que escrev-lo dentro do HTML como foi feito
aqui.
3.1. Cookbook
317
Agora, cada vez que um usurio clicar no link Adicionar uma tag, um novo sub-formulrio vai aparecer na
pgina. Quando o formulrio submetido, todos os novos formulrios de tag sero convertidos em novos objetos Tag
e adicionados propriedade tags do objeto Task.
318
Captulo 3. Cookbook
YAML
# src/Acme/TaskBundle/Resources/config/doctrine/Task.orm.yml
Acme\TaskBundle\Entity\Task:
type: entity
# ...
oneToMany:
tags:
targetEntity: Tag
cascade:
[persist]
XML
<!-- src/Acme/TaskBundle/Resources/config/doctrine/Task.orm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Acme\TaskBundle\Entity\Task" ...>
<!-- ... -->
<one-to-many field="tags" target-entity="Tag">
<cascade>
<cascade-persist />
</cascade>
</one-to-many>
</entity>
</doctrine-mapping>
Um segundo problema potencial aborda o Lado Proprietrio e Lado Inverso dos relacionamentos do Doctrine.
Neste exemplo, se o lado proprietrio da relao Task, ento a persistncia ir funcionar bem pois as tags
so devidamente adicionadas Task. No entanto, se o lado proprietrio a Tag, ento voc vai ter um pouco
mais de trabalho para garantir que o lado correto da relao ser modificado.
O truque ter certeza de que uma nica Task definida em cada Tag. Uma maneira fcil de fazer isso
adicionar alguma lgica extra ao setTags(), que chamada pelo framework de formulrio desde que
by_reference esteja definido como false:
// src/Acme/TaskBundle/Entity/Task.php
// ...
public function setTags(ArrayCollection $tags)
3.1.
{ Cookbook
319
O passo seguinte permitir a remoo de um item em particular na coleo. A soluo similar a que permite que as
tags sejam adicionadas.
Comece adicionando a opo allow_delete no tipo do formulrio:
// src/Acme/TaskBundle/Form/Type/TaskType.php
// ...
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add(description);
$builder->add(tags,
type
=>
allow_add
=>
allow_delete =>
by_reference =>
));
collection, array(
new TagType(),
true,
true,
false,
Modificaes nos Templates A opo allow_delete tem uma consequncia: se um item de uma coleo no
for enviado na submisso, o dado relacionado removido da coleo no servidor. A soluo , portanto, remover o
elemento de formulrio do DOM.
Primeiro, adicione um link excluir esta tag para cada formulrio de tag:
jQuery(document).ready(function() {
// add a delete link to all of the existing tag form li elements
collectionHolder.find(li).each(function() {
addTagFormDeleteLink($(this));
});
// ... the rest of the block from above
});
function addTagForm() {
// ...
// add a delete link to the new form
addTagFormDeleteLink($newFormLi);
}
320
Captulo 3. Cookbook
});
}
Quando um formulrio de tag removido do DOM e submetido, o objeto Tag removido no ser includo na coleo
passada ao setTags. Dependendo de sua camada de persistncia, isto pode ou no ser o suficiente para remover
efetivamente a relao entre os objetos Tag e Task.
3.1. Cookbook
321
322
$em->persist($task);
$em->flush();
// redirect back to some edit page
Captulo 3. Cookbook
A fim de criar o tipo de campo personalizado, primeiro voc precisa criar a classe que o representa. Nesta situao, a
classe que contm o tipo de campo ser chamada GenderType e o arquivo ser armazenado no local padro para campos
de formulrio, que <BundleName>\Form\Type. Verifique se o campo estende a classe AbstractType:
// src/Acme/DemoBundle/Form/Type/GenderType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class GenderType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
choices => array(
m => Male,
f => Female,
)
));
}
public function getParent()
{
return choice;
}
public function getName()
{
return gender;
}
}
Dica: A localizao deste arquivo no importante - o diretrio Form\Type apenas uma conveno.
Aqui, o valor de retorno da funo getParent indica que voc est estendendo o tipo de campo choice. Isto
significa que, por padro, voc herda toda a lgica e renderizao deste tipo de campo. Para ver um pouco da lgica,
confira a classe ChoiceType. Existem trs mtodos que so particularmente importantes:
buildForm() - Cada tipo de campo possui um mtodo buildForm, que onde voc configura e constri
qualquer campo(s). Note que este o mesmo mtodo que voc usa para configurar seus formulrios, e ele
funciona da mesma forma aqui.
buildView() - Este mtodo usado para definir quaisquer variveis extras que voc precisa ao renderizar
o seu campo em um template. Por exemplo, no ChoiceType, uma varivel multiple definida e utilizada
3.1. Cookbook
323
no template para setar (ou no) o atributo multiple no campo select. Veja Criando um template para o
Campo para mais detalhes.
setDefaultOptions() - Define opes para o seu tipo do formulrio, que podem ser usadas no buildForm() e no buildView(). H vrias opes comuns a todos os campos (veja
/reference/forms/types/form), mas voc pode criar quaisquer outras que voc precisar aqui.
Dica: Se voc est criando um campo que consiste de muitos campos, ento no se esquea de definir o seu tipo pai
como form ou algo que estenda form. Alm disso, se voc precisar modificar a viso de qualquer um dos tipos
filho a partir de seu tipo pai, use o mtodo finishView().
O mtodo getName() retorna um identificador que deve ser nico na sua aplicao. Isto usado em vrios lugares,
como ao personalizar a forma que o seu tipo de formulrio ser renderizado.
O objetivo deste campo foi estender o tipo choice para ativar a seleo de um gnero. Isto alcanado atravs do
ajuste das choices para uma lista de gneros possveis.
Criando um Template para o Campo
Cada tipo de campo renderizado por um fragmento de template, o qual determinado, em parte, pelo valor do seu
mtodo getName(). Para maiores informaes, visite O que so Temas de Formulrio?.
Neste caso, uma vez que o campo pai choice, voc no precisa fazer qualquer trabalho pois o tipo de campo
personalizado ser automaticamente renderizado como um tipo choice. Mas, para o propsito deste exemplo, vamos
supor que, quando o seu campo expandido (ou seja, botes de opo ou caixas de seleo em vez de um campo de
seleo), voc quer sempre renderiz-lo em um elemento ul. Em seu template tema de formulrio (veja o link acima
para mais detalhes), crie um bloco gender_widget para lidar com isso:
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% block gender_widget %}
{% spaceless %}
{% if expanded %}
<ul {{ block(widget_container_attributes) }}>
{% for child in form %}
<li>
{{ form_widget(child) }}
{{ form_label(child) }}
</li>
{% endfor %}
</ul>
{% else %}
{# just let the choice widget render the select tag #}
{{ block(choice_widget) }}
{% endif %}
{% endspaceless %}
{% endblock %}
Nota: Certifique-se que usado o prefixo widget correto. Neste exemplo, o nome deve ser gender_widget, de
acordo com o valor retornado pelo getName. Alm disso, o arquivo de configurao principal deve apontar para o
template de formulrio personalizado, assim, ele ser usado ao renderizar todos os formulrios.
# app/config/config.yml
twig:
form:
resources:
- AcmeDemoBundle:Form:fields.html.twig
324
Captulo 3. Cookbook
Agora voc pode usar o seu tipo de campo personalizado imediatamente, simplesmente criando uma nova instncia do
tipo em um de seus formulrios:
// src/Acme/DemoBundle/Form/Type/AuthorType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(gender_code, new GenderType(), array(
empty_value => Choose a gender,
));
}
}
Mas isso s funciona porque o GenderType() muito simples. E se os cdigos do gnero foram armazenados em
configurao ou num banco de dados? A prxima seo explica como os tipos de campos mais complexos resolvem
este problema.
Criando o seu Tipo de Campo como um Servio
At agora, este artigo assumiu que voc tem um tipo de campo personalizado bem simples. Mas se voc precisar
acessar a configurao, uma conexo de banco de dados ou algum outro servio, ento, voc vai querer registrar o seu
tipo personalizado como um servio. Por exemplo, suponha que voc est armazenando os parmetros de gnero em
configurao:
YAML
# app/config/config.yml
parameters:
genders:
m: Male
f: Female
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="genders" type="collection">
<parameter key="m">Male</parameter>
<parameter key="f">Female</parameter>
</parameter>
</parameters>
Para usar o parmetro, defina o seu tipo de campo personalizado como um servio, injetando o valor do parmetro
genders como o primeiro argumento para a sua funo recm-criada __construct:
YAML
# src/Acme/DemoBundle/Resources/config/services.yml
services:
acme_demo.form.type.gender:
3.1. Cookbook
325
class: Acme\DemoBundle\Form\Type\GenderType
arguments:
- "%genders%"
tags:
- { name: form.type, alias: gender }
XML
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<service id="acme_demo.form.type.gender" class="Acme\DemoBundle\Form\Type\GenderType">
<argument>%genders%</argument>
<tag name="form.type" alias="gender" />
</service>
Dica: Certifique-se que o arquivo de servios est sendo importado. Para mais detalhes consulte Importando configurao com imports.
Certifique-se tambm que o atributo alias da tag corresponde ao valor retornado pelo mtodo getName definido
anteriormente. Voc vai ver a importncia disto logo que usar o tipo de campo personalizado. Mas, primeiro, adicione
um mtodo __construct para o GenderType, o qual recebe a configurao do gnero:
// src/Acme/DemoBundle/Form/Type/GenderType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
class GenderType extends AbstractType
{
private $genderChoices;
public function __construct(array $genderChoices)
{
$this->genderChoices = $genderChoices;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
choices => $this->genderChoices,
));
}
// ...
}
timo! O GenderType alimentado agora por parmetros de configurao e registrado como um servio. Alm
disso, devido a voc ter usado o alias form.type na sua configurao, a utilizao do campo muito mais fcil
agora:
// src/Acme/DemoBundle/Form/Type/AuthorType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
// ...
326
Captulo 3. Cookbook
Observe que em vez de criar uma nova instncia, voc pode apenas referir-se ela pelo alias usado na sua configurao
do servio, gender. Divirta-se!
Como criar uma Extenso do Tipo de Formulrio
Tipos de campo de formulrio personalizados so timos quando voc precisa de tipos de campo para um propsito
especfico, por exemplo para selecionar o gnero ou uma entrada de nmero de IVA.
Mas, s vezes, voc realmente no precisa adicionar novos tipos de campo - voc quer adicionar funcionalidades aos
tipos existentes. onde o tipo de formulrio usado.
As extenses do tipo de formulrio tm dois casos de uso principais:
1. Voc quer adicionar uma funcionalidade genrica para vrios tipos (como a adio de um texto de ajuda
para cada tipo de campo);
2. Voc quer adicionar uma funcionalidade especfica para um nico tipo (tal como a adio de uma funcionalidade download para o tipo de campo file).
Em ambos os casos, possvel alcanar o seu objetivo com a renderizao personalizada de formulrio. Mas, usar
extenses do tipo de formulrio pode ser mais limpo (limitando a quantidade de lgica de negcios nos templates) e
mais flexvel (voc pode adicionar vrias extenses do tipo a um nico tipo de formulrio.
Extenses do tipo de formulrio podem alcanar mais do que os tipos de campos personalizados podem fazer, mas,
em vez de serem tipos de campos prprios, elas conectam em tipos existentes.
Imagine que voc gerencia uma entidade Media, e que cada mdia est associada a um arquivo. Seu formulrio
Media usa um tipo file, mas, ao editar a entidade, voc gostaria de ver sua imagem processada automaticamente ao
lado do campo arquivo.
Voc poderia, naturalmente, fazer isso personalizando a forma como este campo renderizado em um template. Mas
as extenses do tipo de formulrio permitem que voc faa isso de uma forma DRY agradvel.
Definindo a Extenso do Tipo de Formulrio
Sua primeira tarefa ser criar a classe da extenso do tipo de formulrio. Vamos cham-la ImageTypeExtension.
Por padro, as extenses de formulrio geralmente residem no diretrio Form\Extension de um de seus bundles.
Ao criar uma extenso de tipo de formulrio,
voc pode implementar a interface
FormTypeExtensionInterface ou estender a classe AbstractTypeExtension. Na maioria dos
casos, mais fcil estender a classe abstrata:
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
class ImageTypeExtension extends AbstractTypeExtension
3.1. Cookbook
327
{
/**
* Returns the name of the type being extended.
*
* @return string The name of the type being extended
*/
public function getExtendedType()
{
return file;
}
}
O nico mtodo que voc deve implementar o getExtendedType. Ele usado para indicar o nome do tipo de
formulrio que ser estendido pela sua extenso.
Dica: O valor que voc retorna no mtodo getExtendedType corresponde ao valor retornado pelo mtodo
getName na classe do tipo de formulrio que voc deseja estender.
Alm da funogetExtendedType, provavelmente voc vai querer sobrescrever um dos seguintes mtodos:
buildForm()
buildView()
setDefaultOptions()
finishView()
Para mais informaes sobre o que esses mtodos fazem, voc pode consultar o artigo do cookbook Criando Tipos de
Campo Personalizados.
Registrando a sua Extenso do Tipo de Formulrio como um Servio
O prximo passo tornar o Symfony ciente da sua extenso. Tudo o que voc precisa fazer declar-la como um servio
usando a tag form.type_extension.
YAML
services:
acme_demo_bundle.image_type_extension:
class: Acme\DemoBundle\Form\Extension\ImageTypeExtension
tags:
- { name: form.type_extension, alias: file }
XML
<service id="acme_demo_bundle.image_type_extension"
class="Acme\DemoBundle\Form\Extension\ImageTypeExtension"
>
<tag name="form.type_extension" alias="file" />
</service>
PHP
$container
->register(
acme_demo_bundle.image_type_extension,
Acme\DemoBundle\Form\Extension\ImageTypeExtension
328
Captulo 3. Cookbook
)
->addTag(form.type_extension, array(alias => file));
A chave alias da tag o tipo de campo que essa extenso deve ser aplicada. No seu caso, como voc deseja estender
o tipo de file, voc vai usar o file como um alias.
Adicionando a lgica de negcio da extenso
O objetivo da sua extenso exibir imagens agradveis ao lado de campos arquivo (quando o modelo subjacente
contm imagens). Para esta finalidade, vamos supor que voc usa uma abordagem semelhante descrita em Como
manusear o upload de arquivos com o Doctrine: voc tem um modelo Media com uma propriedade file (correspondente ao campo de arquivo, no formulrio) e uma propriedade path (correspondendo ao caminho da imagem, no banco
de dados):
// src/Acme/DemoBundle/Entity/Media.php
namespace Acme\DemoBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Media
{
// ...
/**
* @var string The path - typically stored in the database
*/
private $path;
/**
* @var \Symfony\Component\HttpFoundation\File\UploadedFile
* @Assert\File(maxSize="2M")
*/
public $file;
// ...
/**
* Get the image url
*
* @return null|string
*/
public function getWebPath()
{
// ... $webPath being the full image url, to be used in templates
return $webPath;
}
}
Sua classe da extenso do tipo de formulrio ter que fazer duas coisas, a fim de estender o tipo de formulrio file:
1. Sobrescrever o mtodo setDefaultOptions, a fim de adicionar uma opo image_path;
2. Sobrescrever os mtodos buildForm e buildView a fim de passar a url da imagem para a viso.
A lgica a seguinte: quando adicionar um campo de formulrio do tipo file, voc poder especificar uma nova
opo: image_path . Esta opo ir dizer ao campo arquivo como obter o endereo real da imagem, a fim de
exib-la na viso:
3.1. Cookbook
329
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;
use
use
use
use
use
Symfony\Component\Form\AbstractTypeExtension;
Symfony\Component\Form\FormView;
Symfony\Component\Form\FormInterface;
Symfony\Component\Form\Util\PropertyPath;
Symfony\Component\OptionsResolver\OptionsResolverInterface;
330
Captulo 3. Cookbook
Cada tipo de campo renderizado por um fragmento de template. Esses fragmentos de template podem ser sobrescritos, para personalizar a renderizao formulrio. Para mais informaes voc pode consultar o artigo O que so Temas
de Formulrio?.
Em sua classe de extenso, voc adicionou uma nova varivel (image_url), mas voc ainda precisa aproveitar esta
nova varivel em seus templates. Especificamente, voc precisa sobrescrever o bloco file_widget:
Twig
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% extends form_div_layout.html.twig %}
{% block file_widget %}
{% spaceless %}
{{ block(form_widget) }}
{% if image_url is not null %}
<img src="{{ asset(image_url) }}"/>
{% endif %}
{% endspaceless %}
{% endblock %}
PHP
<!-- src/Acme/DemoBundle/Resources/views/Form/file_widget.html.php -->
<?php echo $view[form]->widget($form) ?>
<?php if (null !== $image_url): ?>
<img src="<?php echo $view[assets]->getUrl($image_url) ?>"/>
<?php endif ?>
Nota: Voc precisar mudar o seu arquivo de configurao ou especificar explicitamente como voc quer o tema do
seu formulrio, para que o Symfony utilize o seu bloco sobrecrito. Veja O que so Temas de Formulrio? para mais
informaes.
A partir de agora, ao adicionar um campo do tipo file no seu formulrio, voc pode especificar uma opo
image_path que ser usada para exibir uma imagem prxima ao campo arquivo. Por exemplo:
// src/Acme/DemoBundle/Form/Type/MediaType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class MediaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(name, text)
->add(file, file, array(image_path => webPath));
}
3.1. Cookbook
331
Ao exibir o formulrio, se o modelo subjacente j foi associado com uma imagem, voc vai v-la ao lado do campo
arquivo.
Como usar a opo de campo de formulrio Virtual
A opo de campo de formulrio virtual pode ser muito til quando voc possui alguns campos duplicados em
entidades diferentes.
Por exemplo, imagine que voc tem duas entidades:Company e Customer:
// src/Acme/HelloBundle/Entity/Company.php
namespace Acme\HelloBundle\Entity;
class Company
{
private $name;
private $website;
private
private
private
private
$address;
$zipcode;
$city;
$country;
}
// src/Acme/HelloBundle/Entity/Customer.php
namespace Acme\HelloBundle\Entity;
class Customer
{
private $firstName;
private $lastName;
private
private
private
private
$address;
$zipcode;
$city;
$country;
Como pode-se ver, as entidades possuem alguns campos iguais: address, zipcode, city e country.
Agora, voc deseja construir dois formulrios: um para Company e outro para Customer.
Comece criando classes simples de tipo de formulrio para CompanyType e CustomerType:
// src/Acme/HelloBundle/Form/Type/CompanyType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
332
Captulo 3. Cookbook
$builder
->add(name, text)
->add(website, text);
}
}
// src/Acme/HelloBundle/Form/Type/CustomerType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(firstName, text)
->add(lastName, text);
}
}
Agora, temos que lidar com os quatro campos duplicados. Aqui est um formulrio (simples) para localidade
(Location):
// src/Acme/HelloBundle/Form/Type/LocationType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(address, textarea)
->add(zipcode, text)
->add(city, text)
->add(country, text);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
virtual => true
));
}
public function getName()
{
return location;
}
}
Ns no temos realmente um campo de localidade em cada uma das nossas entidades, de modo que no podemos ligar
diretamente LocationType ao nosso CompanyType ou CustomerType. Mas, com certeza, queremos um tipo
de formulrio prprio para lidar com a localidade (lembre-se, DRY!).
A opo de campo de formulrio virtual a soluo.
3.1. Cookbook
333
Com a opo virtual definida para false (comportamento padro) , o Componente de Formulrio espera que cada
objeto subjacente tenha uma propriedade foo (ou bar) que algum objeto ou array que contm os quatro campos da
localidade. Claro, no temos este objeto/array em nossas entidades e ns no queremos isso!
Com a opo virtual definida como true, o componente de Formulrio ignora a propriedade foo (ou bar), e, em vez
disso, aplica gets e sets aos quatro campos de localidade diretamente no objeto subjacente!
Nota: Ao invs de definir a opo virtual dentro de LocationType, voc pode (assim como com todas as
outras opes) tambm pass-la como uma opo de array no terceiro argumento de $builder->add().
Por padro, o empty_data setado como null. Ou, se voc especificou a opo data_class para a sua classe
de formulrio, ele ser, por padro, uma nova instncia dessa classe. Essa instncia ser criada chamando o construtor
sem argumentos.
Se voc quiser sobrescrever esse comportamento padro, existem duas formas de fazer isso.
334
Captulo 3. Cookbook
Uma razo para usar esta opo se voc quer usar um construtor que possui argumentos. Lembre-se, a opo
data_class padro chama o construtor sem argumentos:
// src/Acme/DemoBundle/Form/Type/BlogType.php
// ...
use Symfony\Component\Form\AbstractType;
use Acme\DemoBundle\Entity\Blog;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BlogType extends AbstractType
{
private $someDependency;
public function __construct($someDependency)
{
$this->someDependency = $someDependency;
}
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
empty_data => new Blog($this->someDependency),
));
}
}
Voc pode instanciar sua classe como desejar. Neste exemplo, ns passamos algumas dependncias para o BlogType
ao instanci-lo, ento, use isso para instanciar o objeto Blog. O ponto , voc pode setar o empty_data para o
objeto novo que voc deseja usar.
Opo 2: Fornecer uma Closure
Usar uma closure o mtodo preferido, uma vez que ir criar o objeto apenas se for necessrio.
A closure deve aceitar uma instncia FormInterface como seu primeiro argumento:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormInterface;
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
empty_data => function (FormInterface $form) {
return new Blog($form->get(title)->getData());
},
));
}
3.1. Cookbook
335
3.1.7 Validao
Como criar uma Constraint de Validao Personalizada
Voc pode criar uma constraint personalizada estendendo uma classe base de constraint Constraint. Como exemplo vamos criar um validador simples que verifica se uma string contm apenas caracteres alfanumricos.
Criando a Classe Constraint
Primeiro voc precisa criar uma classe de Constraint e estender Constraint:
// src/Acme/DemoBundle/Validator/constraints/ContainsAlphanumeric.php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class ContainsAlphanumeric extends Constraint
{
public $message = The string "%string%" contains an illegal character: it can only contain lette
}
Nota: A annotation @Annotation necessria para esta nova constraint para torn-la disponvel para uso em
classes atravs de annotations. Opes para a sua constraint so representadas como propriedades pblicas na classe
constraint.
Criando o Validador em si
Como voc pode ver, uma classe de constraint muito curta. A validao real realizada por uma outra classe validadora de constraint. A classe validadora de constraint especificada pelo mtodo de constraint validatedBy(),
que inclui alguma lgica padro simples:
// in the base Symfony\Component\Validator\Constraint class
public function validatedBy()
{
return get_class($this).Validator;
}
Em outras palavras, se voc criar uma Constraint personalizada (Ex. MyConstraint), o Symfony2 automaticamente ir procurar a outra classe MyConstraintValidator quando realmente executar a validao.
A classe validadora tambm simples, e s contm um mtodo necessrio: validate:
// src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumericValidator.php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ContainsAlphanumericValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
336
Captulo 3. Cookbook
Nota: O mtodo validate no retorna um valor, em vez disso, ele acrescenta violaes propriedade context
do validador com uma chamada do mtodo addViolation se existem falhas de validao. Portanto, um valor pode
ser considerado como sendo vlido, desde que no cause violaes adicionadas ao contexto. O primeiro parmetro da
chamada addViolation a mensagem de erro para usar para aquela violao.
Novo na verso 2.1: O mtodo isValid foi renomeado para validate no Symfony 2.1. O mtodo setMessage
tambm ficou obsoleto, em favor da chamada addViolation do contexto.
Usando o novo Validador
Usar validadores personalizados muito fcil, assim como os fornecidos pelo Symfony2 em si:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\DemoBundle\Entity\AcmeEntity:
properties:
name:
- NotBlank: ~
- Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~
Annotations
// src/Acme/DemoBundle/Entity/AcmeEntity.php
use Symfony\Component\Validator\Constraints as Assert;
use Acme\DemoBundle\Validator\Constraints as AcmeAssert;
class AcmeEntity
{
// ...
/**
* @Assert\NotBlank
* @AcmeAssert\ContainsAlphanumeric
*/
protected $name;
// ...
}
XML
3.1. Cookbook
337
PHP
// src/Acme/DemoBundle/Entity/AcmeEntity.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric;
class AcmeEntity
{
public $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(name, new NotBlank());
$metadata->addPropertyConstraint(name, new ContainsAlphanumeric());
}
}
Se a sua constraint contm opes, ento elas devem ser propriedades pblicas na classe Constraint personalizada que
voc criou anteriormente. Essas opes podem ser configuradas como opes nas constraints do ncleo do Symfony.
Validadores de Constraints com Dependncias
Se o seu validador de restrio possui dependncias, como uma conexo de banco de dados, ela ter que
ser configurada como um servio no container de injeo de dependncia. Este servio deve incluir a tag
validator.constraint_validator e um atributo alias:
YAML
services:
validator.unique.your_validator_name:
class: Fully\Qualified\Validator\Class\Name
tags:
- { name: validator.constraint_validator, alias: alias_name }
XML
PHP
$container
->register(validator.unique.your_validator_name, Fully\Qualified\Validator\Class\Name)
->addTag(validator.constraint_validator, array(alias => alias_name));
Sua classe de constraint pode agora usar este alias para referenciar o validador apropriado:
public function validatedBy()
{
return alias_name;
}
338
Captulo 3. Cookbook
Como mencionado acima, o Symfony2 ir procurar automaticamente por uma classe chamada aps a constraint, com
Validator acrescentado. Se o seu validador constraint est definido como um servio, importante que voc
sobrescreva o mtodo validatedBy() para retornar o alias utilizado na definio de seu servio, caso contrrio,
o Symfony2 no vai usar o servio do validador de constraint, e, em vez disso, ir instanciar a classe, sem quaisquer
dependncias injetadas.
Classe Constraint Validadora
Junto da validao de uma propriedade de classe, uma constraint pode ter um escopo de classe, fornecendo um alvo:
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
Com isso, o mtodo validador validate() obtm um objeto como seu primeiro argumento:
class ProtocolClassValidator extends ConstraintValidator
{
public function validate($protocol, Constraint $constraint)
{
if ($protocol->getFoo() != $protocol->getBar()) {
$this->context->addViolationAtSubPath(foo, $constraint->message, array(), null);
}
}
}
Annotations
/**
* @AcmeAssert\ContainsAlphanumeric
*/
class AcmeEntity
{
// ...
}
XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\DemoBundle\Entity\AcmeEntity">
<constraint name="ContainsAlphanumeric" />
</class>
3.1. Cookbook
339
3.1.8 Configurao
Como Dominar e Criar novos Ambientes
Cada aplicao a combinao de cdigo e um conjunto de configuraes que dita como o cdigo deve funcionar. A
configurao pode: definir o banco de dados a ser utilizado, se algo deve ou no ser armazenado em cache, ou como
deve ser a verbosidade do log. No Symfony2, a idia de ambientes a idia de que a mesma base de cdigo pode ser
executada usando vrias configuraes diferentes. Por exemplo, o ambiente dev deve usar uma configurao que faa
com que o desenvolvimento seja fcil e amigvel, enquanto o ambiente prod deve usar um conjunto de configuraes
otimizada para a velocidade.
Ambientes Diferentes, Diferentes Arquivos de Configurao
Uma aplicao tpica do Symfony2 comea com trs ambientes: dev, prod e test. Como discutido, cada ambiente simplesmente representa uma forma de executar o mesmo cdigo com configuraes diferentes. No deve ser
nenhuma surpresa, ento, que cada ambiente carrega seu arquivo de configurao individual. Se voc estiver usando o
formato de configurao YAML, os seguintes arquivos so usados:
para o ambiente dev: app/config/config_dev.yml
para o ambiente prod: app/config/config_prod.yml
para o ambiente test: app/config/config_test.yml
Isso funciona por meio de uma conveno simples que usada, por padro, dentro da classe AppKernel:
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__./config/config_.$this->getEnvironment()..yml);
}
}
Como voc pode ver, quando o Symfony2 carregado, ele usa um dado ambiente para determinar qual arquivo de configurao deve carregar. Isto alcana o objetivo de vrios ambientes de uma forma elegante, poderosa e transparente.
claro que, na realidade, cada ambiente s difere um pouco dos outros. Geralmente, todos os ambientes iro compartilhar uma grande base de configurao comum. Abrindo o arquivo de configurao dev, voc pode ver como isso
feito fcil e transparentemente:
YAML
imports:
- { resource: config.yml }
# ...
XML
<imports>
<import resource="config.xml" />
</imports>
<!-- ... -->
340
Captulo 3. Cookbook
PHP
$loader->import(config.php);
// ...
Para compartilhar as configuraes comuns, cada arquivo de configurao de ambiente simplesmente primeiro importa
de um arquivo de configurao central (config.yml). O restante do arquivo pode ento desviar-se da configurao
padro sobrescrevendo os parmetros individuais. Por exemplo, por padro, a barra de ferramentas web_profiler
est desativada. No entanto, no ambiente dev, a barra de ferramentas ativada, modificando o valor padro no arquivo
de configurao dev:
YAML
# app/config/config_dev.yml
imports:
- { resource: config.yml }
web_profiler:
toolbar: true
# ...
XML
<!-- app/config/config_dev.xml -->
<imports>
<import resource="config.xml" />
</imports>
<webprofiler:config
toolbar="true"
... />
PHP
// app/config/config_dev.php
$loader->import(config.php);
$container->loadFromExtension(web_profiler, array(
toolbar => true,
...,
));
Para executar a aplicao em cada ambiente, carregue a aplicao usando o front controller app.php (para o ambiente
prod) ou o app_dev.php (para o ambiente dev):
http://localhost/app.php
http://localhost/app_dev.php
Nota: As URLs fornecidas assumem que o seu servidor web est configurado para usar o diretrio web/ da aplicao
como sua raiz. Leia mais em Installing Symfony2.
Se voc abrir um desses arquivos, ver rapidamente que o ambiente utilizado por cada um definido explicitamente:
1
<?php
2
3
require_once __DIR__./../app/bootstrap_cache.php;
3.1. Cookbook
341
require_once __DIR__./../app/AppCache.php;
5
6
use Symfony\Component\HttpFoundation\Request;
7
8
9
Como voc pode ver, a chave prod especifica que esse ambiente ser executado no ambiente prod. Uma aplicao
Symfony2 pode ser executada em qualquer ambiente usando este cdigo e alterando a string de ambiente.
Nota: O ambiente test utilizado quando escrevemos testes funcionais e no acessvel diretamente no navegador
atravs de um front controller. Em outras palavras, ao contrrio dos outros ambientes, no h um arquivo front
controller app_test.php.
Modo Depurao
Importante, mas sem relao com o tema de ambientes a chave false na linha 8 do front controller acima.
Esta especifica se a aplicao deve ou no ser executada em modo de depurao. Independentemente do
ambiente, uma aplicao Symfony2 pode ser executada com o modo de depurao definido como true ou
false. Isso afeta muitas coisas na aplicao, como se os erros devem ou no ser apresentados ou se os arquivos
de cache so dinamicamente reconstrudos em cada pedido. Apesar de no ser uma exigncia, o modo de
depurao geralmente definido para true nos ambientes dev e test e false para o ambiente prod.
Internamente, o valor do modo de depurao torna-se o parmetro kernel.debug utilizado dentro do service container. Se voc olhar dentro do arquivo de configurao da aplicao, ver o parmetro utilizado, por
exemplo, para ligar ou desligar o log ao usar o Doctrine DBAL:
YAML
doctrine:
dbal:
logging:
# ...
"%kernel.debug%"
XML
<doctrine:dbal logging="%kernel.debug%" ... />
PHP
$container->loadFromExtension(doctrine, array(
dbal => array(
logging => %kernel.debug%,
...,
),
...
));
Por padro, uma aplicao Symfony2 tem que lidar com trs ambientes na maioria dos casos. Claro, desde que um
ambiente nada mais do que uma string que corresponde a um conjunto de configurao, a criao de um novo
ambiente bastante fcil.
Suponha, por exemplo, que antes da implantao, voc precisa realizar um benchmark da sua aplicao. Uma forma
de realizar o benchmark da aplicao usar configuraes prximas da produo, mas com o web_profiler do
Symfony2 habilitado. Isto permite ao Symfony2 registrar as informaes sobre sua aplicao enquanto o realiza o
342
Captulo 3. Cookbook
benchmark.
A melhor forma de realizar isto atravs de um novo ambiente chamado, por exemplo, benchmark. Comece por
criar um novo arquivo de configurao:
YAML
# app/config/config_benchmark.yml
imports:
- { resource: config_prod.yml }
framework:
profiler: { only_exceptions: false }
XML
<!-- app/config/config_benchmark.xml -->
<imports>
<import resource="config_prod.xml" />
</imports>
<framework:config>
<framework:profiler only-exceptions="false" />
</framework:config>
PHP
// app/config/config_benchmark.php
$loader->import(config_prod.php)
$container->loadFromExtension(framework, array(
profiler => array(only-exceptions => false),
));
E com esta simples adio, a aplicao agora suporta um novo ambiente chamado benchmark.
Este novo arquivo de configurao importa a configurao do ambiente prod e modifica ela. Isso garante que o novo
ambiente idntico ao ao ambiente prod, exceto por quaisquer alteraes explicitamente feitas aqui.
J que voc deseja que este ambiente seja acessvel atravs de um navegador, voc tambm deve criar um front
controller para ele. Copie o arquivo web/app.php para web/app_benchmark.php e edite o ambiente para
benchmark:
<?php
require_once __DIR__./../app/bootstrap.php;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(benchmark, false);
$kernel->handle(Request::createFromGlobals())->send();
Nota: Alguns ambientes, como o ambiente dev, nunca so destinados ao acesso em qualquer servidor implantado
para o pblico em geral. Isto porque certos ambientes, para fins de depurao, podem fornecer muita informao
sobre a aplicao ou a infra-estrutura subjacente. Para ter certeza que estes ambientes no so acessveis, o front
controller normalmente protegido de endereos IP externos atravs do seguinte cdigo no topo do controlador:
3.1. Cookbook
343
O Symfony2 aproveita o cache de muitas maneiras: para a configurao da aplicao, configurao de roteamento,
templates Twig e mais realizado o cache para objetos PHP armazenados em arquivos no sistema de arquivos.
Por padro, esses arquivos em cache so armazenados, em grande parte, no diretrio app/cache. No entanto, cada
ambiente armazena seu prprio conjunto de arquivos:
app/cache/dev
app/cache/prod
s vezes, quando depurando, pode ser til inspecionar um arquivo em cache para compreender como algo est funcionando. Ao faz-lo, lembre-se de olhar no diretrio do ambiente que voc est usando (mais comumente dev enquanto
estiver desenvolvendo e depurando). Embora possa variar, o diretrio app/cache/dev inclui o seguinte:
appDevDebugProjectContainer.php - o container de servio em cache que representa a configurao da aplicao em cache;
appdevUrlGenerator.php - a classe PHP gerada a partir da configurao de roteamento e usada ao gerar
URLs;
appdevUrlMatcher.php - a classe PHP usada para correspondncia de rota - olhe aqui para ver a lgica
de expresses regulares compiladas usadas para correspondncia das URLs de entrada para diferentes rotas;
twig/ - Este diretrio contm todos templates Twig em cache.
Nota: Voc pode facilmente mudar a localizao do diretrio e o seu nome. Para mais informaes leia o artigo
Como Substituir a Estrutura de Diretrio Padro do Symfony.
344
Captulo 3. Cookbook
Voc pode substituir o diretrio cache, sobrescrevendo o mtodo getCacheDir na classe AppKernel de sua
aplicao:
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
public function getCacheDir()
{
return $this->rootDir./.$this->environment./cache/;
}
}
$this->rootDir o caminho absoluto para o diretrio app e $this->environment o ambiente atual (ou
seja, dev). Neste caso voc alterou a localizao do diretrio cache para app/{environment}/cache.
Cuidado: Voc deve manter o diretrio cache diferente para cada ambiente, caso contrrio, algum comportamento inesperado pode acontecer. Cada ambiente gera seus prprios arquivos de configurao em cache, e assim,
cada um precisa de seu prprio diretrio para armazenar os arquivos de cache.
O processo para substituir o diretrio logs o mesmo do diretrio cache, a nica diferena que voc precisa
sobrescrever o mtodo getLogDir:
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
public function getLogDir()
{
return $this->rootDir./.$this->environment./logs/;
}
}
Se voc precisa renomear ou mover o seu diretrio web, a nica coisa que voc precisa garantir que o caminho
para o diretrio app ainda est correto em seus front controllers app.php e app_dev.php. Se voc simplesmente
renomear o diretrio, ento est tudo ok. Mas se voc moveu de alguma forma, pode precisar modificar os caminhos
dentro desses arquivos:
require_once __DIR__./../Symfony/app/bootstrap.php.cache;
require_once __DIR__./../Symfony/app/AppKernel.php;
3.1. Cookbook
345
Dica: Alguns hosts compartilhados tem um diretrio raiz web public_html. Renomeando seu diretrio web de
web para public_html uma maneira de fazer funcionar o seu projeto Symfony em seu servidor compartilhado.
Outra forma implantar sua aplicao em um diretrio fora do raiz web, excluir seu diretrio public_html e,
ento, substitu-lo por um link simblico para o web em seu projeto.
Nota: Se voc utiliza o AsseticBundle precisar configurar o seguinte, para que ele possa usar o diretrio web correto:
# app/config/config.yml
# ...
assetic:
# ...
read_from: "%kernel.root_dir%/../../public_html"
Agora voc s precisa realizar o dump dos assets novamente e sua aplicao deve funcionar:
$ php app/console assetic:dump --env=prod --no-debug
O Symfony vai pegar qualquer varivel de ambiente com o prefixo SYMFONY__ e set-la como um parmetro no
container de servio. Sublinhados duplos so substitudos por um ponto, pois o ponto no um caracter vlido no
nome de uma varivel de ambiente.
Por exemplo, se voc est usando o Apache, as variveis de ambiente podem ser definidas utilizando a seguinte
configurao VirtualHost:
<VirtualHost *:80>
ServerName
DocumentRoot
DirectoryIndex
SetEnv
SetEnv
Symfony2
"/path/to/symfony_2_app/web"
index.php index.html
SYMFONY__DATABASE__USER user
SYMFONY__DATABASE__PASSWORD secret
<Directory "/path/to/symfony_2_app/web">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>
Nota: O exemplo acima para uma configurao Apache, usando a diretiva SetEnv. No entanto, isso vai funcionar
para qualquer servidor web que suporte a definio de variveis de ambiente.
Alm disso, para que o seu console funcione (que no usa Apache), voc deve exportar estas como variveis shell. Em
um sistema Unix, voc pode executar o seguinte:
346
Captulo 3. Cookbook
$ export SYMFONY__DATABASE__USER=user
$ export SYMFONY__DATABASE__PASSWORD=secret
Agora que voc declarou uma varivel de ambiente, ela estar presente na varivel global $_SERVER do PHP. O Symfony, ento, automaticamente define todas as variveis $_SERVER prefixadas com SYMFONY__ como parmetros
no container de servios.
Agora, voc pode referenciar estes parmetros em qualquer local onde precisar deles.
YAML
doctrine:
dbal:
driver
dbname:
user:
password:
pdo_mysql
symfony2_project
"%database.user%"
"%database.password%"
XML
PHP
$container->loadFromExtension(doctrine, array(dbal => array(
driver
=> pdo_mysql,
dbname
=> symfony2_project,
user
=> %database.user%,
password => %database.password%,
));
Constantes
O container tambm possui suporte para definir constantes do PHP como parmetros. Para aproveitar esse recurso,
mapeie o nome da sua constante para uma chave de parmetro , e defina o tipo como constant.
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parameters>
<parameter key="global.constant.value" type="constant">GLOBAL_CONSTANT</parameter>
<parameter key="my_class.constant.value" type="constant">My_Class::CONSTANT_NAME</parame
</parameters>
</container>
3.1. Cookbook
347
Nota: Isso funciona somente para a configurao XML. Se voc no est usando XML, simplesmente importe um
arquivo XML para aproveitar essa funcionalidade:
# app/config/config.yml
imports:
- { resource: parameters.xml }
Configuraes Diversas
A diretiva imports pode ser usada para puxar os parmetros armazenados em outro lugar. Importando um arquivo
PHP lhe d a flexibilidade para adicionar o que for necessrio no container. O seguinte importa um arquivo chamado
parameters.php.
YAML
# app/config/config.yml
imports:
- { resource: parameters.php }
XML
<!-- app/config/config.xml -->
<imports>
<import resource="parameters.php" />
</imports>
PHP
// app/config/config.php
$loader->import(parameters.php);
Nota: Um arquivo de recursos pode ser um de muitos tipos. PHP, XML, YAML, INI e recursos de closure so todos
suportados pela directiva imports.
No parameters.php, diga ao container de servio os parmetros que voc deseja definir. Isto til quando alguma
configurao importante est em um formato fora do padro. O exemplo abaixo inclui a configurao de um banco de
dados do Drupal no container de servio do Symfony.
// app/config/parameters.php
include_once(/path/to/drupal/sites/default/settings.php);
$container->setParameter(drupal.database.url, $db_url);
348
Captulo 3. Cookbook
session
session_id
session_value
session_time
services:
pdo:
class: PDO
arguments:
dsn:
"mysql:dbname=mydatabase"
user:
myuser
password: mypassword
session.handler.pdo:
class:
Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
arguments: [@pdo, %pdo.db_options%]
XML
<!-- app/config/config.xml -->
<framework:config>
<framework:session handler-id="session.handler.pdo" lifetime="3600" auto-start="true"/>
</framework:config>
<parameters>
<parameter key="pdo.db_options" type="collection">
<parameter key="db_table">session</parameter>
<parameter key="db_id_col">session_id</parameter>
<parameter key="db_data_col">session_value</parameter>
<parameter key="db_time_col">session_time</parameter>
</parameter>
</parameters>
<services>
<service id="pdo" class="PDO">
<argument>mysql:dbname=mydatabase</argument>
<argument>myuser</argument>
<argument>mypassword</argument>
</service>
3.1. Cookbook
349
PHP
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$container->loadFromExtension(framework, array(
// ...
session => array(
...,
handler_id => session.handler.pdo,
),
));
$container->setParameter(pdo.db_options, array(
db_table
=> session,
db_id_col
=> session_id,
db_data_col
=> session_value,
db_time_col
=> session_time,
));
$pdoDefinition = new Definition(PDO, array(
mysql:dbname=mydatabase,
myuser,
mypassword,
));
$container->setDefinition(pdo, $pdoDefinition);
Com a configurao fornecida, as configuraes de conexo do banco de dados so definidas somente para a conexo
de armazenamento de sesso. Isto est OK quando voc usa um banco de dados separado para os dados da sesso.
Mas, se voc gostaria de armazenar os dados da sesso no mesmo banco de dados que o resto dos dados do seu projeto,
voc pode usar as definies de conexo do parameter.ini referenciando os parmetros relacionados ao banco de dados
definidos l:
YAML
pdo:
class: PDO
arguments:
- "mysql:dbname=%database_name%"
- %database_user%
- %database_password%
350
Captulo 3. Cookbook
XML
<service id="pdo" class="PDO">
<argument>mysql:dbname=%database_name%</argument>
<argument>%database_user%</argument>
<argument>%database_password%</argument>
</service>
PHP
$pdoDefinition = new Definition(PDO, array(
mysql:dbname=%database_name%,
%database_user%,
%database_password%,
));
MySQL A instruo SQL para criar a tabela de banco de dados necessria pode ser semelhante a seguinte (MySQL):
CREATE TABLE session (
session_id varchar(255) NOT NULL,
session_value text NOT NULL,
session_time int(11) NOT NULL,
PRIMARY KEY (session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Para fazer o dump das rotas do Apache precisamos primeiro ajustar alguns parmetros de configurao e dizer ao
Symfony2 para usar o ApacheUrlMatcher em vez do padro:
# app/config/config_prod.yml
parameters:
router.options.matcher.cache_class: ~ # disable router cache
router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher
Dica:
Note que o ApacheUrlMatcher estende UrlMatcher assim, mesmo se voc no regerar as regras
3.1. Cookbook
351
url_rewrite, tudo vai funcionar (porque no final do ApacheUrlMatcher::match() realizada uma chamada
para o parent::match()).
Para testar se est funcionando, vamos criar uma rota bem bsica para o bundle demo:
# app/config/routing.yml
hello:
pattern: /hello/{name}
defaults: { _controller: AcmeDemoBundle:Demo:hello }
# hello
RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeD
Agora voc pode reescrever o web/.htaccess para usar as novas regras, portanto, com o nosso exemplo, ele deve parecer
com o seguinte:
<IfModule mod_rewrite.c>
RewriteEngine On
# skip "real" requests
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .* - [QSA,L]
# hello
RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:A
</IfModule>
Nota: O procedimento acima deve ser feito cada vez que voc adicionar/alterar uma rota se deseja aproveitar o
mximo desta configurao.
isso! Voc agora est pronto para usar as regras do Apache Route.
Ajustes adicionais
Para economizar um pouco de tempo de processamento, altere as ocorrncias de Request para ApacheRequest
no web/app.php:
// web/app.php
require_once __DIR__./../app/bootstrap.php.cache;
require_once __DIR__./../app/AppKernel.php;
352
Captulo 3. Cookbook
//require_once __DIR__./../app/AppCache.php;
use Symfony\Component\HttpFoundation\ApacheRequest;
$kernel = new AppKernel(prod, false);
$kernel->loadClassCache();
//$kernel = new AppCache($kernel);
$kernel->handle(ApacheRequest::createFromGlobals())->send();
3.1.9 Bundles
Como usar Melhores Prticas para a Estruturao dos Bundles
Um Bundle um diretrio que tem uma estrutura bem definida e pode hospedar qualquer coisa desde classes at
controladores e recursos web. Mesmo os bundles sendo muito flexveis, voc deve seguir algumas das melhores
prticas se deseja distribu-los.
Nome do Bundle
Um bundle tambm um namespace PHP. O namespace deve seguir os padres tcnicos de interoperabilidade para
namespaces do PHP 5.3 e nomes de classes: ele inicia com o segmento do vendor, seguido por zero ou mais segmentos
de categoria, e termina com o nome curto do namespace, que deve terminar com o sufixo Bundle.
Um namespace torna-se um bundle assim que voc adiciona uma classe bundle ele. O nome da classe do bundle
deve seguir estas regras simples:
Usar apenas caracteres alfanumricos e sublinhados;
Usar o nome em CamelCase;
Usar um nome descritivo e curto (no mais que 2 palavras);
Prefixar o nome com a concatenao do vendor (e, opcionalmente, os namespaces da categoria);
Adicionar o sufixo Bundle ao nome.
Aqui esto alguns namespaces de bundle e nomes de classes vlidos:
Namespace
Acme\Bundle\BlogBundle
Acme\Bundle\Social\BlogBundle
Acme\BlogBundle
Por conveno, o mtodo getName() da classe bundle deve retornar o nome da classe.
Nota: Se voc compartilhar publicamente seu bundle, voc deve usar o nome da classe do bundle como o nome do
repositrio (AcmeBlogBundle e no BlogBundle por exemplo).
Nota: Os Bundles do ncleo do Symfony2 no prefixam a classe Bundle com Symfony e sempre adicionam um
subnamespace Bundle, por exemplo: FrameworkBundle.
Cada bundle tem um alias, que a verso curta e em letras minsculas do nome do bundle
usando sublinhados (por exemplo, acme_hello para AcmeHelloBundle, ou acme_social_blog para
Acme\Social\BlogBundle). Estes alias so usados para definir exclusividade dentro de um bundle (veja abaixo
alguns exemplos de uso).
3.1. Cookbook
353
Estrutura de Diretrios
Diretrio
Command/
Controller/
DependencyInjection/
EventListener/
Resources/config/
Resources/public/
Resources/translations/
Resources/views/
Tests/
Classes
A estrutura de diretrios do bundle usada como hierarquia de namespace. Por exemplo, um controlador
HelloController armazenado em Bundle/HelloBundle/Controller/HelloController.php e
o nome totalmente qualificado da classe Bundle\HelloBundle\Controller\HelloController.
354
Captulo 3. Cookbook
Um bundle no deve incorporar bibliotecas PHP de terceiros. Em vez disso, ele deve contar com o autoloading padro
do Symfony2.
Um bundle no deve incorporar bibliotecas de terceiros escritas em JavaScript, CSS ou qualquer outra linguagem.
Testes
Um bundle deve vir com um conjunto de testes escritos com o PHPUnit e armazenados sob o diretrio Tests/. Os
testes devem seguir os seguintes princpios:
O conjunto de testes deve ser executvel com um simples comando phpunit, executado a partir de uma
aplicao de exemplo;
Os testes funcionais s devem ser usados para testar a resposta de sada e algumas informaes de perfis, caso
voc tiver;
A cobertura de cdigo deve cobrir pelo menos 95% da base de cdigo.
Nota: Um conjunto de testes no deve conter scripts AllTests.php, mas deve contar com a existncia de um
arquivo phpunit.xml.dist.
Documentao
Como melhor prtica, os controladores em um bundle que se destina a ser distribudo no deve estender a classe
base Controller. Ao invs disso, eles podem implementar ContainerAwareInterface ou estender
ContainerAware .
Nota: Se voc verificar os mtodos do Controller, vai ver que eles so apenas atalhos para facilitar a curva de
aprendizado.
3.1. Cookbook
355
Roteamento
Se o bundle oferece rotas, elas devem ser prefixadas com o alias do bundle. Por exemplo, para o AcmeBlogBundle,
todas as rotas devem ser prefixadas com acme_blog_.
Templates
Se um bundle fornece templates, eles devem usar o Twig. Um bundle no deve fornecer um layout principal, exceto
se ele fornece uma aplicao completa.
Arquivos de Traduo
Se um bundle fornece mensagens de traduo, elas devem ser definidas no formato XLIFF; o domnio deve ser nomeado aps o nome do bundle (bundle.hello).
Um bundle no deve sobrescrever as mensagens existentes de outro bundle.
Configurao
Para proporcionar maior flexibilidade, um bundle pode fornecer definies configurveis usando os mecanismos
embutidos do Symfony2.
Definies de configurao simples contam com a entrada padro parameters da configurao do Symfony2. Os
parmetros do Symfony2 so simples pares chave/valor, um valor pode ser qualquer valor vlido em PHP. Cada nome
de parmetro deve comear com o alias do bundle, embora esta seja apenas uma sugesto de melhor prtica. O resto do
nome do parmetro usar um ponto (.) para separar partes diferentes (por exemplo, acme_hello.email.from).
O usurio final pode fornecer valores em qualquer arquivo de configurao:
YAML
# app/config/config.yml
parameters:
acme_hello.email.from: fabien@example.com
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="acme_hello.email.from">fabien@example.com</parameter>
</parameters>
PHP
// app/config/config.php
$container->setParameter(acme_hello.email.from, fabien@example.com);
INI
; app/config/config.ini
[parameters]
acme_hello.email.from = fabien@example.com
356
Captulo 3. Cookbook
$container->getParameter(acme_hello.email.from);
Mesmo esse mecanismo sendo bastante simples, voc altamente encorajado a usar a configurao semntica descrita
no cookbook.
Nota: Se voc estiver definindo servios, eles tambm devem ser prefixados com o alias do bundle.
Esta simples alterao permitir que substitua vrios partes de FOSUserBundle simplesmente criando um arquivo
com o mesmo nome.
Substituindo controladores
3.1. Cookbook
357
$response = parent::registerAction();
// do custom stuff
return $response;
}
}
Dica: Dependendo do tipo de personalizao que precisa fazer no controlador, voc pode substituir completamente o
mtodo com lgica prpria sem nem mesmo chamar parent::registerAction().
Nota: Substituir controladores desta maneira somente funciona se o bundle referencia o controlador utilizando sintaxe
padro FOSUserBundle:Registration:register nas rotas e nos templates. Esta a sintaxe recomendada.
A maioria dos recursos tambm podem ser substitudos, simplesmente criando um arquivo no mesmo caminho relativo
que estiver no bundle pai.
Por exemplo, muito comum precisar substituir o arquivo de template layout.html.twig do bundle
FOSUserBundle para utilizar o layout base de sua prpria aplicao. Uma vez que o arquivo fica no caminho
Resources/views/layout.html.twig dentro do bundle FOSUserBundle voc consegue criar seu prprio
arquivo no mesmo lugar relativo dentro do seu bundler (por exemplo, Resources/views/layout.html.twig
do bundle FOSUserBundle). O Symfony vai ignorar o arquivo dentro do FOSUserBundle e utilizar o seu no
lugar.
O mesmo vale para arquivos de rotas, configurao de Validao e outros recursos.
Nota: A substituio de recursos s funciona quando voc se refere a recursos utilizando a sintaxe recomendada
@FosUserBundle/Resources/config/routing/security.xml. Se voc se referir a recursos sem o
atalho @FosUserBundle, eles no sero substitudos.
Nota: Arquivos de tradues no funcionam da maneira descrita acima. Todos os ficheiros traduzidos sero adicionados em um conjunto de pools organizados por dominios. Symfony abrir os ficheiros de traduo dos bundles
primeiro na ordem que eles so inicializados e ento do seu diretorio app/Resource. Se houver dois ficheiros da
mesma traduo estiver especificados por diferentes Resources, o ficheiro de traduo que for aberto por ultimo ser
o utilizado.
Para obter informaes sobre como sobrescrever templates, consulte * Sobrepondo Templates de Pacote. * Como usar
herana para substituir partes de um Bundle
358
Captulo 3. Cookbook
Roteamento
O roteamento nunca automaticamente importado no Symfony2. Se voc quiser incluir as rotas de qualquer bundle,
elas devem ser manualmente importadas em algum lugar na sua aplicao (ex.: app/config/routing.yml).
A maneira mais fcil para sobrescrever o roteamento de um bundle nunca import-lo . Em vez de importar o
roteamento de um bundle de terceiros, simplesmente copie o arquivo de roteamento em sua aplicao, modifique-o e
importe-o no lugar.
Controladores
Assumindo que o bundle de terceiro envolvido usa controladores no-servios (que quase sempre o caso), voc pode
facilmente sobrescrever os controladores atravs de herana do bundle: Para mais informaes, consulte Como usar
herana para substituir partes de um Bundle.
Servios e Configurao
Existem duas opes para sobrescrever/estender um servio. Primeiro, voc pode definir o parmetro que contm
o nome do servio da classe para a sua prpria classe, definindo ele em app/config/config.yml. Isto, naturalmente, s possvel se o nome da classe est definido como um parmetro na configurao de servio do
bundle que contm o servio. Por exemplo, para sobrescrever a classe usada pelo servio translator do Symfony, voc poderia sobrescrever o parmetro translator.class. Para saber exatamente qual parmetro deve-se
sobrescrever, poder ser necessria alguma pesquisa. Para o tradutor, o parmetro definido e usado no arquivo
Resources/config/translation.xml do FrameworkBundle:
YAML
# app/config/config.yml
parameters:
translator.class:
Acme\HelloBundle\Translation\Translator
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="translator.class">Acme\HelloBundle\Translation\Translator</parameter>
</parameters>
PHP
// app/config/config.php
$container->setParameter(translator.class, Acme\HelloBundle\Translation\Translator);
Em segundo lugar, se a classe no est disponvel como um parmetro, voc quer ter a certeza que a classe ser sempre
sobrescrita quando seu bundle for utilizado, ou quando voc precisa modificar algo alm do nome da classe, voc deve
usar um compiler pass:
// src/Acme/FooBundle/DependencyInjection/Compiler/OverrideServiceCompilerPass.php
namespace Acme\DemoBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
3.1. Cookbook
359
{
$definition = $container->getDefinition(original-service-id);
$definition->setClass(Acme\DemoBundle\YourService);
}
}
Neste exemplo, buscamos a definio de servio do servio original, e definimos seu nome de classe para a nossa
prpria classe.
Veja /cookbook/service_container/compiler_passes para obter informaes sobre como usar compiler passes. Se voc quer fazer algo alm de apenas sobrescrever a classe - como adicionar uma chamada de mtodo voc s pode usar o mtodo compiler pass.
Entidades e Mapeamento de Entidade
Em andamento...
Formulrios
A fim de sobrescrever um tipo de formulrio (form type), ele tem que ser registrado como um servio (o que significa
que tem a tag definida como form.type). Voc pode, ento, sobrescrev-lo como faria com qualquer servio, como
foi explicado em Servios e Configurao. Isto, claro, somente funcionar se o tipo referido por seu alias, em vez
de ser instanciado, ex.:
$builder->add(name, custom_type);
em vez de:
$builder->add(name, new CustomType());
Validao de Metadados
Em andamento..
Tradues
Em andamento...
Como expor uma Configurao Semntica para um Bundle
Se voc abrir o arquivo de configurao da sua aplicao (geralmente app/config/config.yml), ver um nmero de diferentes namespaces de configuraes, como framework, twig e doctrine. Cada um deles configura um bundle especfico, permitindo configurar as coisas a um alto nvel e deixar o bundle fazer todas as modificaes
complexas, de baixo nvel resultantes.
Por exemplo, o cdigo a seguir diz ao FrameworkBundle para habilitar a integrao do formulrio, a qual envolve
a definio de alguns servios, bem como a integrao de outros componentes relacionados:
YAML
framework:
# ...
form:
360
true
Captulo 3. Cookbook
XML
<framework:config>
<framework:form />
</framework:config>
PHP
$container->loadFromExtension(framework, array(
// ...
form
=> true,
// ...
));
Quando voc cria um bundle, voc tem duas opes para lidar com a configurao:
1. Configurao de Servio Normal (fcil):
Voc pode especificar seus servios em um arquivo de configurao (por exemplo services.yml)
que reside em seu bundle e, ento, import-lo a partir da configurao principal da sua aplicao.
Isto realmente fcil, rpido e totalmente eficaz. Se voc fazer uso de parmetros, ento, voc
ainda tem a flexibilidade para personalizar seu bundle a partir da configurao da sua aplicao. Veja
Importando configurao com imports para mais detalhes.
2. Expondo Configurao Semntica (avanado):
Esta a maneira como a configurao feita com os bundles do ncleo (como descrito acima). A
idia bsica que, em vez de o usurio sobrescrever parmetros individuais, voc deixa ele configurar apenas algumas opes criadas . Como desenvolvedor do bundle, voc ento faz o parse desta
configurao e carrega os servios dentro de uma classe Extension. Com este mtodo, voc no
vai precisar importar quaisquer recursos de configurao a partir da configurao principal da sua
aplicao: a classe de Extenso (Extension) pode lidar com tudo isso.
A segunda opo - que voc vai aprender neste artigo - muito mais flexvel, mas tambm requer mais tempo para
configurar. Se voc est se perguntando qual mtodo deve usar, provavelmente uma boa idia comear com o mtodo
1, e depois mudar para o 2 mais tarde, se voc precisar.
O segundo mtodo tem vrias vantagens especficas:
Muito mais poderoso do que simplesmente definir parmetros: um valor de opo especfico pode acionar a
criao de muitas definies de servio;
Possibilidade de ter hierarquia de configurao
Smart merging quando possuir vrios arquivos de configurao (por exemplo config_dev.yml e
config.yml) sobrescrevendo a configurao um do outro;
Validao de configurao (se voc usar uma Classe de Configurao);
Auto-completar da IDE quando voc criar um XSD e os desenvolvedores usarem XML.
Sobresecrevendo parmetros do bundle
Se um Bundle fornecer uma classe Extension, ento, voc geralmente no deve sobrescrever quaisquer parmetros do container de servio daquele bundle. A idia que, se uma classe Extension estiver presente, cada
definio que deve ser configurvel deve estar presente na configurao disponibilizada por esta classe. Em
outras palavras, a classe Extension as definies de configurao pblicas suportadas para as quais a compatibilidade com verses anteriores ser mantida.
3.1. Cookbook
361
Se voc optar por expor uma configurao semntica para seu bundle, voc vai precisar primeiro criar uma nova classe
Extension, que ir lidar com o processo. Esta classe deve residir no diretrio DependencyInjection de seu
bundle e o seu nome deve ser construdo substituindo o sufixo Bundle do nome da classe Bundle por Extension.
Por exemplo, a classe Extension do AcmeHelloBundle seria chamada AcmeHelloExtension:
// Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeHelloExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// ... where all of the heavy logic is done
}
public function getXsdValidationBasePath()
{
return __DIR__./../Resources/config/;
}
public function getNamespace()
{
return http://www.example.com/symfony/schema/;
}
}
Nota: Os mtodos getXsdValidationBasePath e getNamespace so necessrios apenas se o bundle fornece XSDs opcionais para a configurao.
A presena da classe anterior significa que agora voc pode definir um namespace de configurao acme_hello
em qualquer arquivo de configurao. O namespace acme_hello construdo a partir do nome da classe de extenso, removendo a palavra Extension e, em seguida, deixando o resto do nome todo em letras minsculas e com
underscores. Em outras palavras, AcmeHelloExtension torna-se acme_hello.
Voc pode comear a especificar a configurao sob este namespace imediatamente:
YAML
# app/config/config.yml
acme_hello: ~
XML
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:acme_hello="http://www.example.com/symfony/schema/"
xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/sc
<acme_hello:config />
362
Captulo 3. Cookbook
PHP
// app/config/config.php
$container->loadFromExtension(acme_hello, array());
Dica: Se voc seguir as convenes de nomenclatura estabelecidas acima, ento, o mtodo load() de seu cdigo
de extenso sempre chamado, uma vez que seu bundle est registrado no Kernel. Em outras palavras, mesmo se
o usurio no fornecer qualquer configurao (ou seja, a entrada acme_hello nem mesmo aparecer), o mtodo
load() ser chamado e passado um array $configs vazio. Voc ainda pode fornecer alguns padres para seu
bundle se desejar.
Sempre que um usurio inclui o namespace acme_hello em um arquivo de configurao, a configurao abaixo
dele adicionada um array de configuraes e passado para o mtodo load() de sua extenso (o Symfony2
automaticamente converte XML e YAML para um array).
Assuma a seguinte configurao:
YAML
# app/config/config.yml
acme_hello:
foo: fooValue
bar: barValue
XML
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:acme_hello="http://www.example.com/symfony/schema/"
xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/sc
<acme_hello:config foo="fooValue">
<acme_hello:bar>barValue</acme_hello:bar>
</acme_hello:config>
</container>
PHP
// app/config/config.php
$container->loadFromExtension(acme_hello, array(
foo => fooValue,
bar => barValue,
));
O array passado para seu mtodo load() ficar parecido com o seguinte:
array(
array(
foo => fooValue,
3.1. Cookbook
363
Observe que este um array de arrays, e no apenas um nico array simples de valores de configurao. Isso
intencional. Por exemplo, se acme_hello aparece em outro arquivo de configurao - digamos config_dev.yml
- com valores diferentes abaixo dele, ento, o array de entrada poderia ser assim:
array(
array(
foo
bar
),
array(
foo
baz
),
)
=> fooValue,
=> barValue,
=> fooDevValue,
=> newConfigEntry,
Cuidado: Certifique-se que a tcnica de merge acima faz sentido para o seu bundle. Este apenas um exemplo, e
voc deve ter cuidado para no us-lo cegamente.
Dentro do load() a varivel $container refere-se a um container que apenas sabe sobre essa configurao de
namespace (ou seja, no contm informao de servio carregada a partir de outros bundles). O objetivo do mtodo
load() manipular o container, adicionando e configurando quaisquer mtodos ou servios necessrios ao seu
bundle.
Carregando Recursos de Configurao Externos Algo comum de se fazer carregar um arquivo de configurao
externo que pode conter a maioria dos servios necessrios para o seu bundle. Por exemplo, suponha que voc tem um
arquivo services.xml que contm muitas das configuraes de servios do seu bundle:
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
public function load(array $configs, ContainerBuilder $container)
{
364
Captulo 3. Cookbook
Voc pode at fazer isso condicionalmente, com base em um dos valores de configurao. Por exemplo, supondo que
voc quer carregar um conjunto de servios somente se a opo enabled for passada e definida com true:
public function load(array $configs, ContainerBuilder $container)
{
// ... prepare your $config variable
$loader = new XmlFileLoader($container, new FileLocator(__DIR__./../Resources/config));
if (isset($config[enabled]) && $config[enabled]) {
$loader->load(services.xml);
}
}
Configurando Servios e Definindo Parmetros Uma vez que voc j carregou alguma configurao de servio,
voc pode precisar modificar a configurao com base em alguns dos valores de entrada. Por exemplo, supondo
que existe um servio cujo primeiro argumento alguma string type que ele ir usar internamente. Voc gostaria
que isto fosse facilmente configurado pelo usurio do bundle, ento, em seu arquivo de configurao do servio (ex.
services.xml), voc define este servio e usa um parmetro em branco - acme_hello.my_service_type como seu primeiro argumento:
Mas por que definir um parmetro vazio e ento pass-lo ao seu servio? A resposta que voc vai definir este
parmetro em sua classe de extenso, com base nos valores de configurao de entrada. Suponha, por exemplo, que
voc quer permitir ao usurio definir esta opo type sob uma chave chamada my_type. Para fazer isso, adicione o
seguinte ao mtodo load():
public function load(array $configs, ContainerBuilder $container)
{
// ... prepare your $config variable
$loader = new XmlFileLoader($container, new FileLocator(__DIR__./../Resources/config));
$loader->load(services.xml);
if (!isset($config[my_type])) {
throw new \InvalidArgumentException(The "my_type" option must be set);
3.1. Cookbook
365
}
$container->setParameter(acme_hello.my_service_type, $config[my_type]);
}
Agora, o usurio pode efetivamente configurar o servio especificando o valor de configurao my_type:
YAML
# app/config/config.yml
acme_hello:
my_type: foo
# ...
XML
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:acme_hello="http://www.example.com/symfony/schema/"
xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/sc
<acme_hello:config my_type="foo">
<!-- ... -->
</acme_hello:config>
</container>
PHP
// app/config/config.php
$container->loadFromExtension(acme_hello, array(
my_type => foo,
// ...
));
Parmetros Globais Quando estiver configurando o container, esteja ciente de que voc tem os seguintes parmetros
globais disponveis para uso:
kernel.name
kernel.environment
kernel.debug
kernel.root_dir
kernel.cache_dir
kernel.logs_dir
kernel.bundle_dirs
kernel.bundles
kernel.charset
Cuidado: Todos os nomes de parmetros e servios, comeando com um _ so reservados para o framework, e
os novos no devem ser definidos por bundles.
366
Captulo 3. Cookbook
At agora, voc j fez o merge de seus arrays de configurao manualmente e est verificando a presena de valores de
configurao manualmente usando a funo isset() do PHP. Um sistema opcional de Configurao est tambm
disponvel que pode ajudar com merge, validao, valores padro e normalizao de formato.
Nota: Normalizao de formato refere-se ao fato de que certos formatos - em grande parte XML - resultam em arrays
de configurao ligeiramente diferentes e que estes arrays precisam ser normalizados para corresponder com todo o
resto.
Para tirar vantagem deste sistema, voc vai criar uma classe Configuration e construir uma rvore que define a
sua configurao nesta classe:
// src/Acme/HelloBundle/DependencyInjection/Configuration.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root(acme_hello);
$rootNode
->children()
->scalarNode(my_type)->defaultValue(bar)->end()
->end();
return $treeBuilder;
}
Este um exemplo muito simples, mas agora voc pode usar essa classe em seu mtodo load() para o merge da sua
configurao e forar a validao. Se outras opes que no sejam my_type forem passadas, o usurio ser notificado
com uma exceo de que uma opo no suportada foi passada:
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// ...
}
3.1. Cookbook
367
Dump de Configurao Padro Novo na verso 2.1: O comando config:dump-reference foi adicionado
no Symfony 2.1
O comando config:dump-reference permite que a configurao padro de um bundle seja impressa no console
em YAML.
Enquanto a configurao do bundle est localizada no local padro (YourBundle\DependencyInjection\Configuration)
e no tem um __constructor() ele vai funcionar automaticamente. Se voc tem algo diferente a sua classe
Extension ter que sobrescrever o mtodo Extension::getConfiguration(). Fazendo ele retornar uma
instncia de sua Configuration.
Comentrios e exemplos podem ser adicionados aos ns de configurao utilizando os mtodos ->info() e
->example():
// src/Acme/HelloBundle/DependencyExtension/Configuration.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root(acme_hello);
$rootNode
->children()
->scalarNode(my_type)
->defaultValue(bar)
->info(what my_type configures)
->example(example setting)
->end()
->end()
;
return $treeBuilder;
}
368
Captulo 3. Cookbook
{
public function build(ContainerBuilder $container)
{
parent::build($container);
// register extensions that do not follow the conventions manually
$container->registerExtension(new UnconventionalExtensionClass());
}
}
Neste caso, a classe de extenso tambm deve implementar um mtodo getAlias() e retornar um alias exclusivo
nomeado aps o bundle (por exemplo, acme_hello). Isto necessrio porque o nome da classe no segue os padres
terminando em Extension.
Alm disso, o mtodo load() de sua extenso ser apenas chamado se o usurio especificar o alias acme_hello
em pelo menos um arquivo de configurao . Mais uma vez, isso porque a classe de extenso no segue os padres
acima referidos, de modo que, nada acontece automaticamente.
3.1.10 Email
Como enviar um e-mail
Enviar e-mails uma tarefa clssica para qualquer aplicao web e, possui complicaes especiais e potenciais armadilhas. Em vez de recriar a roda, uma soluo para enviar e-mails usar o SwiftmailerBundle, que aproveita o
poder da biblioteca Swiftmailer.
Nota: No esquea de ativar o bundle em seu kernel antes de us-lo:
public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
);
// ...
}
Configurao
Antes de usar o Swiftmailer, no esquea de incluir a sua configurao. O nico parmetro de configurao obrigatrio
o transport:
YAML
# app/config/config.yml
swiftmailer:
transport: smtp
encryption: ssl
auth_mode: login
host:
smtp.gmail.com
username:
your_username
password:
your_password
XML
3.1. Cookbook
369
<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-->
<swiftmailer:config
transport="smtp"
encryption="ssl"
auth-mode="login"
host="smtp.gmail.com"
username="your_username"
password="your_password" />
PHP
// app/config/config.php
$container->loadFromExtension(swiftmailer, array(
transport => "smtp",
encryption => "ssl",
auth_mode => "login",
host
=> "smtp.gmail.com",
username
=> "your_username",
password
=> "your_password",
));
A maioria das configuraes do Swiftmailer lidam com a forma como as mensagens devem ser entregues.
Os seguintes atributos de configurao esto disponveis:
transport (smtp, mail, sendmail, ou gmail)
username
password
host
port
encryption (tls, ou ssl)
auth_mode (plain, login, ou cram-md5)
spool
type (como ser o queue de mensagens, so suportados file ou memory, veja Como fazer Spool de
E-mail)
path (onde armazenar as mensagens)
delivery_address (um endereo de email para onde sero enviados TODOS os e-mails)
disable_delivery (defina como true para desabilitar completamente a entrega)
Enviando e-mails
A biblioteca Swiftmailer funciona atravs da criao, configurao e, ento, o envio de objetos Swift_Message. O
mailer responsvel pela entrega da mensagem e acessvel atravs do servio mailer. No geral, o envio de um
e-mail bastante simples:
370
Captulo 3. Cookbook
Para manter as coisas desacopladas, o corpo do e-mail foi armazenado em um template e renderizado atravs do
mtodo renderView().
O objeto $message suporta mais opes, como, a incluso de anexos, a adio de contedo HTML, e muito mais.
Felizmente, o Swiftmailer cobre o tpico Criao de Mensagens em grande detalhe na sua documentao.
Dica: Vrios outros artigos cookbook relacionados ao envio de e-mails esto disponveis no Symfony2:
Como usar o Gmail para enviar E-mails
Como Trabalhar com E-mails Durante o Desenvolvimento
Como fazer Spool de E-mail
XML
<!-- app/config/config_dev.xml -->
<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmai
-->
<swiftmailer:config
transport="gmail"
3.1. Cookbook
371
username="your_gmail_username"
password="your_gmail_password" />
PHP
// app/config/config_dev.php
$container->loadFromExtension(swiftmailer, array(
transport => "gmail",
username => "your_gmail_username",
password => "your_gmail_password",
));
Est pronto!
Nota: O transporte gmail simplesmente um atalho que usa o transporte smtp e seta as definies encryption,
auth_mode e host para funcionar com o Gmail.
Voc pode desativar o envio de e-mails, definindo a opo disable_delivery para true. Este o padro para o
ambiente test na distribuio Standard. Se voc fizer isso especificamente na configurao test, ento os emails
no ser enviados quando voc executar testes, mas continuaro a ser enviados nos ambientes prod e dev:
YAML
# app/config/config_test.yml
swiftmailer:
disable_delivery: true
XML
<!-- app/config/config_test.xml -->
<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-->
<swiftmailer:config
disable-delivery="true" />
PHP
// app/config/config_test.php
$container->loadFromExtension(swiftmailer, array(
disable_delivery => "true",
));
372
Captulo 3. Cookbook
Se voc tambm gostaria de desativar a entrega no ambiente dev, simplesmente adicione esta configurao ao arquivo
config_dev.yml.
Enviando para um Endereo Especificado
Voc tambm pode optar por enviar todos os emails para um endereo especfico, em vez do endereo atualmente
especificado, ao enviar a mensagem. Isto pode ser feito atravs da opo delivery_address:
YAML
# app/config/config_dev.yml
swiftmailer:
delivery_address: dev@example.com
XML
<!-- app/config/config_dev.xml -->
<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-->
<swiftmailer:config
delivery-address="dev@example.com" />
PHP
// app/config/config_dev.php
$container->loadFromExtension(swiftmailer, array(
delivery_address => "dev@example.com",
));
No ambiente dev, o e-mail ser enviado para dev@example.com. O Swiftmailer ir adicionar um cabealho extra
para o e-mail, X-Swift-To contendo o endereo substitudo, assim voc ainda poder visualizar para quem ele teria
sido enviado.
Nota: Alm do endereo to, ele tambm ir parar os e-mails sendo enviados para quaisquer endereos CC e BCC
definidos. O SwiftMailer ir adicionar cabealhos adicionais para o e-mail com os endereos substitudos neles. Eles
so X-Swift-Cc e X-Swift-Bcc para os endereos CC e BCC, respectivamente.
3.1. Cookbook
373
Voc pode visualizar quaisquer e-mails enviados por uma pgina quando estiver no ambiente dev usando a Barra de
Ferramentas para Debug Web. O cone de e-mail na barra de ferramentas ir mostrar quantos e-mails foram enviados.
Se voc clicar nele, um relatrio mostrando os detalhes dos e-mails ser aberto.
Se voc estiver enviando um e-mail e imediatamente executar um redirecionamento, voc precisar definir a opo
intercept_redirects para true no arquivo config_dev.yml para que possa ver o e-mail na barra de
ferramentas de debug web antes de ser redirecionado.
Como fazer Spool de E-mail
Quando voc estiver usando o SwiftmailerBundle para enviar um email de uma aplicao Symfony2, ele ir,
por padro, enviar o e-mail imediatamente. Voc pode, entretanto, desejar evitar um impacto no desempenho da
comunicao entre o Swiftmailer e o transporte do e-mail, o que poderia fazer com que o usurio tenha que
aguardar a prxima pgina carregar, enquanto est enviando o e-mail. Isto pode ser evitado escolhendo pelo spool
dos e-mails em vez de envi-los diretamente. Isto significa que o Swiftmailer no tentar enviar o email, mas,
ao invs, salvar a mensagem em algum lugar, como um arquivo. Outro processo poder ento ler a partir do spool
e cuidar de enviar os e-mails no spool. Atualmente, apenas o spool para arquivo ou memria so suportados pelo
Swiftmailer.
Spool usando memria
Quando voc usa o spool para armazenar os e-mails em memria, eles so enviados mesmo antes do kernel terminar.
Isto significa que o e-mail s enviado se o pedido foi todo executado sem qualquer exceo no tratada ou quaisquer
erros. Para configurar o SwiftMailer com a opo de memria, utilize a seguinte configurao:
YAML
# app/config/config.yml
swiftmailer:
# ...
spool: { type: memory }
XML
<!-- app/config/config.xml -->
<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmai
-->
<swiftmailer:config>
<swiftmailer:spool type="memory" />
</swiftmailer:config>
PHP
// app/config/config.php
$container->loadFromExtension(swiftmailer, array(
...,
spool => array(type => memory)
));
374
Captulo 3. Cookbook
XML
<!-- app/config/config.xml -->
<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-->
<swiftmailer:config>
<swiftmailer:spool
type="file"
path="/path/to/spool" />
</swiftmailer:config>
PHP
// app/config/config.php
$container->loadFromExtension(swiftmailer, array(
// ...
spool => array(
type => file,
path => /path/to/spool,
)
));
Dica: Se voc deseja armazenar o spool em algum lugar no diretrio do seu projeto, lembre-se que voc pode usar o
parmetro %kernel.root_dir% para referenciar o raiz do seu projeto:
path: %kernel.root_dir%/spool
Agora, quando a sua aplicao enviar um e-mail, ele no ser realmente enviado, ao invs, ser adicionado ao spool.
O envio de mensagens do spool feito separadamente. Existe um comando do console para enviar as mensagens que
encontram-se no spool:
php app/console swiftmailer:spool:send
Ele tem uma opo para limitar o nmero de mensagens a serem enviadas:
php app/console swiftmailer:spool:send --message-limit=10
Claro que, na realidade, voc no vai querer executar ele manualmente. Em vez disso, o comando do console deve ser
disparado por um cron job ou tarefa agendada e executar em um intervalo regular.
3.1. Cookbook
375
3.1.11 Segurana
Listas de controle de acesso (ACLs)
Em aplicativos complexos, comumente existem o problema que as decises de permitir ou negar acesso no podem ser
tomadas somente baseada no usurio (Token) solicitando acesso, mas tambm deve levar em considerao o objeto
de domnio que est tendo o acesso solicitado. a que o sistema ACL entra em ao.
Imagine que est projetando um sistema de blog onde seus usurio podem comentar os textos (posts) publicados.
Agora, voc deseja que um usurio possa editar seus prprios comentrios, mas no os comentrios dos outros usurios. Alm disso, voc como administrador deseja pode editar todos os comentrios. Neste cenrio, Comment seria
seu objeto de domnio ao qual voc quer restringir acesso. Voc poderia usar vrias abordagens para conseguir o
mesmo resultado. Duas dessas seriam:
Impor segurana em seus mtodos: Basicamente, isso significa que dever manter referncias em cada
Comment de todos os usurios que tm acesso e depois comparar com o usurio Token solicitando acesso.
Impor segurana com perfis: Nesta abordagem, voc adicionaria um perfil para cada objeto Comment, isto ,
ROLE_COMMENT_1, ROLE_COMMENT_2, etc.
Ambas abordagens so perfeitamnete vlidas. Elas, porm, amarram sua lgica de autorizao de acesso com seu
cdigo, deixando-o mais difcil de reusar em outros contextos. Tambm aumenta a dificuldade de criar testes unitrios.
Alm disso, pode-se ter problemas de performance caso muitos usurios tenham acesso a um nico objeto de domnio.
Felizmente, h uma maneira melhor que veremos a seguir.
Configurao
Agora, antes de realmente comearmos, precisamos fazer algumas configuraes. Primeiramente, precisamos configurar a conexo de banco de dados queo sistema ACL utilizar.
YAML
# app/config/security.yml
security:
acl:
connection: default
XML
<!-- app/config/security.xml -->
<acl>
<connection>default</connection>
</acl>
PHP
// app/config/security.php
$container->loadFromExtension(security, acl, array(
connection => default,
));
Nota: O sistema ACL requer que ao menos uma conexo Doctrine DBAL esteja configurada. Isto, porm, no
significa que voc tem que utilizar o Doctrine para mapear seus objetos de domnio. Voc pode utilizar qualquer
mapeamento que quiser para seus objetos, seja ele Doctrine ORM, Mongo ODM, Propel, ou SQL puro. A escolha
sua.
376
Captulo 3. Cookbook
Depois de configurar a conexo, temos que importar a estrutura do banco de dados. Felizmente, temos um comando
para isto. Rode o seguinte comando.
php app/console init:acl
Comeando
Voltando ao nosso pequeno exemplo do incio, vamos implementar o sistema ACL dele.
Criando uma ACL, e adicionando uma entrada (ACE)
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
// ...
// BlogController.php
public function addCommentAction(Post $post)
{
$comment = new Comment();
// setup $form, and bind data
// ...
if ($form->isValid()) {
$entityManager = $this->get(doctrine.orm.default_entity_manager);
$entityManager->persist($comment);
$entityManager->flush();
// creating the ACL
$aclProvider = $this->get(security.acl.provider);
$objectIdentity = ObjectIdentity::fromDomainObject($comment);
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$securityContext = $this->get(security.context);
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
}
}
H algumas importantes decises de implementao neste trecho de cdigo. Por enquanto, gostaria de destacar duas.
Primeiro, note que o mtodo ->createAcl() no aceita objetos de domnio diretamente, mas somente implementaes de ObjectIdentityInterface. Este passo adicional permite que trabalhe com ACLs mesmo quando no
tiver uma instncia do objeto de domnio disponvel. Isto ser extremamente til se voc quiser verificar permisses
para um grande nmero de objetos sem realmente criar os objetos.
Outra parte interessante a chamada ->insertObjectAce(). Em nosso exemplo, estamos concedendo ao usurio
que est autenticado permisso de proprietrio do objeto Comment. MaskBuilder::MASK_OWNER uma mscara
(integer bitmask) pr-definida. No se preocupe que MaskBuilder abstrai a maior parte dos detalhes tcnicos, mas
saiba que utilizando esta tcnica possvel armazenar muitas permisses diferentes em apenas uma linha do banco de
dados, o que significa uma considervel melhora na performance.
3.1. Cookbook
377
Dica: A ordem em que as entradas de controle (ACE) so checadas importante. Como regra geral, voc deve
colocar as entradas mais especficas no incio.
Verificando o acesso
// BlogController.php
public function editCommentAction(Comment $comment)
{
$securityContext = $this->get(security.context);
// check for edit access
if (false === $securityContext->isGranted(EDIT, $comment))
{
throw new AccessDeniedException();
}
// retrieve actual comment object, and do your editing here
// ...
}
Neste exemplo, verificamos se o usurio tem permisso de edio (EDIT). Internamente, Symfony2 mapea a permisso
para vrias mscaras (integer bitmasks) e verifica se o usurio tem alguma delas.
Nota: Voc pode definir at 32 permisses base (dependendo do seu SO, pode variar entre 30 e 32). Voc ainda pode
definir permises cumulativas.
Permisses Cumulativas
No nosso primeiro exemplo acima, ns concedemos somente a permisso base OWNER. Apesar disso significar que
o usurio pode executar qualquer operao no objeto de domnio tais como exibir, editar, etc, em alguns casos voc
pode querer conceder essas permisses explicitamente.
O MaskBuilder pode ser usado para criar mscaras (bit masks) facilmente atravs da combinao de vrias permisses base.
$builder = new MaskBuilder();
$builder
->add(view)
->add(edit)
->add(delete)
->add(undelete)
;
$mask = $builder->get(); // int(15)
Este inteiro (integer bitmask) pode ento ser usado para conceder a um usurio todas as permisses base que voc
adicionou acima.
$acl->insertObjectAce(new UserSecurityIdentity(johannes), $mask);
O usurio agora poder exibir, editar, deletar e desfazer a deleo dos objetos.
378
Captulo 3. Cookbook
Os recursos de uma instncia do objeto de segurana do Symfony2 tem base no conceito de uma Lista de Controle
de Acesso (ACL). Cada instncia do objeto de domnio tem a sua prpria ACL. A instncia ACL contm uma lista
detalhada de Entradas de Controle de Acesso (ACEs), que so usadas para tomar decises de acesso. O sistema ACL
do Symfony2 concentra-se em dois objetivos principais:
fornecer uma maneira de recuperar uma grande quantidade de ACLs/ACEs de forma eficiente para os seus
objetos de domnio e para modific-los;
fornecer uma maneira de tomar decises facilmente para saber se uma pessoa est autorizada a executar uma
ao em um objeto de domnio ou no.
Conforme indicado no primeiro ponto, uma das principais capacidades do sistema ACL do Symfony2 uma forma de
recuperar ACLs/ACEs com alto desempenho. Isto extremamente importante, pois cada ACL pode ter vrias ACEs, e
herdam de outra ACL em forma de rvore. Portanto, nenhum ORM utilizado, em vez disso, a implementao padro
interage com a sua conexo diretamente usando o DBAL do Doctrine.
Identidades de Objeto O sistema de ACL completamente desacoplado de seus objetos de domnio. Eles no tem
que ser armazenados na mesma base de dados, ou no mesmo servidor. De forma a atingir esse desacoplamento, os
seus objetos, no sistema ACL, so representados atravs de objetos de identidade de objeto. Toda vez que voc deseja
recuperar a ACL para um objeto de domnio, o sistema de ACL vai primeiro criar uma identidade de objeto do seu
objeto de domnio e, em seguida, passar essa identidade de objeto ao provedor ACL para processamento adicional.
Identidades de Segurana Isto anlogo identidade de objeto, mas representa um usurio ou um papel em sua
aplicao. Cada papel ou usurio tem a sua prpria identidade de segurana.
Estrutura da Tabela de Banco de Dados
A implementao padro usa cinco tabelas de banco de dados, conforme listado abaixo. Em uma aplicao tpica, as
tabelas so ordenadas da com menos linhas para a com mais linhas:
acl_security_identities: Esta tabela registra todas as identidades de segurana (SID) que detm ACEs.
A implementao padro vem com duas identidades de segurana: RoleSecurityIdentity e
UserSecurityIdentity
acl_classes: Esta classe mapeia nomes de classes para um ID nico, que pode ser referenciado a partir de outras
tabelas.
acl_object_identities: Cada linha nessa tabela representa um nico domnio de instncia de objeto.
acl_object_identity_ancestors: Esta tabela permite que todos os ancestrais de uma ACL, possam ser determinados de uma maneira muito eficiente.
acl_entries: Esta tabela contm todas as ACEs. Essa tipicamente a tabela com o maior nmero de linhas. Ela
pode conter dezenas de milhes sem afetar significativamente o desempenho.
3.1. Cookbook
379
Entradas de controle de acesso podem ter escopos diferentes nos quais se aplicam. No Symfony2, existem basicamente
dois escopos diferentes:
Class-Scope: Essas entradas se aplicam a todos os objetos com a mesma classe.
Object-Scope: Este foi o escopo utilizado unicamente no captulo anterior, e ele s se aplica a um objeto
especfico.
s vezes, voc vai encontrar a necessidade de aplicar uma ACE apenas a um campo especfico do objeto. Vamos
dizer que voc quer que o ID somente seja visualizado por um administrador, mas no por seu servio do cliente. Para
resolver este problema comum, mais dois sub-escopos foram adicionados:
Classe-Field-Scope: Essas entradas se aplicam a todos os objetos com a mesma classe, mas apenas a um campo
especfico dos objetos.
Object-Field-Scope: Essas entradas se aplicam a um objeto especfico, e apenas a um campo especfico do
objeto.
Decises de Pr-Autorizao
Para as decises de pr-autorizao, que so as decises tomadas antes de qualquer mtodo seguro (ou ao segura)
ser invocado, o servio AccessDecisionManager usado. O AccessDecisionManager tambm usado para tomar
decises de autorizao com base em papis. Assim como os papis, o sistema de ACL adiciona vrios atributos
novos que podem ser utilizados para verificar se h permisses diferentes.
Atributo
VIEW
Significado Pretendido
Bitmasks In
EDIT
CREATE
DELETE
UNDELETE
OPERATOR
MASTER
OWNER
VIEW, EDI
OPERATOR
ou OWNER
EDIT, OPER
MASTER o
CREATE, O
MASTER o
DELETE, O
MASTER o
UNDELETE
OPERATOR
ou OWNER
OPERATOR
ou OWNER
Se algum tem permisso para executar todas as aes acima, e alm disso
autorizada a conceder qualuqer uma das permisses acima para outros.
Se algum dono do objeto de domnio. Um dono pode executar qualquer
uma das aes acima e conceder permisses master e owner
380
Captulo 3. Cookbook
MASTER o
OWNER
Extensibilidade O mapa de permisso acima no de forma alguma esttico, e teoricamente poderia ser completamente substitudo. No entanto, ele deve cobrir a maioria dos problemas que voc encontra e para a interoperabilidade
com outros bundles, voc incentivado a manter o significado previsto por eles.
Decises Ps-Autorizao
Decises ps-autorizao so feitas depois de um mtodo seguro ter sido invocado, e normalmente envolvem o objeto
de domnio que retornado por esse mtodo. Aps a invocao de providers, tambm permitido modificar ou filtrar
o objeto de domnio antes de ser devolvido.
Devido s limitaes atuais da linguagem PHP, no existem recursos de ps-autorizao integrados no componente
de segurana. No entanto, existe um JMSSecurityExtraBundle experimental que adiciona esses recursos. Consulte a
documentao para obter mais informaes sobre a forma como isso feito.
Processo para Tomar Decises de Autorizao
A classe ACL fornece dois mtodos para determinar se uma identidade de segurana tem as bitmasks necessrias,
isGranted e isFieldGranted. Quando a ACL recebe um pedido de autorizao atravs de um desses mtodos,
ela delega esse pedido para uma implementao de PermissionGrantingStrategy. Isso permite a substituio da forma
como as decises de acesso so tomadas sem modificar a prpria classe ACL.
O PermissionGrantingStrategy primeiro verifica todos os seus ACEs object-scope, se nenhum for aplicvel, as ACEs
class-scope sero verificadas, se nenhuma for aplicvel, em seguida, o processo vai ser repetido com as ACEs da ACL
pai. Se no existe nenhuma ACL pai, uma exceo ser lanada.
Como forar HTTPS ou HTTP para URLs Diferentes
Atravs da configurao de segurana, voc pode forar que reas do seu site usem o protocolo HTTPS. Isso feito
atravs das regras access_control usando a opo requires_channel. Por exemplo, se voc quiser forar
que todas as URLs que comeam com /secure utilizem HTTPS ento voc poderia usar a seguinte configurao:
YAML
access_control:
- path: ^/secure
roles: ROLE_ADMIN
requires_channel: https
XML
<access-control>
<rule path="^/secure" role="ROLE_ADMIN" requires_channel="https" />
</access-control>
PHP
access_control => array(
array(
path
=> ^/secure,
role
=> ROLE_ADMIN,
requires_channel => https,
),
),
3.1. Cookbook
381
O prprio formulrio de login precisa permitir acesso annimo, caso contrrio, os usurios no seriam capazes
de se autenticar. Para for-lo a usar HTTPS voc pode ainda usar regras access_control com o papel
IS_AUTHENTICATED_ANONYMOUSLY:
YAML
access_control:
- path: ^/login
roles: IS_AUTHENTICATED_ANONYMOUSLY
requires_channel: https
XML
<access-control>
<rule path="^/login"
role="IS_AUTHENTICATED_ANONYMOUSLY"
requires_channel="https" />
</access-control>
PHP
access_control => array(
array(
path
=> ^/login,
role
=> IS_AUTHENTICATED_ANONYMOUSLY,
requires_channel => https,
),
),
Tambm possvel especificar o uso de HTTPS na configurao de roteamento. Veja Como forar as rotas a usar
sempre HTTPS ou HTTP para mais detalhes.
Como personalizar o seu Formulrio de Login
Usar um formulrio de login para autenticao um mtodo comum e flexvel para lidar com a autenticao no
Symfony2. Praticamente todos os aspectos do formulrio de login podem ser personalizados. A configurao padro
completa mostrada na prxima seo.
Configurao de Referncia do Formulrio de Login
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# the user is redirected here when he/she needs to login
login_path:
/login
# if true, forward the user to the login form instead of redirecting
use_forward:
false
# submit the login form here
check_path:
/login_check
382
Captulo 3. Cookbook
post_only:
true
_csrf_token
authenticate
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
check_path="/login_check"
login_path="/login"
use_forward="false"
always_use_default_target_path="false"
default_target_path="/"
target_path_parameter="_target_path"
use_referer="false"
failure_path="null"
failure_forward="false"
username_parameter="_username"
password_parameter="_password"
csrf_parameter="_csrf_token"
intention="authenticate"
post_only="true"
/>
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
check_path
=> /login_check,
login_path
=> /login,
user_forward
=> false,
always_use_default_target_path => false,
default_target_path
=> /,
target_path_parameter
=> _target_path,
use_referer
=> false,
failure_path
=> null,
failure_forward
=> false,
3.1. Cookbook
383
username_parameter
password_parameter
csrf_parameter
intention
post_only
=>
=>
=>
=>
=>
_username,
_password,
_csrf_token,
authenticate,
true,
)),
),
));
Voc pode alterar o local para onde o formulrio de login redireciona aps um login bem-sucedido usando opes de configurao diferentes. Por padro, o formulrio ir redirecionar para a URL solicitada pelo usurio
(ou seja, a URL que acionou o formulrio de login que est sendo exibido). Por exemplo, se o usurio solicitou
http://www.example.com/admin/post/18/edit, ento, aps o login bem-sucedido, ele ser enviado de
volta para http://www.example.com/admin/post/18/edit . Isto feito atravs do armazenamento da
URL solicitada em sesso. Se nenhuma URL estiver presente na sesso (talvez o usurio acessou diretamente a pgina
de login), ento, o usurio redirecionado para a pgina padro, que / (ou seja, a homepage). Voc pode alterar esse
comportamento de vrias formas.
Nota: Como mencionado, por padro, o usurio redirecionado para a pgina que ele solicitou originalmente. s
vezes, isso pode causar problemas, como, por exemplo, uma requisio AJAX em background aparece sendo a
ltima URL visitada, fazendo com que o usurio seja redirecionado para l. Para informaes sobre como controlar
esse comportamento, consulte /cookbook/security/target_path.
Alterando a Pgina Padro Primeiro, a pgina padro pode ser definida (ou seja, a pgina para a qual o usurio ser
redirecionado se nenhuma pgina anterior foi armazenada em sesso). Para configur-la para /admin use a seguinte
configurao:
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
default_target_path: /admin
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
default_target_path="/admin"
/>
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
384
Captulo 3. Cookbook
Agora, quando no houver uma URL definida na sesso, os usurios sero enviados para /admin.
Sempre Redirecionar para a Pgina Padro Voc pode fazer com que os usurios sejam sempre redirecionados
para a pgina padro, independentemente da URL que eles tenham solicitado anteriormente, definindo a a opo
always_use_default_target_path para true:
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
always_use_default_target_path: true
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
always_use_default_target_path="true"
/>
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
...,
always_use_default_target_path => true,
)),
),
));
Usando a URL de referncia (Referring URL) No caso de nenhuma URL anterior estar armazenada em sesso,
voc pode desejar usar o HTTP_REFERER no lugar, pois este, muitas vezes, o mesmo. Voc pode fazer isso
configurando o use_referer para true (o padro false):
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
3.1. Cookbook
385
# ...
use_referer:
true
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
use_referer="true"
/>
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
...,
use_referer => true,
)),
),
));
Novo na verso 2.1: A partir da verso 2.1, se o referer for igual opo login_path, o usurio ser redirecionado
para default_target_path.
Controlando a URL de redirecionamento dentro do Formulrio Voc tambm pode sobrescrever para onde o
usurio ser redirecionado, atravs do prprio formulrio incluindo um campo oculto com o nome _target_path.
Por exemplo, para redirecionar para a URL definida por rota account, use o seguinte:
Twig
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form action="{{ path(login_check) }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<input type="hidden" name="_target_path" value="account" />
<input type="submit" name="login" />
</form>
PHP
<!-- src/Acme/SecurityBundle/Resources/views/Security/login.html.php -->
<?php if ($error): ?>
<div><?php echo $error->getMessage() ?></div>
<?php endif; ?>
386
Captulo 3. Cookbook
Agora, o usurio ser redirecionado para o valor do campo oculto do formulrio. O valor do atributo pode ser um
caminho relativo, uma URL absoluta, ou um nome de rota. Voc pode at mesmo alterar o nome do campo oculto do
formulrio, alterando a opo target_path_parameter para um outro valor.
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
target_path_parameter: redirect_url
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
target_path_parameter="redirect_url"
/>
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
target_path_parameter => redirect_url,
)),
),
));
Redirecionando quando o Login Falhar Alm de redirecionar o usurio aps um login bem-sucedido, voc tambm pode definir a URL que o usurio deve ser redirecionado aps uma falha de login (por exemplo, quando um nome
de usurio ou senha invlida foi submetida). Por padro, o usurio redirecionado de volta ao formulrio de login.
Voc pode definir isso para uma URL diferente com a seguinte configurao:
YAML
# app/config/security.yml
security:
firewalls:
main:
3.1. Cookbook
387
form_login:
# ...
failure_path: /login_failure
XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
failure_path="login_failure"
/>
</firewall>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
...,
failure_path => login_failure,
)),
),
));
Em primeiro lugar, independentemente de onde os seus dados de usurio esto vindo, voc vai precisar criar uma classe
User que representa esses dados. No entando, a classe User pode parecer com o que voc quiser e conter quaisquer
dados. O nico requisito que a classe implemente UserInterface. Os mtodos dessa interface devem ser
definidos na classe de usurio personalizada: getRoles(), getPassword(), getSalt(), getUsername(),
eraseCredentials(). Tambm pode ser til implementar a interface EquatableInterface, a qual define
um mtodo para verificar se o usurio igual ao usurio atual. Essa interface requer um mtodo isEqualTo() .
Vamos ver isso na prtica:
// src/Acme/WebserviceUserBundle/Security/User/WebserviceUser.php
namespace Acme\WebserviceUserBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class WebserviceUser implements UserInterface, EquatableInterface
{
388
Captulo 3. Cookbook
private
private
private
private
$username;
$password;
$salt;
$roles;
3.1. Cookbook
389
Se voc tiver mais informaes sobre seus usurios - como um primeiro nome - ento voc pode adicionar um campo
firstName para guardar esse dado.
Criar um Provider de Usurio
Agora que voc tem uma classe User, voc vai criar um provider de usurio, que ir pegar informaes de usurio de
algum servio web, criar um objeto WebserviceUser e popular ele com os dados.
O provider de usurio apenas uma classe PHP que deve implementar a UserProviderInterface, que requer a definio de trs mtodos: loadUserByUsername($username), refreshUser(UserInterface
$user) e supportsClass($class). Para mais detalhes, consulte UserProviderInterface.
Aqui est um exemplo de como isso pode parecer:
// src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php
namespace Acme\WebserviceUserBundle\Security\User;
use
use
use
use
Symfony\Component\Security\Core\User\UserProviderInterface;
Symfony\Component\Security\Core\User\UserInterface;
Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
Symfony\Component\Security\Core\Exception\UnsupportedUserException;
390
Captulo 3. Cookbook
# src/Acme/WebserviceUserBundle/Resources/config/services.yml
parameters:
webservice_user_provider.class: Acme\WebserviceUserBundle\Security\User\WebserviceUserProvid
services:
webservice_user_provider:
class: "%webservice_user_provider.class%"
XML
PHP
// src/Acme/WebserviceUserBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
$container->setParameter(webservice_user_provider.class, Acme\WebserviceUserBundle\Security\U
Dica: A verdadeira implementao do provider de usurio provavelmente ter algumas dependncias, opes de
configurao ou outros servios. Adicione eles como argumentos na definio de servio.
Nota: Certifique-se que o arquivo de servios est sendo importado. Veja Importando configurao com imports para
mais detalhes.
Modifique o security.yml
Tudo ser combinado em sua configurao de segurana. Adicione o provider de usurio na lista de providers na seo
security. Escolha um nome para o provider de usurio (por exemplo, webservice) e mencione o id do servio que
voc acabou de definir.
YAML
// app/config/security.yml
security:
providers:
webservice:
id: webservice_user_provider
XML
3.1. Cookbook
391
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
providers => array(
webservice => array(
id => webservice_user_provider,
),
),
));
O Symfony tambm precisa saber como codificar as senhas que so fornecidas no site pelos usurios, por exemplo,
atravs do preenchimento de um formulrio de login. Voc pode fazer isso adicionando uma linha na seo encoders
da sua configurao de segurana:
YAML
# app/config/security.yml
security:
encoders:
Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512
XML
<!-- app/config/security.xml -->
<config>
<encoder class="Acme\WebserviceUserBundle\Security\User\WebserviceUser">sha512</encoder>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
encoders => array(
Acme\WebserviceUserBundle\Security\User\WebserviceUser => sha512,
),
));
O valor aqui deve corresponder porm com as senhas que foram originalmente codificadas ao criar os seus usurios
(no entanto os usurios foram criados). Quando um usurio submete a sua senha, o salt acrescentado ao valor da
senha e ento so codificados usando este algoritmo antes de ser comparada com o hash da senha retornado pelo seu
mtodo getPassword(). Alm disso, dependendo das suas opes, a senha pode ser codificada vrias vezes e
codificada para base64.
392
Captulo 3. Cookbook
XML
<!-- app/config/security.xml -->
<config>
<encoder class="Acme\WebserviceUserBundle\Security\User\WebserviceUser"
algorithm="sha512"
encode-as-base64="false"
iterations="1"
/>
</config>
PHP
// app/config/security.php
$container->loadFromExtension(security, array(
encoders => array(
Acme\WebserviceUserBundle\Security\User\WebserviceUser => array(
algorithm
=> sha512,
encode_as_base64 => false,
iterations
=> 1,
),
),
));
3.1.12 Cache
Como usar Varnish para aumentar a velocidade do meu Website
Pelo cache do Symfony2 usar os cache headers padres do HTTP, o Proxy Reverso do Symfony2 pode facilmente se
substituido por qualquer por qualquer outro proxy reverso. Varnish um poderoso, open-source, HTTP accelerator
capaz de servir contedo em cache de forma rpida em incluindo suporte Edge Side Includes (ESI).
3.1. Cookbook
393
Configurao
Como visto anteriormente, Symfony2 esperto o bastente para detectar se fala com um proxy reverso que compreende
ESI ou no. Ele trabalha fora da caixa quando voc usa o proxy reverso do Symfony2, mas voc precisa de um
configurao especial para poder trabalhar com Varnish. Felizmente, Symfony2 depende ainda de outro padro escrito
por Akama (Edge Architecture), ento as dicas de configurao desde captulo podem ser teis mesmo se voc no
usar Symfony2.
Nota: Varnish suporta apenas o atributo src para tags ESI (atributos onerror e alt so ignorados).
Primeiro, configure o Varnish para que ele informe o suporte ESI adicionando um Surrogate-Capability ao
cabealho nas requisies enviadas ao servidor de aplicao:
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
}
Aps isso, otimize o Varnish para que ele apenas analise o contedo da resposta quando existir ao menos uma tag ESI,
verificando o cabealho Surrogate-Control que adicionado automaticamente pelo Symfony2.
sub vcl_fetch {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
// para Varnish >= 3.0
set beresp.do_esi = true;
// para Varnish < 3.0
// esi;
}
}
Cuidado: Compresso com ESI no suportada pelo Varnish at a verso 3.0 (leia GZIP and Varnish). Se voc
no est utilizando Varnish 3.0, coloque um servidor web a frente do Varnish para executar a compresso.
Invalidao do Cache
Voc nunca dever precisar invalidar os dados em cache porque a invalidao colocada nativamente nos modelos de
cache HTTP (veja Invalidao do Cache).
Ainda assim, o Varnish pode ser configurado para aceitar um mtodo HTTP PURGE especial que invalidar o cache
para derterminado recurso:
sub vcl_hit {
if (req.request == "PURGE") {
set obj.ttl = 0s;
error 200 "Purgado";
}
}
sub vcl_miss {
if (req.request == "PURGE") {
error 404 "No Purgado";
}
}
394
Captulo 3. Cookbook
Cuidado: Voc deve proteger o mtodo HTTPPURGE para evitar que qualquer pessoa possa purgar os dados
em cache.
3.1.13 Templating
Utilizando variveis em todas templates (Variveis globais)
Algumas vezes voc quer que uma varivel esteja disponvel em todas as templates que utiliza. Isto possvel configurando o twig dentro do arquivo app/config/config.yml :
# app/config/config.yml
twig:
# ...
globals:
ga_tracking: UA-xxxxx-x
Agora a varivel ga_tracking est disponvel em todas templates Twig e pode ser acessada da seguinte forma.
<p>Our google tracking code is: {{ ga_tracking }} </p>
fcil! Voc tambm pode utilizar do sistema de parmetros (Parmetros do Servio), que permite voc isolar e
reutilizar o valor como a seguir.
; app/config/parameters.yml
[parameters]
ga_tracking: UA-xxxxx-x
# app/config/config.yml
twig:
globals:
ga_tracking: %ga_tracking%
Se a varivel global que deseja definir mais complexa, como um objeto por exemplo, ento voc no poder utilizar
o mtodo acima. Ao invs disso, precisa criar uma extenso Twig (Twig Extension) e retornar a varivel global como
uma das entradas no mtodo getGlobals.
3.1.14 Log
Como usar o Monolog para escrever Logs
O Monolog uma biblioteca de log para o PHP 5.3 usada pelo Symfony2. inspirado pela biblioteca LogBook do
Python.
Utilizao
Para fazer o log de uma mensagem, basta obter o servio logger a partir do container em seu controlador:
3.1. Cookbook
395
O servio logger possui mtodos diferentes para os nveis de log. Para mais detalhes sobre os mtodos que esto
disponveis, veja LoggerInterface.
Manipuladores (handlers) e Canais (channels): Escrevendo logs em locais diferentes
No Monolog, cada logger define um canal de log, que organiza as suas mensagens de log em diferentes categorias.
Ento, cada canal tem uma pilha de manipuladores para escrever os logs (os manipuladores podem ser compartilhados).
Dica: Ao injetar o logger em um servio voc pode usar um canal personalizado para controlar que canal o logger
ir realizar o log.
O manipulador bsico o StreamHandler, que grava logs em um stream (por padro em app/logs/prod.log
no ambiente prod e app/logs/dev.log no ambiente dev).
O Monolog tambm vem com um poderoso manipulador integrado para realizar log no ambiente prod:
FingersCrossedHandler. Ele permite que voc armazene as mensagens em um buffer e registre o log somente se a mensagem alcana o nvel de ao (ERROR na configurao fornecida na edio standard), enviando as
mensagens para outro manipulador.
Usando vrios manipuladores O logger usa uma pilha de manipuladores que so chamados sucessivamente. Isto
permite registrar facilmente as mensagens de vrias maneiras.
YAML
# app/config/config.yml
monolog:
handlers:
applog:
type: stream
path: /var/log/symfony.log
level: error
main:
type: fingers_crossed
action_level: warning
handler: file
file:
type: stream
level: debug
syslog:
type: syslog
level: error
XML
<!-- app/config/config.xml -->
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
396
Captulo 3. Cookbook
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/mono
<monolog:config>
<monolog:handler
name="applog"
type="stream"
path="/var/log/symfony.log"
level="error"
/>
<monolog:handler
name="main"
type="fingers_crossed"
action-level="warning"
handler="file"
/>
<monolog:handler
name="file"
type="stream"
level="debug"
/>
<monolog:handler
name="syslog"
type="syslog"
level="error"
/>
</monolog:config>
</container>
PHP
// app/config/config.php
$container->loadFromExtension(monolog, array(
handlers => array(
applog => array(
type => stream,
path => /var/log/symfony.log,
level => error,
),
main => array(
type
=> fingers_crossed,
action_level => warning,
handler
=> file,
),
file => array(
type => stream,
level => debug,
),
syslog => array(
type => syslog,
level => error,
),
),
));
A configurao acima define uma pilha de manipuladores que ser chamada na ordem em que eles so definidos.
Dica: O manipulador chamado file no ser includo na prpria pilha, pois, ele usado como um manipulador
3.1. Cookbook
397
Alterando o formatador O manipulador usa um Formatter para formatar o registro antes de efetuar o log.
Todos os manipuladores do Monolog usam uma instncia do
Monolog\Formatter\LineFormatter por padro, mas voc pode substitu-lo facilmente. O formatador deve implementar Monolog\Formatter\FormatterInterface.
YAML
# app/config/config.yml
services:
my_formatter:
class: Monolog\Formatter\JsonFormatter
monolog:
handlers:
file:
type: stream
level: debug
formatter: my_formatter
XML
PHP
// app/config/config.php
$container
->register(my_formatter, Monolog\Formatter\JsonFormatter);
$container->loadFromExtension(monolog, array(
handlers => array(
file => array(
type
=> stream,
398
Captulo 3. Cookbook
level
=> debug,
formatter => my_formatter,
),
),
));
O Monolog permite processar o registro antes de fazer o log para adicionar alguns dados extras. Um processador pode
ser aplicado para toda a pilha do manipulador ou somente para um manipulador especfico.
Um processador simplesmente um callable recebendo o registro como seu primeiro argumento.
Os processadores so configurados usando a tag DIC monolog.processor. Veja a referencia sobre ela.
Adicionando uma Sesso/Token de Pedido s vezes, difcil dizer que entradas no log pertencem a qual sesso
e/ou pedido. O exemplo a seguir ir adicionar um token exclusivo para cada pedido utilizando um processador.
namespace Acme\MyBundle;
use Symfony\Component\HttpFoundation\Session\Session;
class SessionRequestProcessor
{
private $session;
private $token;
public function __construct(Session $session)
{
$this->session = $session;
}
public function processRecord(array $record)
{
if (null === $this->token) {
try {
$this->token = substr($this->session->getId(), 0, 8);
} catch (\RuntimeException $e) {
$this->token = ????????;
}
$this->token .= - . substr(uniqid(), -8);
}
$record[extra][token] = $this->token;
return $record;
}
}
YAML
# app/config/config.yml
services:
monolog.formatter.session_request:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n"
3.1. Cookbook
399
monolog.processor.session_request:
class: Acme\MyBundle\SessionRequestProcessor
arguments: ["@session"]
tags:
- { name: monolog.processor, method: processRecord }
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.formatter.session_request
XML
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/mono
<services>
<service id="monolog.formatter.session_request" class="Monolog\Formatter\LineFormatter">
<argument>[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%&#
</service>
PHP
// app/config/config.php
$container
->register(monolog.formatter.session_request, Monolog\Formatter\LineFormatter)
->addArgument([%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n);
$container
->register(monolog.processor.session_request, Acme\MyBundle\SessionRequestProcessor)
->addArgument(new Reference(session))
->addTag(monolog.processor, array(method => processRecord));
$container->loadFromExtension(monolog, array(
handlers => array(
main => array(
400
Captulo 3. Cookbook
type
path
level
formatter
=>
=>
=>
=>
stream,
%kernel.logs_dir%/%kernel.environment%.log,
debug,
monolog.formatter.session_request,
),
),
));
Nota: Se voc usa vrios manipuladores, voc tambm pode registrar o processador no nvel do manipulador, em vez
de globalmente.
XML
3.1. Cookbook
401
<monolog:handler
name="swift"
from-email="error@example.com"
to-email="error@example.com"
subject="An Error Occurred!"
level="debug"
/>
</monolog:config>
</container>
PHP
// app/config/config_prod.php
$container->loadFromExtension(monolog, array(
handlers => array(
mail => array(
type
=> fingers_crossed,
action_level => critical,
handler
=> buffered,
),
buffered => array(
type
=> buffer,
handler => swift,
),
swift => array(
type
=> swift_mailer,
from_email => error@example.com,
to_email
=> error@example.com,
subject
=> An Error Occurred!,
level
=> debug,
),
),
));
O manipulador mail um manipulador fingers_crossed, significando que ele acionado apenas quando o
nvel de ao, neste caso, critical alcanado. Em seguida, ele faz log de tudo, incluindo mensagens abaixo do
nvel de ao. O nvel critical s acionado para erros HTTP de cdigo 5xx. A definio handler significa que
a sada , ento, transferida para o manipulador buffered.
Dica: Se voc deseja que tanto erros de nvel 400 quanto de nvel 500 acionem um e-mail, defina o action_level
para error ao invs de critical.
O manipulador buffered simplesmente mantm todas as mensagens para um pedido e em seguida, passa elas para
o manipulador aninhado de uma s vez. Se voc no usar este manipulador, ento cada mensagem ser enviada
separadamente. Em seguida, passado para o manipulador swift. Este o manipulador que efetivamente trata de
enviar o erro para o e-mail. As configuraes para isso so simples, os endereos de (to), para (from) e o assunto
(subject).
Voc pode combinar esses manipuladores com outros manipuladores de modo que os erros ainda sero registrados no
servidor, bem como enviados os e-mails:
YAML
# app/config/config_prod.yml
monolog:
handlers:
main:
type:
fingers_crossed
402
Captulo 3. Cookbook
action_level: critical
handler:
grouped
grouped:
type:
group
members: [streamed, buffered]
streamed:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
buffered:
type:
buffer
handler: swift
swift:
type:
swift_mailer
from_email: error@example.com
to_email:
error@example.com
subject:
An Error Occurred!
level:
debug
XML
3.1. Cookbook
403
/>
</monolog:config>
</container>
PHP
// app/config/config_prod.php
$container->loadFromExtension(monolog, array(
handlers => array(
main => array(
type
=> fingers_crossed,
action_level => critical,
handler
=> grouped,
),
grouped => array(
type
=> group,
members => array(streamed, buffered),
),
streamed => array(
type => stream,
path => %kernel.logs_dir%/%kernel.environment%.log,
level => debug,
),
buffered
=> array(
type
=> buffer,
handler => swift,
),
swift => array(
type
=> swift_mailer,
from_email => error@example.com,
to_email
=> error@example.com,
subject
=> An Error Occurred!,
level
=> debug,
),
),
));
Isto usa o manipulador group para enviar as mensagens para os dois membros do grupo, os manipuladores
buffered e stream. As mensagens sero agora gravadas no arquivo de log e enviadas por e-mail.
Como fazer log de Mensagens em Arquivos Diferentes
Novo na verso 2.1: A capacidade de especificar os canais para um manipulador especfico foi adicionada ao MonologBundle para o Symfony 2.1.
A Edio Standard do Symfony contm vrios canais para log: doctrine, event, security e request. Cada
canal corresponde a um servio logger (monolog.logger.XXX) no container e, injetado no servio em questo.
A finalidade dos canais de serem capazes de organizar diferentes tipos de mensagens de log.
Por padro, o Symfony2 realiza o log de todas as mensagens em um nico arquivo (independentemente do canal).
Mudando um canal para um Handler diferente
Agora, suponha que voc queira registrar o canal doctrine para um arquivo diferente.
Para isso, basta criar um novo manipulador (handler) e configur-lo como segue:
404
Captulo 3. Cookbook
YAML
monolog:
handlers:
main:
type: stream
path: /var/log/symfony.log
channels: !doctrine
doctrine:
type: stream
path: /var/log/doctrine.log
channels: doctrine
XML
<monolog:config>
<monolog:handlers>
<monolog:handler name="main" type="stream" path="/var/log/symfony.log">
<monolog:channels>
<type>exclusive</type>
<channel>doctrine</channel>
</monolog:channels>
</monolog:handler>
<monolog:handler name="doctrine" type="stream" path="/var/log/doctrine.log" />
<monolog:channels>
<type>inclusive</type>
<channel>doctrine</channel>
</monolog:channels>
</monolog:handler>
</monolog:handlers>
</monolog:config>
Especificao Yaml
Voc pode mudar o canal que monolog faz o log para um servio de cada vez. Isto feito adicionando a tag
monolog.logger ao seu servio e especificando qual canal o servio deve fazer o log. Ao fazer isso, o logger
3.1. Cookbook
405
que injetado naquele servio pr-configurado para usar o canal que voc especificou.
Para mais informaes - incluindo um exemplo completo - leia dic_tags-monolog na seo de referncia das Tags
de Injeo de Dependncia.
Aprenda mais no Cookbook
3.1.15 Console
Como criar um Comando de Console
A pgina Console na seo Componentes (O Componente Console) aborda como criar um comando de Console. Este
artigo do cookbook abrange as diferenas ao criar comandos do Console com o framework Symfony2.
Registrando os Comandos Automaticamente
Para disponibilizar os comandos de console automaticamente com o Symfony2, adicione um diretrio Command
dentro de seu bundle e crie um arquivo php com o sufixo Command.php para cada comando que voc deseja fornecer. Por exemplo, se voc deseja estender o AcmeDemoBundle (disponvel na Edio Standard do Symfony) para
cumprimentar-nos a partir da linha de comando, crie o GreetCommand.php e adicione o seguinte cdigo:
// src/Acme/DemoBundle/Command/GreetCommand.php
namespace Acme\DemoBundle\Command;
use
use
use
use
use
Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Symfony\Component\Console\Input\InputArgument;
Symfony\Component\Console\Input\InputInterface;
Symfony\Component\Console\Input\InputOption;
Symfony\Component\Console\Output\OutputInterface;
406
Captulo 3. Cookbook
$text = strtoupper($text);
}
$output->writeln($text);
}
}
Testando Comandos
Ao testar os comandos utilizados como parte do framework completo Application deve ser usado em vez de
Application:
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Acme\DemoBundle\Command\GreetCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
public function testExecute()
{
// mock the Kernel or create one depending on your needs
$application = new Application($kernel);
$application->add(new GreetCommand());
$command = $application->find(demo:greet);
$commandTester = new CommandTester($command);
$commandTester->execute(array(command => $command->getName()));
$this->assertRegExp(/.../, $commandTester->getDisplay());
// ...
}
}
Usando ContainerAwareCommand como classe base para o comando (em vez do mais bsico Command), voc
tem acesso ao container de servio. Em outras palavras, voc tem acesso a qualquer servio configurado. Por exemplo,
voc pode facilmente estender a tarefa para a mesma ser traduzvel:
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument(name);
$translator = $this->getContainer()->get(translator);
if ($name) {
$output->writeln($translator->trans(Hello %name%!, array(%name% => $name)));
} else {
$output->writeln($translator->trans(Hello!));
}
}
3.1. Cookbook
407
ou o equivalente:
$ php app/console cache:clear -e=prod
Alm de alterar o ambiente, voc pode optar tambm por desativar o modo de depurao. Isto pode ser til quando
deseja-se executar comandos no ambiente dev mas evitar o impacto no desempenho devido a coleta de dados de
depurao:
$ php app/console list --no-debug
H um shell interativo que permite voc digitar os comandos sem ter que especificar php app/console toda vez,
o que til se voc precisa executar vrios comandos. Para entrar no shell execute:
$ php app/console --shell
$ php app/console -s
Ao usar o shell voc pode escolher em executar cada comando em um processo separado:
$ php app/console --shell --process-isolation
$ php app/console -s --process-isolation
Quando fizer isso, a sada no ser colorida e a interatividade no suportada, ento, voc vai precisar passar todos os
parmetros de comando explicitamente.
Nota: A menos que voc estiver usando processos isolados, limpar o cache no shell no ter efeito sobre os comandos
subsequentes que voc executar. Isto porque os arquivos originais em cache ainda esto sendo usados.
408
Captulo 3. Cookbook
Para configurar o Contexto do Pedido - que usado pelo Gerador de URL - voc pode redefinir os parmetros que
ele usa como valores padro para alterar o host padro (localhost) e o esquema (http). Note que isso no impacta nas
URLs geradas atravs de solicitaes normais da web, uma vez que substituir os padres.
YAML
# app/config/parameters.yml
parameters:
router.request_context.host: example.org
router.request_context.scheme: https
XML
<!-- app/config/parameters.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">
<parameters>
<parameter key="router.request_context.host">example.org</parameter>
<parameter key="router.request_context.scheme">https</parameter>
</parameters>
</container>
PHP
// app/config/config_test.php
$container->setParameter(router.request_context.host, example.org);
$container->setParameter(router.request_context.scheme, https);
Para alter-lo em apenas um comando, voc pode simplesmente chamar o servio do Contexto de Pedido e sobrescrever
as suas configuraes:
// src/Acme/DemoBundle/Command/DemoCommand.php
// ...
class DemoCommand extends ContainerAwareCommand
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$context = $this->getContainer()->get(router)->getContext();
$context->setHost(example.com);
$context->setScheme(https);
// ... your code here
}
}
409
Fornecer ao desenvolvedor um feedback preciso sempre que algo der errado (barra de ferramentas de depurao
web, pginas de exceo agradveis, profiler, ...);
Ser o mais semelhante possvel do ambiente de produo para evitar problemas ao implantar o projeto.
Desabilitando o Arquivo de Bootstrap e o Cache de Classe
E para tornar o ambiente de produo o mais rpido possvel, o Symfony cria grandes arquivos PHP em seu cache
que contm a agregao das classes PHP que o projeto precisa para cada solicitao. No entanto, este comportamento
pode confundir a sua IDE ou seu depurador. Esta receita mostra como voc pode ajustar este mecanismo de cache
para torn-lo mais amigvel quando voc precisar depurar cdigo que envolve classes do Symfony.
O front controller app_dev.php, por padro, o seguinte:
// ...
require_once __DIR__./../app/bootstrap.php.cache;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(dev, true);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();
Para deixar o seu depurador ainda mais feliz, desabilite todos os caches de classe PHP, removendo a chamada para
loadClassCache() e substituindo as declaraes require como abaixo:
// ...
// require_once __DIR__./../app/bootstrap.php.cache;
require_once __DIR__./../app/autoload.php;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(dev, true);
// $kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();
Dica: Se voc desativar os caches PHP, no se esquea de voltar depois da sua sesso de depurao.
Algumas IDEs no gostam do fato de que algumas classes so armazenadas em locais diferentes. Para evitar problemas, voc pode dizer a sua IDE para ignorar os arquivos de cache PHP , ou voc pode mudar a extenso usada pelo
Symfony para esses arquivos:
$kernel->loadClassCache(classes, .php.cache);
410
Captulo 3. Cookbook
No symfony1, isto era feito atravs dos mtodos PreExecute e postExecute. A maioria dos principais frameworks
possuem mtodos semelhantes, mas isso no existe no Symfony2. A boa nova que h uma forma muito melhor para
interferir no processo Pedido -> Resposta usando o componente EventDispatcher.
Exemplo de validao de token
Imagine que voc precisa desenvolver uma API onde alguns controladores so pblicos mas outros so restritos a um
ou alguns clientes. Para estas funcionalidades privadas, voc pode fornecer um token para os clientes identificarem-se.
Ento, antes de executar a ao do controlador, voc precisa verificar se a ao restrita ou no. Se for restrita, voc
precisa validar o token informado.
Nota: Por favor, note que, por simplicidade, nesta receita os tokens sero definidos na configurao e no ser usada
configurao de banco de dados nem autenticao atravs do componente de Segurana.
Primeiro, armazene algumas configuraes bsicas do token usando o config.yml e a chave parameters:
YAML
# app/config/config.yml
parameters:
tokens:
client1: pass1
client2: pass2
XML
<!-- app/config/config.xml -->
<parameters>
<parameter key="tokens" type="collection">
<parameter key="client1">pass1</parameter>
<parameter key="client2">pass2</parameter>
</parameter>
</parameters>
PHP
// app/config/config.php
$container->setParameter(tokens, array(
client1 => pass1,
client2 => pass2,
));
Tags de Controladores a serem verificadas Um ouvinte kernel.controller notificado em todos os pedidos, mesmo antes do controlador ser executado. Ento, primeiro, voc precisa de alguma forma para identificar se o
controlador, que coincide com o pedido, precisa de validao do token.
Uma maneira fcil e limpa criar uma interface vazia e fazer os controladores implement-la:
namespace Acme\DemoBundle\Controller;
interface TokenAuthenticatedController
{
3.1. Cookbook
411
// ...
}
Criando um Ouvinte de Evento Em seguida, voc precisa criar um ouvinte de evento, que ir conter a lgica que
voc deseja executar antes de seus controladores. Se voc no est familiarizado com ouvintes de eventos, voc pode
aprender mais sobre eles em /cookbook/service_container/event_listener:
// src/Acme/DemoBundle/EventListener/TokenListener.php
namespace Acme\DemoBundle\EventListener;
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class TokenListener
{
private $tokens;
public function __construct($tokens)
{
$this->tokens = $tokens;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure. This is not usual in Symfony2 but i
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get(token);
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException(This action needs a valid token!);
}
}
412
Captulo 3. Cookbook
}
}
Registrando o Ouvinte Finalmente, registre seu ouvinte como um servio e adicione a ele uma tag de ouvinte de
evento. Ao ouvir o kernel.controller, voc est dizendo ao Symfony que deseja que seu ouvinte seja chamado
antes que qualquer controlador seja executado.
YAML
XML
<!-- app/config/config.xml (or inside your services.xml) -->
<service id="demo.tokens.action_listener" class="Acme\DemoBundle\EventListener\TokenListener">
<argument>%tokens%</argument>
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" />
</service>
PHP
// app/config/config.php (or inside your services.php)
use Symfony\Component\DependencyInjection\Definition;
Com esta configurao, seu mtodo TokenListener onKernelController ser executado em cada pedido.
Se o controlador que est prestes a ser executado implementa TokenAuthenticatedController, o token de
autenticao aplicado. Isso permite que voc tenha um filtro antes em qualquer controlador que desejar.
Filtros aplicados Aps com o evento kernel.response
Alm de ter um hook que executado antes de seu controlador, voc tambm pode adicionar um hook que ser
executado aps seu controlador. Para este exemplo, imagine que voc deseja adicionar um hash sha1 (com um salt
usando aquele token) para todas as respostas que passaram este token de autenticao.
Outro evento do ncleo do Symfony - chamado kernel.response - notificado em cada pedido, mas depois que
o controlador retorna um objeto de Resposta. Criar um ouvinte aps to fcil quanto criar uma classe ouvinte e
registr-la como um servio neste evento.
Por exemplo, considere o TokenListener do exemplo anterior e primeiro grave o token de autenticao dentro dos
atributos do pedido. Isto servir como uma flag bsica de que este pedido foi submetido autenticao por token:
public function onKernelController(FilterControllerEvent $event)
{
// ...
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get(token);
3.1. Cookbook
413
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException(This action needs a valid token!);
}
// mark the request as having passed token authentication
$event->getRequest()->attributes->set(auth_token, $token);
}
}
Agora, adicione um outro mtodo nesta classe - onKernelResponse - que procura por esta flag no objeto do pedido
e define um cabealho personalizado na resposta se ele for encontrado:
// add the new use statement at the top of your file
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
public function onKernelResponse(FilterResponseEvent $event)
{
// check to see if onKernelController marked this as a token "authed" request
if (!$token = $event->getRequest()->attributes->get(auth_token)) {
return;
}
$response = $event->getResponse();
// create a hash and set it as a response header
$hash = sha1($response->getContent().$token);
$response->headers->set(X-CONTENT-HASH, $hash);
}
Finalmente, uma segunda tag necessria na definio do servio para notificar o Symfony que o evento
onKernelResponse deve ser notificado para o evento kernel.response:
YAML
XML
<!-- app/config/config.xml (or inside your services.xml) -->
<service id="demo.tokens.action_listener" class="Acme\DemoBundle\EventListener\TokenListener">
<argument>%tokens%</argument>
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" />
<tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
</service>
PHP
// app/config/config.php (or inside your services.php)
use Symfony\Component\DependencyInjection\Definition;
414
Captulo 3. Cookbook
isso! O TokenListener agora notificado antes de cada controlador ser executado (onKernelController)
e depois de cada controlador retornar uma resposta (onKernelResponse). Fazendo com que controladores especficos implementem a interface TokenAuthenticatedController , o ouvinte saber em quais controladores
ele deve agir. E armazenando um valor nos atributos do pedido, o mtodo onKernelResponse sabe adicionar o
cabealho extra. Divirta-se!
Como estender uma Classe sem usar Herana
Para permitir que vrias classes adicionem mtodos para uma outra, voc pode definir o mtodo mgico __call()
na classe que voc deseja que seja estendida da seguinte forma:
class Foo
{
// ...
public function __call($method, $arguments)
{
// cria um evento chamado foo.method_is_not_found
$event = new HandleUndefinedMethodEvent($this, $method, $arguments);
$this->dispatcher->dispatch($this, foo.method_is_not_found, $event);
Ela utiliza um HandleUndefinedMethodEvent especial que tambm deve ser criado. Esta uma classe genrica
que poderia ser reutilizada cada vez que voc precisa utilizar esse padro de extenso de classe:
use Symfony\Component\EventDispatcher\Event;
class HandleUndefinedMethodEvent extends Event
{
protected $subject;
protected $method;
protected $arguments;
protected $returnValue;
protected $isProcessed = false;
public function __construct($subject, $method, $arguments)
{
$this->subject = $subject;
$this->method = $method;
$this->arguments = $arguments;
}
public function getSubject()
{
return $this->subject;
3.1. Cookbook
415
}
public function getMethod()
{
return $this->method;
}
public function getArguments()
{
return $this->arguments;
}
/**
* Define o valor de retorno e pra a notificao para outros listeners
*/
public function setReturnValue($val)
{
$this->returnValue = $val;
$this->isProcessed = true;
$this->stopPropagation();
}
public function getReturnValue($val)
{
return $this->returnValue;
}
public function isProcessed()
{
return $this->isProcessed;
}
}
Em seguida, crie uma classe que vai ouvir o evento foo.method_is_not_found e adicionar o mtodo bar():
class Bar
{
public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event)
{
// queremos somente responder as chamadas do mtodo bar
if (bar != $event->getMethod()) {
// permite que outro listener cuide deste mtodo desconhecido
return;
}
// o objeto (a instncia foo)
$foo = $event->getSubject();
// os argumentos do mtodo bar
$arguments = $event->getArguments();
// faz algo
// ...
// define o valor de retorno
$event->setReturnValue($someValue);
}
}
416
Captulo 3. Cookbook
Por fim, adicione o novo mtodo bar na classe Foo para registrar uma instncia de Bar com o evento
foo.method_is_not_found:
$bar = new Bar();
$dispatcher->addListener(foo.method_is_not_found, $bar);
Se voc deseja realizar algo logo antes ou aps um mtodo ser chamado, voc pode despachar um evento, respectivamente, no incio ou no fim do mtodo:
class Foo
{
// ...
public function send($foo, $bar)
{
// do something before the method
$event = new FilterBeforeSendEvent($foo, $bar);
$this->dispatcher->dispatch(foo.pre_send, $event);
// get $foo and $bar from the event, they may have been modified
$foo = $event->getFoo();
$bar = $event->getBar();
// the real method implementation is here
$ret = ...;
// do something after the method
$event = new FilterSendReturnValue($ret);
$this->dispatcher->dispatch(foo.post_send, $event);
return $event->getReturnValue();
}
}
Neste exemplo, dois eventos so lanados: foo.pre_send, antes do mtodo ser executado, e foo.post_send
aps o mtodo ser executado. Cada um usa uma classe Event personalizada para comunicar informaes para os
ouvintes dos dois eventos. Essas classes de evento teriam que ser criadas por voc e, devem permitir, neste exemplo,
que as variveis $foo, $bar e $ret sejam recuperadas e definidas pelos ouvintes.
Por exemplo, supondo que o FilterSendReturnValue tem um mtodo setReturnValue, um ouvinte pode
ter o seguinte aspecto:
public function onFooPostSend(FilterSendReturnValue $event)
{
$ret = $event->getReturnValue();
// modify the original $ret value
$event->setReturnValue($ret);
}
3.1. Cookbook
417
3.1.18 Request
Como registrar um novo Formato de Requisio e de Mime Type
Todo Request possui um formato (e.g. html, json), que usado para determinar o tipo de contedo a ser
retornado pelo Response. Na verdade, o formato de requisio, acessvel pelo mtodo getRequestFormat(),
usado para definir o MIME type do cabealho Content-Type no objeto Response. Internamente, Symfony
contm um mapa dos formatos mais comuns (e.g. html, json) e seus MIME types associados (e.g. text/html,
application/json). Naturalmente, formatos adicionais de MIME type de entrada podem ser facilmente adicionados. Este documento ir mostrar como voc pode adicionar o formato jsonp e seu MIME type correspondente.
Criando um kernel.request Listener
A chave para definir um novo MIME type criar uma classe que ir ouvir o evento kernel.request enviado
pelo kernel do Symfony. O evento kernel.request enviado no incio no processo de manipulao da requisio
Symfony e permite que voc modifique o objeto da requisio.
Crie a seguinte classe, substituindo o caminho com um caminho para um pacote em seu projeto:
// src/Acme/DemoBundle/RequestListener.php
namespace Acme\DemoBundle;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$event->getRequest()->setFormat(jsonp, application/javascript);
}
}
Como para qualquer outro listener, voc precisa adicion-lo em um arquivo de configurao e registr-lo como um
listerner adicionando a tag kernel.event_listener:
XML
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<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/ser
<service id="acme.demobundle.listener.request" class="Acme\DemoBundle\RequestListener">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
</service>
</container>
YAML
418
Captulo 3. Cookbook
# app/config/config.yml
services:
acme.demobundle.listener.request:
class: Acme\DemoBundle\RequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
PHP
# app/config/config.php
$definition = new Definition(Acme\DemoBundle\RequestListener);
$definition->addTag(kernel.event_listener, array(event => kernel.request, method => onK
$container->setDefinition(acme.demobundle.listener.request, $definition);
3.1.19 Profiler
Como criar um Coletor de Dados personalizado
O Profiler do Symfony2 delega a coleta de dados para os coletores de dados. O Symfony2 vem com alguns deles, mas
voc pode criar o seu prprio facilmente.
Criando um Coletor de Dados Personalizado
O mtodo getName() deve retornar um nome nico. Ele usado para acessar a informao mais tarde (veja o
/cookbook/testing/profiling por exemplo).
O mtodo collect() responsvel por armazenar os dados que ele quer fornecer acesso nas propriedades locais.
3.1. Cookbook
419
Cuidado: Como o profiler serializa instncias do coletor de dados, voc no deve armazenar objetos que no
podem ser serializados (como objetos PDO), ou voc precisa fornecer o seu prprio mtodo serialize().
Na maioria das vezes, conveniente estender o DataCollector e popular a propriedade $this->data (que
cuida da serializao da propriedade $this->data):
class MemoryDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
memory => memory_get_peak_usage(true),
);
}
public function getMemory()
{
return $this->data[memory];
}
public function getName()
{
return memory;
}
}
Para ativar um coletor de dados, adicione-o como um servio regular, em uma de suas configuraes, e adicione a tag
data_collector a ele:
YAML
services:
data_collector.your_collector_name:
class: Fully\Qualified\Collector\Class\Name
tags:
- { name: data_collector }
XML
<service id="data_collector.your_collector_name" class="Fully\Qualified\Collector\Class\Name">
<tag name="data_collector" />
</service>
PHP
$container
->register(data_collector.your_collector_name, Fully\Qualified\Collector\Class\Name)
->addTag(data_collector)
;
Quando voc quiser exibir os dados coletados pelo seu Coletor de Dados na barra de ferramentas para debug web ou
no profiler web, adicione um template Twig seguindo este esqueleto:
420
Captulo 3. Cookbook
{% extends WebProfilerBundle:Profiler:layout.html.twig %}
{% block toolbar %}
{# the web debug toolbar content #}
{% endblock %}
{% block head %}
{# if the web profiler panel needs some specific JS or CSS files #}
{% endblock %}
{% block menu %}
{# the menu content #}
{% endblock %}
{% block panel %}
{# the panel content #}
{% endblock %}
Cada bloco opcional. O bloco toolbar usado para a barra de ferramentas para debug web e o menu e panel
so usados para adicionar um painel no profiler web.
Todos os blocos tm acesso ao objeto collector.
Dica:
Templates integrados usam uma imagem codificada em base64 para a barra de ferramentas (<img
src="data:image/png;base64,..."). Voc pode facilmente calcular o valor base64 para uma imagem com
este pequeno script: echo base64_encode(file_get_contents($_SERVER[argv][1]));.
Para ativar o template, adicione um atributo template para a tag data_collector em sua configurao. Por
exemplo, assumindo que o seu template est em algum AcmeDebugBundle:
YAML
services:
data_collector.your_collector_name:
class: Acme\DebugBundle\Collector\Class\Name
tags:
- { name: data_collector, template: "AcmeDebugBundle:Collector:templatename", id: "y
XML
PHP
$container
->register(data_collector.your_collector_name, Acme\DebugBundle\Collector\Class\Name)
->addTag(data_collector, array(
template => AcmeDebugBundle:Collector:templatename,
id
=> your_collector_name,
))
;
421
fony1 continuam a ser muito relevantes ao desenvolver com o Symfony2. Claro, o app.yml no existe mais, mas
quanto ao roteamento, controladores e templates, todos permanecem.
Neste captulo, vamos percorrer as diferenas entre o symfony1 e Symfony2. Como voc ver, muitas tarefas so
abordadas de uma forma ligeiramente diferente. Voc vai apreciar estas pequenas diferenas pois elas promovem um
cdigo estvel, previsvel, testvel e desacoplado em suas aplicaes Symfony2.
Ento, sente e relaxe, pois vamos gui-lo a partir de agora.
Estrutura de Diretrios
Ao olhar um projeto Symfony2 - por exemplo, o Symfony2 Standard - voc ver uma estrutura de diretrios muito
diferente da encontrada no symfony1. As diferenas, no entanto, so um tanto superficiais.
O Diretrio app/
No symfony1, o seu projeto tem uma ou mais aplicaes, e cada uma fica localizada dentro do diretrio apps/
(ex. apps/frontend). Por padro, no Symfony2, voc tem apenas uma aplicao representada pelo diretrio
app/. Como no symfony1, o diretrio app/ contm a configurao especfica para determinada aplicao. Ele
tambm contm os diretrios cache, log e template especficos para a aplicao, bem como, uma classe Kernel
(AppKernel), que o objeto base que representa a aplicao.
Ao contrrio do symfony1, quase nenhum cdigo PHP reside no diretrio app/. Este diretrio no pretende armazenar os mdulos ou arquivos de biblioteca como acontece no symfony1. Em vez disso, ele simplesmente o lar da
configurao e outros recursos (templates, arquivos de traduo).
O Diretrio src/
Simplificando, o seu cdigo fica armazendo aqui. No Symfony2, todo o cdigo real da aplicao reside dentro de
um bundle (equivalente a um plugin no symfony1) e, por padro, cada bundle reside dentro do diretrio src. Dessa
forma, o diretrio src um pouco parecido com o diretrio plugins do symfony1, mas muito mais flexvel. Alm
disso, enquanto os seus bundles vo residir no diretrio src/, os bundles de terceiros iro residir am algum lugar no
diretrio vendor/.
Para obter uma imagem melhor do diretrio src/, vamos primeiro pensar em uma aplicao symfony1. Primeiro,
parte do seu cdigo provavelmente reside dentro de uma ou mais aplicaes. Mais geralmente estas incluem mdulos,
mas tambm podem incluir quaisquer outras classes PHP que voc adicionar na sua aplicao. Voc tambm pode
ter criado um arquivo schema.yml no diretrio config do seu projeto e construiu vrios arquivos de modelo.
Finalmente, para ajudar com alguma funcionalidade comum, voc est usando vrios plugins de terceiros que residem
no diretrio plugins/. Em outras palavras, o cdigo que compem a sua aplicao reside em vrios locais diferentes.
No Symfony2, a vida muito mais simples porque todo o cdigo Symfony2 deve residir em um bundle. Em nosso
projeto symfony1, todo o cdigo pode ser movido em um ou mais plugins (o que, na verdade, uma prtica muito
boa). Assumindo que todos os mdulos, classes PHP, esquema, configurao de roteamento, etc, foram movidos para
um plugin, o diretrio plugins/ do symfony1 seria muito semelhante ao diretrio src/ do Symfony2.
Simplificando novamente, o diretrio src/ onde o seu cdigo, assets, templates e mais qualquer outra coisa especfica ao seu projeto, vai residir.
O Diretrio vendor/
O diretrio vendor/ basicamente equivalente ao diretrio lib/vendor/ do symfony1, que foi o diretrio convencional para todas as bibliotecas vendor e bundles. Por padro, voc vai encontrar os arquivos de biblioteca do
422
Captulo 3. Cookbook
Symfony2 nesse diretrio, juntamente com vrias outras bibliotecas dependentes, como Doctrine2, Twig e SwiftMailer. Bundles Symfony2 de terceiros residem em algum local no diretrio vendor/.
O Diretrio web/
No mudou muita coisa no diretrio web/. A diferena mais notvel a ausncia dos diretrios css/, js/ e
images/. Isto intencional. Tal como o seu cdigo PHP, todos os assets tambm devem residir dentro de um bundle.
Com a ajuda de um comando do console, o diretrio Resources/public/ de cada pacote copiado ou ligado
simbolicamente ao diretrio web/bundles/. Isto permite-lhe manter os assets organizados dentro do seu bundle,
mas, ainda torn-los disponveis ao pblico. Para certificar-se de que todos os bundles esto disponveis, execute o
seguinte comando:
php app/console assets:install web
O Autoloading
Uma das vantagens dos frameworks modernos nunca ter que preocupar-se em incluir arquivos. Ao fazer uso de
um autoloader, voc pode fazer referncia qualquer classe em seu projeto e confiar que ela estar disponvel. O
autoloading mudou no Symfony2 para ser mais universal, mais rpido e independente da necessidade de limpar o seu
cache.
No symfony1, o autoloading realizado pesquisando em todo o projeto pela presena de arquivos de classe PHP
e realizando o cache desta informao em um array gigante. Este array diz ao symfony1 exatamente qual arquivo
continha cada classe. No ambiente de produo, isto faz com que voc precise limpar o cache quando as classes forem
adicionadas ou movidas.
No Symfony2, uma nova classe - UniversalClassLoader - lida com esse processo. A idia por trs do autoloader
simples: o nome da sua classe (incluindo o namespace) deve coincidir com o caminho para o arquivo que contm
essa classe. Considere, por exemplo, o FrameworkExtraBundle da Edio Standard do Symfony2:
namespace Sensio\Bundle\FrameworkExtraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
// ...
class SensioFrameworkExtraBundle extends Bundle
{
// ...
3.1. Cookbook
423
Usando o Console
No symfony1, o console est no diretrio raiz do seu projeto e chamado symfony:
php symfony
Aplicaes
Em um projeto symfony1, comum ter vrias aplicaes: uma para o frontend e uma para o backend, por exemplo.
Em um projeto Symfony2, voc s precisa criar uma nica aplicao (um aplicao blog, uma aplicao intranet,
...). Na maioria das vezes, se voc desejar criar uma segunda aplicao, voc pode, em vez, criar outro projeto e
compartilhar alguns bundles entre elas.
E, se voc precisa separar as funcionalidades do frontend e backend de alguns bundles, voc pode criar sub-namespaces
para os controladores, sub-diretrios para templates, diferentes configuraes semnticas, configuraes separadas de
roteamento, e assim por diante.
Claro, no h nada de errado em ter vrias aplicaes em seu projeto, isto depende inteiramente de voc. Uma
segunda aplicao significaria um novo diretrio, por exemplo: my_app/, com a mesma configurao bsica do
diretrio app/.
Dica: Leia a definio de Projeto, Aplicao e Bundle no glossrio.
424
Captulo 3. Cookbook
Bundles e Plugins
Em um projeto symfony1, um plugin pode conter configurao, mdulos, bibliotecas PHP, assets e qualquer outra
coisa relacionada ao seu projeto. No Symfony2, a idia de um plugin substituda pelo bundle. Um bundle
ainda mais poderoso do que um plugin porque o ncleo do framework Symfony2 fornecido atravs de uma srie
de bundles. No Symfony2, os bundles so cidados de primeira classe que so to flexveis que mesmo o cdigo do
ncleo em si um bundle.
No symfony1, um plugin deve ser ativado dentro da classe ProjectConfiguration:
// config/ProjectConfiguration.class.php
public function setup()
{
$this->enableAllPluginsExcept(array(/* some plugins here */));
}
No Symfony2, a configurao um pouco semelhante ao app.yml do symfony1, exceto que muito mais sistemtica. Com o app.yml, voc poderia simplesmente criar as chaves que desejava. Por padro, as entradas eram sem
significado e dependia inteiramente de como voc utilizava em sua aplicao:
# some app.yml file from symfony1
all:
3.1. Cookbook
425
email:
from_address:
foo.bar@example.com
No Symfony2, voc tambm pode criar entradas arbitrrias sob a chave parameters de sua configurao:
parameters:
email.from_address: foo.bar@example.com
Na realidade, a configurao no Symfony2 muito mais potente e usada principalmente para configurar objetos que
voc pode usar. Para maiores informaes, visite o captulo intitulado Container de Servio.
426
Captulo 3. Cookbook
A forma mais bsica de implantar uma aplicao copiando os arquivos manualmente via FTP/SCP (ou mtodo
similar). Isto tem as suas desvantagens, pois voc no tm controle sobre o sistema com o progresso de atualizao.
Este mtodo tambm requer que voc realize algumas etapas manuais aps transferir os arquivos (consulte Tarefas
Comuns Ps-Implantao)
Usando Controle de Cdigo Fonte
Se voc estiver usando controle de cdigo fonte (por exemplo, git ou svn), voc pode simplificar com a sua instalao
ao vivo tambm sendo uma cpia de seu repositrio. Quando estiver pronto para atualiz-lo to simples quanto
buscar as ltimas atualizaes de seu sistema de controle de cdigo fonte.
Isso torna a atualizao de seus arquivos fcil, mas voc ainda precisa se preocupar com a realizao manual de outros
passos (consulte Tarefas Comuns Ps-Implantao).
Usando Build scripts e outras ferramentas
H tambm ferramentas de alta qualidade para ajudar a aliviar o sofrimento da implantao. Existem at mesmo
algumas ferramentas que foram especificamente adaptadas s exigncias do Symfony2, e que tem um cuidado especial
para garantir que tudo, antes, durante e aps uma implantao ocorreu corretamente.
Veja As ferramentas para uma lista de ferramentas que podem ajudar com a implantao.
Tarefas Comuns Ps-Implantao
Depois de implantar o seu cdigo fonte, h uma srie de tarefas comuns que precisam ser feitas:
A) Configurar o seu arquivo app/config/parameters.ini
Este arquivo deve ser personalizado em cada sistema. O mtodo utilizado para implantar o seu cdigo fonte no
deve implantar esse arquivo. Em vez disso, voc deve configur-lo manualmente (ou atravs de algum processo de
construo) em seu(s) servidor(es).
B) Atualizar os seus vendors
Seus vendors podem ser atualizados antes de transferir o seu cdigo fonte (ou seja, atualizar o diretrio vendor/,
e, em seguida, transferir com seu cdigo fonte) ou depois no servidor. De qualquer forma, apenas atualize os seus
vendors como faria normalmente:
$ php composer.phar install --optimize-autoloader
Dica: A flag --optimize-autoloader deixa o autoloader do Composer com mais performance atravs da
construo de um mapa de classe.
3.1. Cookbook
427
Se voc estiver usando o Assetic, voc tambm vai desejar fazer o dump de seus assets:
$ php app/console assetic:dump --env=prod --no-debug
E) Outras coisas!
Podem haver muitas outras coisas que voc precisa fazer, dependendo de sua configurao:
A execuo de quaisquer migraes de banco de dados
Limpar o cache do APC
Executar assets:install (j considerado em composer.phar install)
Adicionar/editar CRON jobs
Mover os assets para um CDN
...
Ciclo de Vida da Aplicao: Integrao Contnua, QA, etc
Embora este artigo abrange os detalhes tcnicos da implantao, o ciclo de vida completo de buscar o cdigo do
desenvolvimento at a produo pode ter muito mais passos (pense na implantao para staging, QA, execuo de
testes, etc.)
O uso de staging, testes, QA, integrao contnua, as migraes de banco de dados e a capacidade de reverter em caso
de falha so todos fortemente aconselhados. Existem ferramentas simples e outras mais complexas, que podem fazer
a implantao to fcil (ou sofisticada) quanto o seu ambiente requer.
No se esquea que a implantao de sua aplicao tambm envolve a atualizao de qualquer dependncia (normalmente atravs do Composer), a migrao do seu banco de dados, limpar o cache e outras coisas potenciais como mover
os assets para um CDN (consulte Tarefas Comuns Ps-Implantao).
As Ferramentas
Capifony:
Esta ferramenta fornece um conjunto especializado de ferramentas no topo do Capistrano, especificamente
sob medida para projetos symfony e Symfony2.
sf2debpkg:
Esta ferramenta ajuda a criar um pacote Debian nativo para o seu projeto Symfony2.
Magallanes:
Esta ferramenta de implantao, semelhante ao Capistrano, construda em PHP, e pode ser mais fcil,
para os desenvolvedores PHP, estend-la para as suas necessidades.
Bundles:
H muitos bundles que adicionam recursos de implantao diretamente em seu console do Symfony2.
428
Captulo 3. Cookbook
Script bsico:
Voc pode, com certeza, usar o shell, Ant, ou qualquer outra ferramenta de construo para criar o script
de implantao de seu projeto.
Plataforma como Prestadora de Servios:
PaaS uma forma relativamente nova para implantar sua aplicao. Tipicamente uma PaaS vai usar um
nico arquivo de configurao no diretrio raiz do seu projeto para determinar como construir um ambiente em tempo real que suporta o seu software. Um provedor com suporte confirmado para o Symfony2
o PagodaBox.
Dica: Procurando mais? Fale com a comunidade no Canal IRC do Symfony #symfony (no freenode) para obter mais
informaes.
Assetic
Como usar o Assetic para o Gerenciamento de Assets
Como Minificar JavaScripts e Folhas de Estilo com o YUI Compressor
Como usar o Assetic para otimizao de imagem com funes do Twig
Como Aplicar um filtro Assetic a uma extenso de arquivo especfica
Bundles
Como usar Melhores Prticas para a Estruturao dos Bundles
Como usar herana para substituir partes de um Bundle
Como Sobrescrever qualquer parte de um Bundle
Como expor uma Configurao Semntica para um Bundle
Cache
Como usar Varnish para aumentar a velocidade do meu Website
Configurao
Como Dominar e Criar novos Ambientes
Como Substituir a Estrutura de Diretrio Padro do Symfony
Como definir Parmetros Externos no Container de Servios
Como usar o PdoSessionStorage para armazenar as Sesses no Banco de Dados
Como usar o Apache Router
Console
Como criar um Comando de Console
Como usar o Console
Como gerar URLs com um Host personalizado em Comandos de Console
Controlador
Como personalizar as pginas de erro
Como definir Controladores como Servios
Depurao
Como otimizar seu ambiente de desenvolvimento para a depurarao
3.1. Cookbook
429
Doctrine
Como Manipular o Upload de Arquivos com o Doctrine
Como usar as extens?es do Doctrine: Timestampable, Sluggable, Translatable, etc.
Como Registrar Ouvintes e Assinantes de Eventos
Como usar a Camada DBAL do Doctrine
Como gerar Entidades de uma base de dados existente
Como trabalhar com Mltiplos Gerenciadores de Entidade
Como Registrar Funes DQL Personalizadas
Como Implementar um Formulrio Simples de Registro
Email
Como enviar um e-mail
Como usar o Gmail para enviar E-mails
Como Trabalhar com E-mails Durante o Desenvolvimento
Como fazer Spool de E-mail
Dispatcher de Eventos
Como configurar Filtros aplicados antes e aps
Como estender uma Classe sem usar Herana
Como personalizar o Comportamento do Mtodo sem o uso de Herana
Formulrio
Como personalizar a Renderizao de Formulrios
Como usar os Transformadores de Dados
Como Modificar Formulrios dinamicamente usando Eventos de Formulrio
Como embutir uma Coleo de Formulrios
Como Criar um Tipo de Campo de Formulrio Personalizado
Como criar uma Extenso do Tipo de Formulrio
Como usar a opo de campo de formulrio Virtual
Como configurar Dados Vazios para uma Classe de Formulrio
Log
Como usar o Monolog para escrever Logs
Como configurar o Monolog para enviar erros por e-mail
Como fazer log de Mensagens em Arquivos Diferentes
Profiler
Como criar um Coletor de Dados personalizado
Request
Como registrar um novo Formato de Requisio e de Mime Type
Roteamento
430
Captulo 3. Cookbook
3.1. Cookbook
431
432
Captulo 3. Cookbook
CAPTULO 4
Componentes
4.1 Os Componentes
4.1.1 O Componente ClassLoader
O Componente ClassLoader carrega as classes do seu projeto automaticamente se elas seguirem algumas
convenes PHP padro.
Sempre que voc usar uma classe indefinida, o PHP utiliza o mecanismo de autoload automtico para delegar o
carregamento de um arquivo definindo a classe. O Symfony2 fornece um autoloader universal, que capaz de
carregar classes de arquivos que implementam uma das seguintes convenes:
Os padres tcnicos de interoperabilidade para namespaces do PHP 5.3 e para nomes de classes;
A conveno de nomenclatura de classes do PEAR.
Se as suas classes e as bibliotecas de terceiros que voc usa no seu projeto seguem estes padres, o autoloader do
Symfony2 o nico autoloader que voc vai precisar.
Instalao
Voc pode instalar o componente de vrias formas diferentes:
Usando o repositrio Git oficial (https://github.com/symfony/ClassLoader);
Instalando via PEAR ( pear.symfony.com/ClassLoader);
Instalando via Composer (symfony/class-loader no Packagist).
Uso
Novo na verso 2.1: O mtodo useIncludePath foi adicionado no Symfony 2.1.
Registrar o autoloader UniversalClassLoader simples:
require_once /path/to/src/Symfony/Component/ClassLoader/UniversalClassLoader.php;
use Symfony\Component\ClassLoader\UniversalClassLoader;
$loader = new UniversalClassLoader();
// You can search the include_path as a last resort.
433
$loader->useIncludePath(true);
// ... register namespaces and prefixes here - see below
$loader->register();
Para ganhos de desempenho menores, os caminhos das classes podem ser armazenados em memria usando com o
APC registrando o ApcUniversalClassLoader:
require_once /path/to/src/Symfony/Component/ClassLoader/UniversalClassLoader.php;
require_once /path/to/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php;
use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
$loader = new ApcUniversalClassLoader(apc.prefix.);
$loader->register();
$loader->registerNamespace(Symfony, __DIR__./vendor/symfony/symfony/src);
$loader->registerNamespaces(array(
Symfony => __DIR__./../vendor/symfony/symfony/src,
Monolog => __DIR__./../vendor/monolog/monolog/src,
));
$loader->register();
Para as classes que seguem a conveno de nomenclatura do PEAR, utilize os mtodos registerPrefix() ou
registerPrefixes()
$loader->registerPrefix(Twig_, __DIR__./vendor/twig/twig/lib);
$loader->registerPrefixes(array(
Swift_ => __DIR__./vendor/swiftmailer/swiftmailer/lib/classes,
Twig_ => __DIR__./vendor/twig/twig/lib,
));
$loader->register();
Nota:
Algumas bibliotecas tambm exigem que seu caminho raiz seja registrado no include path do PHP
(set_include_path()).
Classes de um sub-namespace ou uma sub-hierarquia das classes PEAR podem ser buscadas em uma lista de localizao para facilitar o vendoring de um sub-conjunto de classes para projetos grandes:
$loader->registerNamespaces(array(
Doctrine\\Common
=>
Doctrine\\DBAL\\Migrations =>
Doctrine\\DBAL
=>
Doctrine
=>
));
434
__DIR__./vendor/doctrine/common/lib,
__DIR__./vendor/doctrine/migrations/lib,
__DIR__./vendor/doctrine/dbal/lib,
__DIR__./vendor/doctrine/orm/lib,
Captulo 4. Componentes
$loader->register();
Neste exemplo, se voc tentar usar uma classe no namespace Doctrine\Common ou em um de seus filhos, o
autoloader vai procurar primeiro pela classe sob o diretrio doctrine-common, e vai, em seguida, retornar para o
diretrio padro Doctrine (o ltimo configurado) se no for encontrada, antes de desistir. A ordem das inscries
significativa neste caso.
4.1.2 Console
O Componente Console
O componente Console facilita a criao de interfaces de linha de comando bonitas e testveis.
O componente Console permite a criao dos seus prprios comandos de linha de comando. Seus comandos do
console podem ser usados para qualquer tarefa recorrente, como cronjobs, importaes ou outros trabalhos realizados
em lote.
Instalao
Para fazer um comando de console que nos cumprimenta na linha de comando, crie o GreetCommand.php e
adicione o seguinte cdigo nele:
namespace Acme\DemoBundle\Command;
use
use
use
use
use
Symfony\Component\Console\Command\Command;
Symfony\Component\Console\Input\InputArgument;
Symfony\Component\Console\Input\InputInterface;
Symfony\Component\Console\Input\InputOption;
Symfony\Component\Console\Output\OutputInterface;
4.1. Os Componentes
435
if ($name) {
$text = Hello .$name;
} else {
$text = Hello;
}
if ($input->getOption(yell)) {
$text = strtoupper($text);
}
$output->writeln($text);
}
}
Voc tambm precisa criar o arquivo para ser executado na linha de comando que cria uma Application e adicionar
comandos ele:
#!/usr/bin/env php
# app/console
<?php
use Acme\DemoBundle\Command\GreetCommand;
use Symfony\Component\Console\Application;
$application = new Application();
$application->add(new GreetCommand);
$application->run();
Voc tambm pode usar a opo --yell para converter tudo em letras maisculas:
$ app/console demo:greet Fabien --yell
Ir mostrar:
HELLO FABIEN
Colorindo a sada Sempre que a sada for um texto, voc pode envolv-lo com tags para colorir a sua sada. Por
exemplo:
// green text
$output->writeln(<info>foo</info>);
// yellow text
$output->writeln(<comment>foo</comment>);
// black text on a cyan background
$output->writeln(<question>foo</question>);
// white text on a red background
$output->writeln(<error>foo</error>);
436
Captulo 4. Componentes
As cores de primeiro plano e de fundo disponveis so: black, red, green, yellow, blue, magenta, cyan e
white.
E as opes disponveis so: bold, underscore, blink, reverse e conceal.
Usando argumentos de comando
A parte mais interessante dos comandos so os argumentos e opes que voc pode disponibilizar. Argumentos so
as strings - separados por espaos - que vem aps o nome do comando. Eles so ordenados, e podem ser opcionais
ou obrigatrios. Por exemplo, adicione um argumento opcional last_name ao comando e torne o argumento name
obrigatrio:
$this
// ...
->addArgument(name, InputArgument::REQUIRED, Who do you want to greet?)
->addArgument(last_name, InputArgument::OPTIONAL, Your last name?);
O comando pode agora ser usado em qualquer uma das seguintes formas:
$ app/console demo:greet Fabien
$ app/console demo:greet Fabien Potencier
Ao contrrio dos argumentos, as opes no so ordenadas (ou seja, voc pode especific-las em qualquer ordem) e
so especificadas com dois traos (ex., --yell - voc tambm pode declarar um atalho de uma letra que pode chamar
com um trao nico, como -y). As opes so sempre opcionais, e podem ser configuradas para aceitar um valor (ex.
dir=src) ou simplesmente como um sinalizador booleano sem um valor (ex. yell).
Dica: Tambm possvel fazer uma opo opcionalmente aceitar um valor (de modo que --yell ou yell=loud
funcionem). Opes tambm podem ser configuradas para aceitar um array de valores.
Por exemplo, adicione uma nova opo ao comando que pode ser usada para especificar quantas vezes a mensagem
deve ser impressa:
$this
// ...
->addOption(iterations, null, InputOption::VALUE_REQUIRED, How many times should the message b
Em seguida, use o cdigo abaixo no comando para imprimir a mensagem vrias vezes:
for ($i = 0; $i < $input->getOption(iterations); $i++) {
$output->writeln($text);
}
4.1. Os Componentes
437
Agora, quando executar a tarefa, voc pode, opcionalmente, especificar uma flag --iterations:
$ app/console demo:greet Fabien
$ app/console demo:greet Fabien --iterations=5
O primeiro exemplo ir imprimir apenas uma vez, pois iterations est vazio e o padro 1 (o ltimo argumento
de addOption). O segundo exemplo ir imprimir cinco vezes.
Lembre-se de que as opes no se importam com a ordem. Ento, qualquer um dos seguintes comandos vai funcionar:
$ app/console demo:greet Fabien --iterations=5 --yell
$ app/console demo:greet Fabien --yell --iterations=5
Valor
Esta opo aceita vrios valores (ex. --dir=/foo --dir=/bar)
No aceitar a entrada para esta opo (ex. --yell)
Este valor obrigatrio (ex. --iterations=5), a opo em si ainda
opcional
Esta opo pode ou no ter um valor (ex. yell ou yell=loud)
$this
// ...
->addOption(iterations, null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, How m
Ao criar comandos, voc tem a possibilidade de coletar mais informaes do usurio, fazendo-lhe perguntas. Por
exemplo, suponha que voc queira confirmar uma ao antes de execut-la. Adicione o seguinte ao seu comando:
$dialog = $this->getHelperSet()->get(dialog);
if (!$dialog->askConfirmation($output, <question>Continue with this action?</question>, false)) {
return;
}
Neste caso, o usurio ser perguntado Continue with this action, e, a menos que ele responda com y, a tarefa no ir
executar. O terceiro argumento para askConfirmation o valor padro para retornar se o usurio no informar
nenhuma entrada.
Voc tambm pode fazer perguntas com mais do que uma simples resposta sim/no. Por exemplo, se voc precisa
saber o nome de alguma coisa, voc pode fazer o seguinte:
$dialog = $this->getHelperSet()->get(dialog);
$name = $dialog->ask($output, Please enter the name of the widget, foo);
Testando Comandos
O Symfony2 fornece vrias ferramentas para ajudar a testar os seus comandos. A mais til a classe
CommandTester . Ela usa classes de entrada e sada especiais para facilitar o teste sem um console real:
438
Captulo 4. Componentes
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Acme\DemoBundle\Command\GreetCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
public function testExecute()
{
$application = new Application();
$application->add(new GreetCommand());
$command = $application->find(demo:greet);
$commandTester = new CommandTester($command);
$commandTester->execute(array(command => $command->getName()));
$this->assertRegExp(/.../, $commandTester->getDisplay());
// ...
}
}
O mtodo getDisplay() retorna o que teria sido exibido durante uma chamada normal ao console.
Voc pode testar o envio de argumentos e opes para o comando, passando-os como um array para o mtodo
getDisplay()
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Acme\DemoBundle\Command\GreetCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
// ...
public function testNameIsOutput()
{
$application = new Application();
$application->add(new GreetCommand());
$command = $application->find(demo:greet);
$commandTester = new CommandTester($command);
$commandTester->execute(
array(command => $command->getName(), name => Fabien)
);
$this->assertRegExp(/Fabien/, $commandTester->getDisplay());
}
}
Dica: Voc tambm pode testar uma aplicao de console inteira usando ApplicationTester.
Se um comando depende que outro seja executado antes dele, em vez de pedir ao usurio para lembrar a ordem de
execuo, voc mesmo pode cham-lo diretamente. Isso tambm til se voc quiser criar um comando meta que
apenas executa vrios outros comandos (por exemplo, todos os comandos que precisam ser executados quando cdigo
4.1. Os Componentes
439
do projeto mudou nos servidores de produo: limpar o cache, gerar proxies do Doctrine2, realizar o dump dos assets
do Assetic, ...).
Chamar um comando a partir de outro simples:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find(demo:greet);
$arguments = array(
command => demo:greet,
name
=> Fabien,
--yell => true,
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
// ...
}
Primeiro, voc find() o comando que voc deseja executar, passando o nome de comando.
Em seguida, voc precisa cria um novo ArrayInput com os argumentos e opes que voc deseja passar para o
comando.
Eventualmente, chamando o mtodo run() efetivamente executa o comando e retorna o cdigo retornado pelo comando (valor de retorno do mtodo execute() do comando).
Nota: Na maioria das vezes, chamando um comando a partir de cdigo que no executado na linha de comando no
uma boa idia por vrias razes. Primeiro, a sada do comando otimizada para o console. Mas, mais importante,
voc pode pensar em um comando como sendo um controlador, que deve usar o modelo para fazer algo e exibir
informaes para o usurio. Assim, em vez de chamar um comando pela web, refatore o seu cdigo e mova a lgica
para um nova classe apropriada.
Class Loader
O Componente ClassLoader
Console
O Componente Console
Leia a documentao sobre os Componentes.
440
Captulo 4. Componentes
CAPTULO 5
Documentos de Referncia
stream
/var/log/symfony.log
ERROR
false
my_formatter
fingerscrossed
WARNING
30
custom
service
my_handler
441
action_level:
activation_strategy:
stop_buffering:
buffer_size:
handler:
members:
channels:
type:
~
elements: ~
from_email:
to_email:
subject:
email_prototype:
id:
factory-method:
channels:
type:
elements:
formatter:
WARNING
~
true
0
~
[]
~
~
~
~ # Required (when the email_prototype is used)
~
~
[]
~
XML
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/mono
<monolog:config>
<monolog:handler
name="syslog"
type="stream"
path="/var/log/symfony.log"
level="error"
bubble="false"
formatter="my_formatter"
/>
<monolog:handler
name="main"
type="fingerscrossed"
action-level="warning"
handler="custom"
/>
<monolog:handler
name="custom"
type="service"
id="my_handler"
/>
</monolog:config>
</container>
Nota: Quando o profiler ativado, um handler adicionado para armazenar as mensagens dos logs no profiler. O
Profiler usa o nome debug, ento, ele reservado e no pode ser utilizado na configurao.
442
443
444
CAPTULO 6
Bundles
A Edio Standard do Symfony vem com alguns bundles. Saiba mais sobre eles:
445
446
Captulo 6. Bundles
CAPTULO 7
Contribuindo
7.1 Contribuindo
7.1.1 Contribuindo com o Cdigo
Reportando um Bug
Sempre que voc encontrar um bug no Symfony2, pedimos gentilmente para report-lo. Isso ajuda-nos a tornar o
Symfony2 melhor.
Cuidado: Se voc acredita ter encontrado um problema de segurana, por favor, use o procedimento especial
em seu lugar.
Antes de submeter um bug:
Verifique a documentao oficial para certificar-se de que no est fazendo mau uso do framework;
Pea ajuda na lista de discusso dos usurios_, no frum_, ou no canal IRC do #symfony se voc no tiver
certeza se o problema realmente um bug.
Se o seu problema definitivamente parece um bug, relate-o usando o tracker oficial de bug e siga algumas regras
bsicas:
Use o campo de ttulo para descrever claramente o problema;
Descreva os passos necessrios para reproduzir o bug com exemplos curtos de cdigo (fornecer um teste unitrio
que ilustra o bug melhor);
Fornea o mximo de detalhes possvel sobre o seu ambiente (SO, verso do PHP, verso do Symfony, extenses
habilitadas, ...);
(opcional) Anexe um patch.
Padres de Codificao
Para contribuir com cdigo para o Symfony2, voc deve seguir seus padres de codificao. Para encurtar a histria,
esta a regra de ouro: Imite o cdigo existente no Symfony2. Muitos Bundles e bilbiotecas usados pelo Symfony2
tambm seguem as mesmas regras, e voc tambm deveria.
447
Lembre-se que a principal vantagem de padres que cada pedao de cdigo parece familiar, no sobre isso ou
aquilo ser mais legvel.
Como uma imagem ou cdigo diz mais que mil palavras, aqui est um exemplo curto, contendo a maior parte
dos aspectos descrito abaixo.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Acme;
class Foo
{
const SOME_CONST = 42;
private $foo;
/**
* @param string $dummy Some argument description
*/
public function __construct($dummy)
{
$this->foo = $this->transform($dummy);
}
/**
* @param string $dummy Some argument description
* @return string|null Transformed input
*/
private function transform($dummy)
{
if (true === $dummy) {
return;
}
if (string === $dummy) {
$dummy = substr($dummy, 0, 5);
}
return $dummy;
}
}
Estrutura
Captulo 7. Contribuindo
449
Licena
450
Captulo 7. Contribuindo