Utilizando funções de um banco de dados específico com o Doctrine2 e o Symfony2

Um dos ganhos de performance do Doctrine2 é devido a sua DQL conter apenas as funções que são comuns a todos os bancos de dados.

Para utilizarmos funções específicas de cada banco de dados (por exemplo, a função IFNULL do MySQL ou a função NVL do Oracle), o Doctrine nos permite adicioná-las através de extensões.

Segue aqui um exemplo para adicionar uma função YEAR, que retorna o ano de uma data fornecida, utilizando a função EXTRACT do Oracle:

Primeiro adicione um diretório que irá conter a sua função, por exemplo DoctrineExtensions\Query\Oracle.

Dentro deste diretório vamos criar a classe Year.php, que irá conter a nossa função:


<php
namespace DoctrineExtensions\Query\Oracle;

use Doctrine\ORM\Query\Lexer,
    Doctrine\ORM\Query\AST\Functions\FunctionNode;

class Year extends FunctionNode
{
    private $date;

    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
    {
        return sprintf(
                'EXTRACT(YEAR FROM %s)',
                $sqlWalker->walkArithmeticPrimary($this->date));
    }

    public function parse(\Doctrine\ORM\Query\Parser $parser)
    {
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);
        $this->date = $parser->ArithmeticPrimary();
        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
    }
}

A classe Year deve estender a classe FunctionNode, que requer que sejam implementados dois métodos: getSql e parse. Informações mais detalhadas podem ser encontradas no artigo do cookbook do Doctrine "DQL user defined functions"

Registrando a função

Agora, só precisamos disponibilizar a função YEAR ao DQL do Doctrine. Para isso, é necessário registrá-la, através das configurações do Doctrine no arquivo app/config/config.yml, sob a chave dql. Logo abaixo da chave dql, você deve definir a chave do tipo da sua função, que pode ser: string_functions, numeric_functions ou datetime_functions. Em seguida, vem a chave com o nome da sua função e a localização da respectiva classe:


# Doctrine Configuration
doctrine:
  orm:
    dql:
      datetime_functions:
        year: DoctrineExtensions\Query\Oracle\Year

Dica: Antes de escrever a sua função, verifique se ela já existe no repo DoctrineExtensions, que contém uma coleção de funções, com várias contribuições.

Mais sobre

O que mudou na versão 2.2 do Symfony

Na sexta-feira passada (01/03), foi lançada a versão 2.2 do Symfony, que inclui várias funcionalidades e melhorias, que estavam sendo anunciadas semanalmente no blog oficial. Confira aqui um resumo de algumas das novas funcionalidades disponíveis:

Console

Autocompletar na linha de comando: mais uma melhoria para agilizar o desenvolvimento! Agora usando tab e as setas podemos selecionar a resposta rapidamente e sem erros. Você pode passar todas as respostas válidas para o seu comando como último argumento do método askAndValidate():

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

$app = new Application();
$app->register('ask-color')->setCode(function (InputInterface $input,
   OutputInterface $output) use ($app) {
   $colors = array(
              'red', 'blue', 'yellow', 'yellow-light', 'yellow-dark'
             );
   $validation = function ($color) use ($colors) {
       if (!in_array($color, array_values($colors))) {
           throw new \InvalidArgumentException(sprintf(
                         'Color "%s" is invalid.', $color));
       }

       return $color;
   };

   // pergunta e valida a resposta
   $dialog = $app->getHelperSet()->get('dialog');
   $color = $dialog->askAndValidate($output, 
                        'Enter your favorite color (default to red): ', 
                        $validation, false, 'red', $colors);

   $output->writeln(sprintf('You have just entered: %s', $color));
});

$app->run();

O funcionamento do comando, pode ser verificado neste vídeo.

Exibição de uma barra de progresso para as tarefas de longa execução: ao executar comandos de longa duração pela CLI, podemos fornecer um feedback ao usuário, durante a execução do comando, através de um helper de barra de progresso que faz todo o trabalho para você:

$progress = $app->getHelperSet()->get('progress');

$progress->start($output, 50);
$i = 0;
while ($i++ < 50) {     
    // faz algo     
    // avança a barra de progresso em 1 unidade     
    $progress->advance();
}

$progress->finish();

Escondendo as senhas fornecidas na linha de comando: o novo método askHiddenResponse nos permite esconder o que os usuários digitam na linha de comando:

$dialog      = $this->getHelperSet()->get('dialog');
$password = $dialog->askHiddenResponse($output, 'Qual é a senha do banco de dados?');

Restringindo as opções solicitadas ao usuário: há várias formas de pedir algumas informações na CLI. A partir do Symfony 2.2, podemos restringir o que o usuário pode informar através do novo helper select(), fazendo com que ele escolha a partir de uma lista de opções.

Finder

O componente Finder oferece uma agradável DSL para pesquisar arquivos e diretórios. As seguintes melhorias estão disponíveis com a versão 2.2:

Filtro por caminho: com o método path(), que aceita strings ou expressões regulares, agora é possível restringir arquivos e diretórios pelo caminho:
Finder::create()->path('some/special/dir');
Finder::create()->path('/^foo\/bar/');

Para negar a restrição, utilize os métodos notName() e notPath().

Suporte a Glob no método in(): os diretórios onde o Finder deve procurar por arquivos e diretórios podem ser definidos como globs no método in():

Finder::create()->files()->in(
           'src/Symfony/*/*/Resources/translations');

Aumento de velocidade em algumas Plataformas (Linux, MacOs e BSD): o desempenho do Finder foi muito melhorado, através da conversão dos critérios para comandos nativos. Verifique os resultados iniciais do benchmark que foram publicados quando o PR foi submetido.

HttpKernel

Log das chamadas obsoletas: A primeira versão LTS do Symfony2 será a 2.3 (a ser lançada em Maio de 2013) após estão versão, não serão mais realizadas quebras de compatibilidade com as versões anteriores sem uma razão muito boa (ex. uma questão de segurança). Assim, desde a versão 2.0, em vez de apenas substituir ou remover as funcionalidades existentes, elas passam a ser definidas como obsoletas, e serão removidas definitivamente na versão 2.3.

Para fornecer uma transição mais suave, o Symfony 2.2 apresenta um novo recurso: o log das chamadas obsoletas. Sempre que você chamar um método obsoleto ou quando criar uma instância de uma classe obsoleta, o Symfony vai fazer o log da chamada e alertá-lo na barra de ferramentas de depuração. Também é possível verificar exatamente onde a chamada ocorreu no painel de log do profiler web.

Novo sub-framework para gerenciar fragmentos de recursos: um novo sub-framework foi adicionado para lidar com a renderização de fragmentos de recursos e facilitar o uso de diferentes estratégias: sub-requests internos (processados diretamente pelo Symfony), ESIs (processados por um proxy reverso - Varnish, ...), HIncludes (processados pelo navegador) e SSIs ( processandos pelo servidor web - Apache, ... - somente no Symfony 2.3). O componente HttpKernel pode lidar agora com sub-requests :)

Exibição agradável dos erros fatais: os erros fatais são exibidos de uma forma agradável como os outros erros.

Erro fatal no Symfony 2.1:

Mensagem de erro fatal melhorada no Symfony 2.2:

Componente Process

O componente Process permite a execução de sub-processos em PHP, tanto na forma de bloqueio (próximo processo espera a finalização do processo anterior para executar) como sem bloqueio (processos são executados simultaneamente). Ele também ganhou melhorias na nova versão do Symfony:

Obtenção de saída incremental de um Processo: você pode obter os dados de saída incrementais usando os métodos getIncrementalOutput() e getIncrementalErrorOutput(), que retornam as novas saídas desde a última chamada enquanto getOutput() e getErrorOutput() retornam os resultados completos:

use Symfony\Component\Process\Process;

$processes = array();
$processes[] = new Process('ls -lsa');
$processes[] = new Process('ps waux');

while (count($processes) > 0) {
     foreach ($processes as $i => $process) {
         if (!$process->isStarted()) {
             echo "Process starts\n";

             $process->start();

             continue;
         }

         echo $process->getIncrementalOutput();
         echo $process->getIncrementalErrorOutput();

         if (!$process->isRunning()) {
             echo "Process stopped\n";

             unset($processes[$i]);
         }
     }

     sleep(1);
}

Reiniciar um processo: podemos agora reiniciar um processo, por exemplo, no caso dele falhar:

use Symfony\Component\Process\ProcessBuilder;

if (!$process->isRunning()) {
    if (!$process->isSuccessful()) {
        $cloned = $process->restart();

        // ...
    }
}

Obter o status de um processo em execução: ao executar um conjunto de processos, você pode querer reiniciar processos que morreram (como no exemplo anterior). Mas se o processo ainda não é capaz de iniciar, não há motivo para reiniciá-lo. A partir da versão 2.2, você pode verificar o status de um processo e agir em conformidade:

$process->isSuccessful();
$process->hasBeenSignaled();
$process->hasBeenStopped();
$process->isRunning();

// novidade no Symfony 2.2:
$process->isStarted();
$process->isTerminated();

if (!$process->isRunning()) {
    if ($process->isStarted() && !$process->isSuccessful()) {
        $cloned = $process->restart();

        // ...
    }
}

Roteamento

Suporte para host na URL: as rotas contam agora com suporte nativo à host na URL. A constraint do host funciona exatamente da mesma forma como o padrão: ela pode conter placeholders, os placeholders podem ter requisitos, e um requisito do placeholder pode usar algum parâmetro do container de serviço para ser configurado.

Exemplo típico de uso:

user_homepage:
    path: /
    host: "{user}.example.com"
    defaults: { _controller: AcmeDemoBundle:User:profile }

main_homepage:
    path:  /
    defaults: { _controller: AcmeDemoBundle:Main:homepage }

A constraint do host também pode ser adicionada a um conjunto de rotas ao importá-las.

URLs relativas ao esquema e ao caminho: foram adicionados mais dois tipos de URLs que você gerar:

URLs relativas ao esquema: usado quando algumas páginas são acessadas por HTTP ou HTTPS, evitando as warnings dos navegadores.
//example.org/blog/what-a-wonderful-world

{{ url('blog', { post: 'what-a-wonderful-world' }, true) }}

URLs relativas ao caminho: útil quando você precisa gerar arquivos HTML estáticos que podem ser baixados para serem acessados localmente em um navegador (ex: gerador de blog estático)
../ (caminho relativo com base na URL atual)

{{ path('blog', { post: 'what-a-wonderful-world' }, true) }}

Segurança

Utilitários de Segurança: alguns utilitários de segurança foram refatorados para que você possa usá-los em seu próprio código. Estes utilitários estão disponíveis no namespace Symfony\Component\Security\Core\Util.

Geração de um número aleatório seguro: temos a disposição uma implementação robusta para a geração de um número aleatório, com a classe SecureRandom:

use Symfony\Component\Security\Core\Util\SecureRandom;

$generator = new SecureRandom();
$random    = $generator->nextBytes(10);

O método nextBytes() retorna uma string aleatória composta pelo número de caracteres passados ​​como argumento (10 no exemplo acima).

Comparando Strings: Timing attacks ainda não são bem conhecidos, mas mesmo assim, o Symfony possui proteção contra eles. No Symfony 2.0 e 2.1, esta proteção foi aplicada para comparações de senha feitas no bundle Security, mas, a partir do Symfony 2.2, também está disponível para o desenvolvedor:
use Symfony\Component\Security\Core\Util\StringUtils;

// a senha1 é igual a senha2?
$bool = StringUtils::equals($senha1, $senha2);

Validadores

Validadores para pagamento: adicionado novo validador para cartões de crédito, utilizando o algoritmo Luhn:

// src/Acme/SubscriptionBundle/Entity/Transaction.php

use Symfony\Component\Validator\Constraints as Assert;

class Transaction{
    /**
     * @Assert\Luhn(message = "Por favor verifique o número do cartão fornecido.")
     */
    protected $cardNumber;
}

É possível também verificar se um cartão é válido para uma determinada companhia de cartão de crédito com a constraint CardSchemeValidator:

// src/Acme/SubscriptionBundle/Resources/config/validation.yml

Acme\SubscriptionBundle\Entity\Transaction:
    properties:
        cardNumber:
            - CardScheme:
                schemes: [VISA]
                message: O número do cartão de crédito é inválido.

FrameworkBundle

Mais velocidade nos testes funcionais: agora o profiler é desabilitado por padrão nos testes funcionais (na configuração que vem com a Edição Standard):

# in app/config/config_test.yml
framework:
    profiler:
        enabled: false

Cache para páginas estáticas: todos os sites possuem algum tipo de página estática (por exemplo, uma página sobre). Uma página é estática quando não precisa que nenhuma lógica seja processada. Para facilitar neste caso (e evitar ter que criar um controlador e roteamentos para estas páginas), você pode usar o controlador FrameworkBundle:Template:template que configura tudo, diretamente no arquivo de roteamento. Isto funciona desde a versão 2.0, mas na 2.2, você pode definir também a estratégia de cache:

about:
    pattern: /sobre
    defaults:
        _controller: FrameworkBundle:Template:template
        template: 'AcmeBundle:Pages:sobre.html.twig'
        maxAge: 86400
        sharedMaxAge: 86400
        private: false

Novos componentes

Contamos agora com dois novos componentes (que foram extraídos do código existente):

  • Stopwatch - permite medir o tempo de execução de partes específicas do seu código
  • PropertyAccess - função para ler e escrever de/para um array ou objeto utilizando uma notação simples de string

Mais Documentação

Foram adicionados vários artigos na documentação oficial sobre as novas funcionalidades.

Migração da versão 2.1

Se você possui projetos utilizando a versão 2.1, para fazer a migração para a 2.2 deve-se atualizar o arquivo composer.json e executar o composer.phar update.

Após a atualização, verificar com cuidado as modificações citadas neste documento.

Symfony2 Cheat-Sheet: Console (CLI)

A interface de linha de comando ou console do Symfony2 também é um componente, que pode ser usado separadamente, e facilita a criação da sua própria interface de linha de comando.

A referência a seguir é sobre os comandos que vem juntamente com o Framework Symfony2 e com alguns bundles que são usados frequentemente ( SwiftmailerBundleTwigBundleAsseticBundleFOSJsRoutingBundleFOSUserBundleDoctrineBundleSecurityBundleDoctrineFixturesBundle ):

Mais informações:

Atualização em 06/09/2012: Adicionada informação do bundle ao qual o comando pertence e adicionados os comandos dos bundles: FOSUserBundle e DoctrineFixturesBundle. As versões anteriores encontram-se aqui: [English] e [Português]

Adicionando um novo filtro em seus templates Twig

Adicionar um novo filtro aos seus templates Twig utilizando o Symfony2 é uma tarefa bastante simples, que veremos a seguir, com um exemplo que adiciona um novo filtro unserialize - usando a função PHP com o mesmo nome.

Primeiro, vamos criar uma classe para a nossa nova extensão do Twig, que irá conter o nosso filtro. Para facilitar, podemos estender a classe Twig_Extension, ao invés de implementar a interface Twig_ExtensionInterface, pois esta classe já irá definir todos os métodos para nós, então, não precisamos definir todos novamente, mas somente os que nos interessam ( neste exemplo: getFilters() e getName() ).

A extensão poderá ser salva em qualquer local, neste exemplo iremos salvar em \Extension\Twig dentro do bundle TesteBundle (assumindo que este bundle já foi previamente criado).


\\ Acme\TesteBundle\Extension\Twig\UnserializeTwigExtension.php

<?php  

namespace Acme\TesteBundle\Extension\Twig;

use Symfony\Component\HttpKernel\KernelInterface;

class UnserializeTwigExtension extends \Twig_Extension  
{
  public function getFilters()  
  {
    return array(  
       'unserialize' => new \Twig_Filter_Function('unserialize'),  
    );  
  }  

  public function getName()  
  {  
    return 'unserialize_twig_extension';  
  }  
}

O método getName() deverá retornar um identificador único para a nossa extensão.

No arquivo de configuração config.yml (app/config/config.yml) vamos definir um novo serviço para a classe UnserializeTwigExtension:


services:
  teste.twig.extension.unserializetwigextension:
    class: Acme\TesteBundle\Extension\Twig\UnserializeTwigExtension
    tags:
      -  { name: twig.extension }

Ao aplicarmos a tag para injeção de dependência (DI - Dependency Injection) twig.extension em nosso serviço, estamos informando que nossa classe é uma extensão do Twig personalizada e, também, habilitamos ela.

Pronto! Agora podemos chamar nosso novo filtro unserialize em qualquer template Twig:


# Acme\TesteBundle\Resources\views\Default\index.html.twig
{{ app.session.get('myvar')|unserialize }} 

Para mais informações sobre como estender o Twig criando um novo filtro, tag ou função, verifique a documentação em: http://twig.sensiolabs.org/doc/advanced.html

Até mais ;)