web-dev-qa-db-fra.com

En quoi les annotations sont-elles utiles en PHP?

En quoi les annotations sont-elles utiles en PHP? et je ne parle pas de PHPDoc génériquement.

Je veux juste un exemple du monde réel ou quelque chose, je suppose.


Donc, selon la réponse de @ Max: Les annotations accomplissent la même chose que Abstract Factories, uniquement via une ligne de PHPDoc spécialisée. - hopeseekr Il y a 0 secondes modifier 

37
Theodore R. Smith

Rob Olmos a bien expliqué: 

Les annotations vous permettent en principe d’injecter du comportement et peuvent favoriser le découplage.

Dans mes mots, je dirais que ces annotations sont précieuses, en particulier dans le contexte de réflexion où vous rassemblez des métadonnées (supplémentaires) sur la classe/méthode/propriété que vous inspectez.

Un autre exemple à la place de ORM: Dependency Injection frameworks. Le futur cadre FLOW3 , par exemple, utilise docComments/annotations pour identifier les objets injectés dans une instance créée à partir d'un conteneur DI au lieu de le spécifier dans un fichier de configuration XML. 

Exemple simplifié ci-après:

Vous avez deux classes, une classe Soldier et une classe Weapon. Une instance Weapon est injectée dans une instance Soldier. Regardez la définition des deux 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();
    }
}

Si vous utilisiez cette classe et injectiez toutes les dépendances à la main, vous le feriez comme ceci:

$weapon = new Weapon();

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

Très bien, c’était beaucoup de code standard (avec moi, je viens expliquer pourquoi les annotations sont utiles pour très bientôt). Ce que les frameworks Dependency Injection peuvent faire pour vous, c'est d’abréger la création de tels objets composés et d’injecter automatiquement toutes les dépendances, il vous suffit de:

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

Oui, mais la Container doit savoir quelles dépendances une classe Soldier a. Ainsi, la plupart des infrastructures courantes utilisent XML comme format de configuration. Exemple de configuration:

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

Mais ce que FLOW3 utilise à la place de XML, ce sont les annotations directement dans le code PHP afin de définir ces dépendances. Dans FLOW3, votre classe Soldier ressemblerait à ceci (syntaxe uniquement à titre d'exemple):

class Soldier {
    ...

    // ---> this

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

    ...

Donc, aucun XML requis pour marquer la dépendance de Soldier à Weapon pour le conteneur DI. 

FLOW 3 utilise ces annotations également dans le contexte de AOP , pour marquer les méthodes à "tisser" (comportement d'injection avant ou après une méthode). 


En ce qui me concerne, je ne suis pas trop sûr de l'utilité de ces annotations. Je ne sais pas si cela rend les choses plus faciles ou pires si nous "cachons" ce type de dépendances et d’installation dans du code PHP au lieu d’utiliser un fichier séparé.

J'ai travaillé e. g. dans Spring.NET, NHibernate et avec un framework DI (pas FLOW3) dans PHP, tous deux basés sur des fichiers de configuration XML et ne pouvant pas dire que c’était trop difficile. La maintenance de ces fichiers d'installation était également satisfaisante. 

Mais peut-être qu'un futur projet avec FLOW3 prouve le contraire et que les annotations sont la vraie voie à suivre. 

52
Max

Exactement à quoi ça sert? 

Les annotations vous permettent en principe d’injecter du comportement et peuvent favoriser le découplage. Un exemple serait la doctrine ORM. En raison de l'utilisation des annotations, vous ne devez pas hériter d'une classe spécifique à Doctrine contrairement à Propel ORM.

Difficile de déboguer le codage dynamique du chargement paresseux?

Malheureusement, il s’agit d’un effet secondaire comme la plupart/toutes les actions de découplage telles que les modèles de conception, les traductions de données, etc.

Hmm. Mon cerveau ne l'utilise toujours pas. - hopeseekr

Si vous n'avez pas hérité d'une classe Doctrine, vous devrez probablement utiliser une autre spécification de métadonnées, telle qu'un fichier de configuration, pour spécifier qu'une propriété particulière est l'ID de l'enregistrement. Dans ce cas, il serait trop éloigné de la syntaxe décrite par l'annotation (métadonnées).

7
Rob Olmos

Par souci d'exhaustivité, voici un exemple pratique d'utilisation à la fois d'annotations et de l'extension du langage PHP pour les prendre en charge, le tout dans un seul fichier.

Ce sont des annotations "réelles", c'est-à-dire déclarées au niveau de la langue et non cachées dans les commentaires. L'avantage d'utiliser des annotations de style 'Java' comme celles-ci est qu'elles ne peuvent pas être négligées par des analyseurs qui ignorent les commentaires.

La partie supérieure, avant __halt_compiler();, est le processeur, qui étend le langage PHP avec une annotation de méthode simple qui met en cache les appels de méthode.

La classe en bas est un exemple d'utilisation de l'annotation @cache sur une méthode.

(Ce code est préférable de lire de bas en haut). 

<?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();

En reprenant l'exemple d'un conteneur DI (qui n'a fondamentalement rien à voir avec des annotations), l'approche ci-dessus peut également être utilisée pour modifier les constructeurs de classes afin de prendre en charge l'injection de dépendances, ce qui rend l'utilisation des composants totalement transparente. L’approche consistant à modifier le code source avant son évaluation est à peu près équivalente à «l’instrumentation bytecode» dans les chargeurs de classes Java personnalisés. (Je mentionne Java depuis AFAIK, c’est là que les annotations ont été introduites pour la première fois).

L’utilité de cet exemple particulier est qu’au lieu de devoir écrire manuellement le code de mise en cache pour chaque méthode, vous pouvez simplement marquer une méthode comme devant être mise en cache, ce qui réduit le volume de travail répétitif et rend le code plus clair. De plus, les effets de n'importe quelle annotation peuvent être activés et désactivés au moment de l'exécution.

3
Kenney

phpDocumentor et les IDE modernes utilisent des annotations pour déterminer les types de paramètre de méthode (@param), les valeurs de retour (@return), etc.

PhpUnit Testing utilise l'annotation pour grouper des tests, définir des dépendances.

0
kta