Utilizando Annotations com PHP

Você sabe o que são anotações? Se você ainda não ouviu falar de annotations não sabe o que está perdendo.
Para quem não sabe ainda, anotações são etiquetas com informações relevantes dentro de um bloco de comentário no qual você escreve metadados sobre alguma classe, método ou mesmo atributos de classe para que se possa, em tempo de execução resgatar esses metadados e trabalhá-los de acordo com a sua necessidade.
Eu testei o Addendum, uma biblioteca que estende a ReflectionClass e que funciona muito bem, para quem deseja anotar ao estilo dela. Porém, como eu teria que estender a classe Addendum e ainda teria que modificar seu namespace, isso não seria viável para mim.
A solução para isso? Brincar um pouquinho com expressões regulares e a família de classes Reflection do PHP para resolver o meu problema.

Pensando nas expressões regulares

Se você já leu uma classe documentada no padrão PHPDoc ou JavaDoc já viu que existe um padrão de criação desses comentários. Eles, na maioria das IDEs, são reconhecidos e para alguns até servem de artifício para que o code insight funcione, buscando essas informações de comentários, negritando nomes ou tipos de variáveis e essas coisas.
Um dos casos mais legais para servir de exemplo seria o caso de um ORM (Object-Relation Mapping). Há pouco tempo, eu e mais dois amigos concordamos em tocar um projeto de um ORM juntos. Esse ORM baseava-se na sintaxe do django e ao documentarmos pareceu um desafio bem legal. Pensamos nas anotações e tudo mais para que pudéssemos dividir as tarefas e uma delas era de criar classes baseadas nos tipos de dados que queríamos para que elas fossem instanciadas nos atributos das classes.
Ao abrir o código de uma classe e vermos a sua documentação, nós podemos ver tags como @author, @since, @version etc. As anotações tem o mesmo padrão, com a diferença que podem ser personalizadas de acordo com a necessidade. No caso de um ORM a primeira que podemos falar seria algo como @table. Essa tag, claramente, nos informaria qual a tabela do banco seria a fonte de dados para essa classe. Veja um pequeno exemplo:

Veja, como falado acima, temos tags que são padrão (como @author). Nesse nosso caso escrevemos a tag @table para informar qual a tabela seria a fonte dos dados. Outras sintaxes, principalmente quando formos anotar dados sobre atributos será mostrados, pois podem necessitar de atributos multivalorados.
Veja abaixo como podemos anotar os dois atributos (id e nome) da classe Pessoa mostrada acima:

Veja que a tag @column pode ter seu valor type é diferente para cada atributo. Um type serial é para um campo serial, ou seja, uma sequence. No caso do mysql isso é conhecido como auto_increment. O outro tipo (type), string, tem outros atributos como notnull (campo não nulo) e size (tamanho do campo).
Com esses exemplos já podemos detectar alguns padrões de anotações e a partir dai começarmos a implementar as expressões regulares para apanhar essas anotações.

Como fazer e onde testar

Expressões regulares são muito comuns em todas as linguagens e no PHP não é diferente. Nas suas últimas versões aposentamos algumas funções como ereg, eregi, ereg_replace e eregi_replace. Usaremos em nossos exemplos a função preg_match_all que serve melhor para esse propósito.
De acordo com o escrito na sua documentação, existente no site php.net sob o endereço <a href=”http://php.net/manual/en/function.preg-match-all.php”http://php.net/manual/en/function.preg-match-all.php, vemos o seguinte:
int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] ), onde:
$pattern: é a nossa expressão regular, ou seja, a string que simboliza o padrão a ser reconhecido
$subject: é o texto a ser pesquisado em busca do padrão $pattern. No nosso caso será o comentário da classe ou do atributo;
$matches: é uma variável do tipo array que receberá todas as ocorrências de $pattern em $subject.
Se você usa linux pode baixar o kiki para realizar os testes de expressões regulares. Se não, pode usar sites do http://regexpal.com/ ou http://www.solmetra.com/scripts/regex/ (esse último mais alinhado à nossa função preg_math_all, pois você pode escolher usar a mesma função). O kiki é, para o gnome, a sua melhor opção, nem precisa estar conectado.
Sabendo disso, vamos ao próximo passo, que é criar as expressões regulares para os nossos padrões.

Reconhecendo as suas anotações

Temos @table e @column, ambas tags que precisaríamos no nosso para mapear essa classe, e cada uma com uma sintaxe diferente. Vejamos cada uma separadamente até encontrar cada padrão.
Para a nossa anotação de tabela, temos @table=tb_pessoa, que tem o seu padrão representado por:
@ + tag + = + valor.
Para esse padrão a nossa expressão regular será a seguinte:
@[w]+[ ]{0,1}=[ ]{0,1}[w]+, onde:
[w]+ é a string que dá nome à tag;
[ ]{0,1} é um espaço que pode ou não existir antes do =;
= separa os dois termos (tag e valor)
[ ]{0,1} é um espaço que pode ou não existir depois do =;
[w]+ é a string que atribui valor à anotação;

Já para a tag de coluna nós temos um padrão um pouco diferente:
@tag + ( + valor + ).
E a expressão regular para isso é pode ser:
@[w]+[ ]{0,1}([w=, .-<>:]+, onde:
[w]+ é a string que dá nome à tag;
[ ]{0,1} é um espaço que pode ou não existir antes do =;
( escapa a abertura parênteses
[ ]{0,1} é um espaço que pode ou não existir depois do =;
[w]+ é a string que atribui valor à anotação;
( escapa o fechamento parênteses.
Para separar os valores dentro dos parênteses você pode usar expressões regulares, explode ou outra função que seja necessária. No momento vamos focar somente em encontrar as tags e descobrir seus valores de forma mais bruta.
Para que usemos a expressão regular tanto para @table quanto para @column, vamos uni-las com o caracter “|” que concatena as duas formando um só $pattern.
A implementação em PHP disso ficaria como no bloco abaixo:

Como obter os comentários e extrair as anotações

Em PHP usamos as classes de Reflection para obter os comentários de classes, métodos e atributos. Para um caso como o nosso devemos usar duas dessas classes: ReflectionClasse e ReflectionProperty.
Extrair os comentários de uma classe é a tarefa de ReflectionClass. Ela é uma classe muito fácil de usar e só precisa de um parâmetro para que possamos começar seu uso. Vejamos:

Agora que já instanciamos ReflectionClass, podemos tranquilamente obter os comentários de seus atributos instanciando ReflectionProperty como a seguir:
<?
$reflection = ReflectionClass(‘Person’);
$doc = $reflection->getDocComment();

$prop = ReflectionProperty($reflection,’id’);
$comment_id = $prop->getDocComment();
?>
No código acima instanciamos ReflectionProperty e a partir dai obtemos os comentários do atributo id na variável $comment_id.
Nesse momento já temos a faca e o queijo na mão, ou melhor, as expressões regulares e os comentários que devem ser submetidos à função preg_match_all e nada mais nos falta.

Conclusão

Anotações em código são coisas muito legais de se ver. Elas organizam, informam e ainda podem servir de metadados para que possamos reaproveitar melhor nossos códigos, nos propiciando uma maior produtividade.
Os códigos acima foram parte da minha base de testes para reconhecer as expressões regulares das tags mostradas acima, mas não pense que outros padrões não podem surgir. Eles, tanto para mim quanto para qualquer outro programador, podem aparecer diante de uma necessidade específica. Tome a liberdade de testar outros padrões.
Eu pesquisei essa solução porque outra (Addendum) não era exatamente o que eu precisava para o momento. Mais uma vez vemos aquela máxima de que “A necessidade é a mãe da invenção”.
Os códigos aqui dispostos estão em uma classe que estão disponíveis para a comunidade no endereço http://github.com/evaldobarbosa. Baixe, brinque, modifique, use. Não se esqueça de me informar se tiver algum outro padrão interessante.
Se solução for interessante e você quiser falar mais sobre, estou no @evaldobarbosa.

Be Sociable, Share!

4 ideias sobre “Utilizando Annotations com PHP

  1. Pingback: Utilizando Annotations com PHP - Evaldo Barbosa

Deixe uma resposta