ti-enxame.com

Como a anotação é útil no PHP?

Como a anotação é útil no PHP? e não me refiro ao PHPDoc genericamente.

Eu só quero um exemplo do mundo real ou algo assim, eu acho.


Então, de acordo com a resposta de @ Max: Anotações realizam a mesma coisa que as Abstract Factories, apenas através de uma linha de PHPDoc especializada. - hopeseekr 0 segundos atrás edit

37
Theodore R. Smith

Rob Olmos explicou bem: 

As anotações basicamente permitem que você injete o comportamento e pode promover o desacoplamento.

Em minhas palavras, eu diria que essas anotações são valiosas, especialmente no contexto de reflection onde você coleta metadados (adicionais) sobre a classe/método/propriedade que você está inspecionando.

Outro exemplo, em vez de ORM: Injeção de Dependência frameworks. A próxima estrutura FLOW3 por exemplo, usa docComments/anotações para identificar quais objetos são injetados em uma instância criada a partir de um contêiner DI, em vez de especificá-lo em um arquivo de configuração XML. 

Exemplo simplificado a seguir:

Você tem duas classes, uma Soldier class e uma Weapon class. Uma instância Weapon é injetada em uma instância Soldier. Veja a definição das duas classes:

class Weapon {
    public function shoot() {
        print "... shooting ...";
    }
}

class Soldier {
    private $weapon;

    public function setWeapon($weapon) {
        $this->weapon = $weapon;
    }

    public function fight() {
        $this->weapon->shoot();
    }
}

Se você usasse essa classe e injetasse todas as dependências manualmente, você faria assim:

$weapon = new Weapon();

$soldier = new Soldier();
$soldier->setWeapon($weapon); 
$soldier->fight();

Tudo bem, foi um monte de código clichê (fique comigo, estou vindo para explicar que anotações são úteis em breve). O que as estruturas de Injeção de Dependência podem fazer por você é abstrair a criação de tais objetos compostos e injetar todas as dependências automaticamente, você simplesmente faz:

$soldier = Container::getInstance('Soldier');
$soldier->fight(); // ! weapon is already injected

Certo, mas a Container precisa saber quais dependências uma classe Soldier possui. Assim, a maioria dos frameworks comuns usa XML como formato de configuração. Exemplo de configuração:

<class name="Soldier">
    <!-- call setWeapon, inject new Weapon instance -->
    <call method="setWeapon">
        <argument name="Weapon" />
    </call>
</class>

Mas o que o FLOW3 usa ao invés de XML são as anotações diretamente no código PHPpara definir essas dependências. No FLOW3, sua classe Soldier seria assim (sintaxe apenas como um exemplo):

class Soldier {
    ...

    // ---> this

    /**
     * @inject $weapon Weapon
     */
    public function setWeapon($weapon) {
        $this->weapon = $weapon;
    }

    ...

Portanto, nenhum XML é necessário para marcar a dependência de Soldier para Weapon para o contêiner DI. 

O FLOW 3 usa essas anotações também no contexto de AOP , para marcar métodos que devem ser "entrelaçados" (significa comportamento injetado antes ou depois de um método). 


No que me diz respeito, não tenho muita certeza sobre a utilidade dessas anotações. Eu não sei se isso torna as coisas mais fáceis ou piores "escondendo" esse tipo de dependências e configuração no código PHPem vez de usar um arquivo separado.

Eu trabalhei e. g. no Spring.NET, NHibernate e com um framework DI (não FLOW3) em PHP ambos baseados em arquivos de configuração XML e não posso dizer que foi muito difícil. Manter esses arquivos de configuração também foi bom. 

Mas talvez um projeto futuro com o FLOW3 prove o oposto e as anotações sejam o caminho real a seguir. 

52
Max

Exatamente o que é bom? 

As anotações basicamente permitem que você injete o comportamento e pode promover o desacoplamento. Um exemplo seria a Doutrina ORM. Devido ao uso de anotações, você não precisa herdar de uma classe específica do Doctrine, ao contrário do Propel ORM.

É difícil depurar a codificação dinâmica de carregamento lento?

Infelizmente, isso é um efeito colateral, como a maioria das ações de desacoplamento, como padrões de design, traduções de dados, etc.

Hmm. Meu cérebro ainda não está crescendo. - hopeseekr

Se você não herdou de uma classe Doctrine, provavelmente teria que usar alguma outra especificação de metadados, como um arquivo de configuração, para especificar que uma determinada propriedade é o ID do registro. Nesse caso, seria muito longe da sintaxe que a anotação (metadados) descreve.

7
Rob Olmos

Por questões de integridade, aqui está um exemplo de trabalho usando anotações e também como estender a linguagem PHP para suportá-las, tudo em um único arquivo.

Estas são as anotações "reais", ou seja, declaradas no nível da linguagem e não ocultas nos comentários. A vantagem de usar anotações de estilo 'Java' como essas é que elas não podem ser ignoradas pelos analisadores que ignoram os comentários.

A parte superior, antes de __halt_compiler();, é o processador, estendendo a linguagem PHP com uma anotação de método simples que armazena em cache as chamadas de método.

A classe na parte inferior é um exemplo do uso da anotação @cache em um método.

(este código é melhor lido de baixo para cima). 

<?php

// parser states
const S_MODIFIER  = 0;  // public, protected, private, static, abstract, final
const S_FUNCTION  = 1;  // function name
const S_SIGSTART  = 2;  // (
const S_SIGEND    = 3;  // )
const S_BODYSTART = 4;  // {
const S_BODY      = 5;  // ...}

function scan_method($tokens, $i)
{
  $state = S_MODIFIER;

  $depth = 0;  # {}

  $funcstart = $i;
  $fnameidx;
  $funcbodystart;
  $funcbodyend;
  $sig_start;
  $sig_end;
  $argnames=array();

  $i--;
  while ( ++$i < count($tokens) )
  {
    $tok = $tokens[$i];

    if ( $tok[0] == T_WHITESPACE )
      continue;

    switch ( $state )
    {
      case S_MODIFIER:
        switch ( $tok[0] )
        {
          case T_PUBLIC:
          case T_PRIVATE:
          case T_PROTECTED:
          case T_STATIC:
          case T_FINAL:
          case T_ABSTRACT:  # todo: handle body-less functions below
            break;

          case T_FUNCTION:
            $state=S_FUNCTION;
            break;

          default:
            return false;
        }
        break;

      case S_FUNCTION:
        $fname = $tok[1];
        $fnameidx = $i;
        $state = S_SIGSTART;
        break;

      case S_SIGSTART:
        if ( $tok[1]=='(' )
        {
          $sig_start = $i;
          $state = S_SIGEND;
        }
        else return false;

      case S_SIGEND:
        if ( $tok[1]==')' )
        {
          $sig_end = $i;
          $state = S_BODYSTART;
        }
        else if ( $tok[0] == T_VARIABLE )
          $argnames[]=$tok[1];
        break;

      case S_BODYSTART:
        if ( $tok[1] == '{' )
        {
          $funcbodystart = $i;
          $state = S_BODY;
        }
        else return false;
        #break;  # fallthrough: inc depth

      case S_BODY:
        if ( $tok[1] == '{' ) $depth++;
        else if ( $tok[1] == '}' )
          if ( --$depth == 0 )
            return (object) array(
              'body_start'  => $funcbodystart,
              'body_end'    => $i,
              'func_start'  => $funcstart,
              'fnameidx'    => $fnameidx,
              'fname'       => $fname,
              'argnames'    => $argnames,
              'sig_start'   => $sig_start,
              'sig_end'     => $sig_end,
            );
        break;

      default: die("error - unknown state $state");
    }
  }

  return false;
}

function fmt( $tokens ) {
  return implode('', array_map( function($v){return $v[1];}, $tokens ) );
}

function process_annotation_cache( $tokens, $i, $skip, $mi, &$instructions )
{
    // prepare some strings    
    $args  = join( ', ', $mi->argnames );
    $sig   = fmt( array_slice( $tokens, $mi->sig_start,  $mi->sig_end    - $mi->sig_start  ) );
    $origf = fmt( array_slice( $tokens, $mi->func_start, $mi->body_start - $mi->func_start ) );

    // inject an instruction to rename the cached function
    $instructions[] = array(
      'action'  => 'replace',
      'trigger' => $i,
      'arg'     => $mi->sig_end -$i -1,
      'tokens'  => array( array( "STR", "private function __cached_fn_$mi->fname$sig" ) )
    );

    // inject an instruction to insert the caching replacement function
    $instructions[] = array(
      'action'  => 'inject',
      'trigger' => $mi->body_end + 1,
      'tokens'  => array( array( "STR", "

  $origf
  {
    static \$cache = array();
    \$key = join('#', func_get_args() );
    return isset( \$cache[\$key] ) ? \$cache[\$key]: \$cache[\$key] = \$this->__cached_fn_$mi->fname( $args );
  }
      " ) ) );
}


function process_tokens( $tokens )
{
  $newtokens=array();
  $skip=0;
  $instructions=array();

  foreach ( $tokens as $i=>$t )
  {
    // check for annotation
    if ( $t[1] == '@'
      && $tokens[$i+1][0]==T_STRING    // annotation name
      && $tokens[$i+2][0]==T_WHITESPACE 
      && false !== ( $methodinfo = scan_method($tokens, $i+3) )
    )
    {
      $skip=3;  // skip '@', name, whitespace

      $ann_method = 'process_annotation_'.$tokens[$i+1][1];
      if ( function_exists( $ann_method ) )
        $ann_method( $tokens, $i, $skip, $methodinfo, $instructions );
      # else warn about unknown annotation
    }

    // process instructions to modify the code
    if ( !empty( $instructions ) )
      if ( $instructions[0]['trigger'] == $i ) // the token index to trigger at
      {
        $instr = array_shift( $instructions );
        switch ( $instr['action'] )
        {
          case 'replace': $skip = $instr['arg']; # fallthrough
          case 'inject':  $newtokens=array_merge( $newtokens, $instr['tokens'] );
            break;

          default:
            echo "<code style='color:red'>unknown instruction '{$instr[1]}'</code>";
        }
      }

    if ( $skip ) $skip--;
    else $newtokens[]=$t;
  }

  return $newtokens;
}

// main functionality

$data   = file_get_contents( __FILE__, null, null, __COMPILER_HALT_OFFSET__ );
$tokens = array_slice( token_get_all("<"."?php ". $data), 1 );
// make all tokens arrays for easier processing
$tokens = array_map( function($v) { return is_string($v) ? array("STR",$v) : $v;}, $tokens );

echo "<pre style='background-color:black;color:#ddd'>" . htmlentities( fmt($tokens) ) . "</pre>";

// modify the tokens, processing annotations
$newtokens = process_tokens( $tokens );

// format the new source code
$newcode = fmt( $newtokens );
echo "<pre style='background-color:black;color:#ddd'>" . htmlentities($newcode) . "</pre>";

// execute modified code
eval($newcode);

// stop processing this php file so we can have data at the end
__halt_compiler();

class AnnotationExample {

  @cache
  private function foo( $arg = 'default' ) {
    echo "<b>(timeconsuming code)</b>";
    return $arg . ": 1";
  }

  public function __construct() {
    echo "<h1 style='color:red'>".get_class()."</h1>";
    echo $this->foo("A")."<br/>";
    echo $this->foo("A")."<br/>";
    echo $this->foo()."<br/>";
    echo $this->foo()."<br/>";
  }
}

new AnnotationExample();

Mantendo o exemplo de um Contêiner DI (que basicamente não tem nada a ver com anotações), a abordagem acima também pode ser usada para modificar construtores de classe para cuidar da injeção de quaisquer dependências, o que torna o uso de componentes completamente transparente. A abordagem de modificar o código-fonte antes de ser avaliado é aproximadamente equivalente a 'instrumentação de bytecode' em Java Classloaders personalizados. (Eu menciono o Java desde o AFAIK, onde as anotações foram introduzidas).

A utilidade deste exemplo em particular é que, em vez de manualmente ter que escrever código de cache para cada método, você pode simplesmente marcar um método como tendo que ser armazenado em cache, reduzindo a quantidade de trabalho repetitivo e tornando o código mais claro. Além disso, os efeitos de qualquer anotação em qualquer lugar podem ser ativados e desativados no tempo de execução.

3
Kenney

o phpDocumentor e os IDEs modernos usam anotações para determinar os tipos de parâmetros do método (@param), valores de retorno (@return) e assim por diante.

O PhpUnit Testing usa anotações para agrupar testes, definir dependências.

0
kta