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

Dica: Definindo um nome personalizado para as tabelas i18n do Doctrine

Pesquisando na documentação do Doctrine e em blogs *around the web* a resposta não encontra-se tão facilmente, então deixo aqui a dica para quem pretende utilizar a internacionalização do banco de dados com o symfony e o behaviour i18n do Doctrine e precisa personalizar o nome das classes para as tabelas i18n 😉

A palavra mágica é tableName. Ela é uma opção do behaviour i18n para definir o nome da classe personalizado, em seu schema do banco:


BlogPost:
  actAs:
    I18n:
      tableName: custom_table_name_translation
      fields: [title]
  columns:
    title: string(255)

A solução está em uma thread no grupo do Doctrine que faz referência ao ticket 1000, onde encontra-se a resposta.

Na documentação do Doctrine sobre o behaviour i18n, onde temos a lista de opções disponíveis para o behaviour, encontramos a opção className (%CLASS%Translation) com a qual podemos definir o nome padrão para utilizar nas classes geradas, porém, esta opção não funcionará se você deseja utilizar um nome para sua tabela que não contenha a palavra Translation no final, como por exemplo, na situação em que você está trabalhando com um sistema legado, e o nome das suas tabelas mais a palavra “Translation”, ultrapassarem o limite do tamanho do banco de dados para tabelas, como no caso do Oracle, por exemplo, que é de 30 caracteres (para todos os seus identificadores).

Doctrine + symfony 1.4 – Alterando o nome das sequences do Oracle utilizadas pelo Doctrine

A seguir veremos como alterar no symfony o nome das sequences utilizadas pelo Doctrine.

Por padrão, os nomes das sequences são gerados automaticamente pelo Doctrine seguindo o formato: nomeTabela_seq (‘%s_seq’)

Mas, onde e como posso alterar o nome das sequences, por um novo padrão ou definir um nome para cada modelo independentemente de um formato fixo, para meu banco de dados existente?

Para definir um nome diferenciado de sequence para cada modelo, você deve informar o parâmetro “sequence” na chave primária do modelo desejado em seu esquema do banco de dados (schema.yml). Por exemplo, digamos que o nome de sua sequence para o modelo (tabela) Area seja SEQ_AREA:


Area:
  tableName: AREA
  columns:
    cd_area:      { type: integer(3), primary: true, sequence: SEQ_AREA}
    descricao:    { type: string(255) }

Ao verificar o SQL da sequence na barra de ferramentas para debug web o nome da sequence ainda continua incorreto, pois será adicionado o padrão do Doctrine %s_seq:

SELECT SEQ_AREA_seq.nextval FROM DUAL

O Doctrine nos permite modificar uma série de configurações antes de carregar os modelos através das configurações globais. Você pode verificar as configurações disponíveis na documentação do Doctrine.

O atributo que precisamos alterar agora é o ATTR_SEQNAME_FORMAT que, como o nome sugere, modifica o formato do nome das sequences.
Os atributos globais são setados através da instância singleton Doctrine_Manager.

No symfony, as configurações globais do Doctrine são definidas na classe config/ProjectConfiguration.class.php
através do método configureDoctrine.

Vamos modificar então o atributo ATTR_SEQNAME_FORMAT, onde podemos definir o formato para aceitar qualquer
nome de sequence informado com o atributo “sequence” no schema:


<?php

require_once '/var/www/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration{

  public function setup(){
    $this->enablePlugins('sfDoctrinePlugin', 'sfDoctrineGuardPlugin');
  }

  public function configureDoctrine(Doctrine_Manager $manager){
    $manager->setAttribute(Doctrine::ATTR_SEQNAME_FORMAT, '%s');
  }
}

Observação: Ao definir o nome da sequence com o atributo “sequence” no esquema do banco de dados, não deve-se utilizar o atributo “autoincrement: true”.