Чем полезна аннотация в PHP? - PullRequest
37 голосов
/ 02 сентября 2010

Чем полезны аннотации в PHP? и я не имею в виду PHPDoc в общем.

Мне просто нужен пример из жизни или что-то в этом роде.


Итак, согласно ответу @ Max: аннотации выполняют то же самое, что и абстрактные фабрики, только через одну строку специализированных PHPDoc. - hopeseekr 0 секунд назад редактировать

Ответы [ 4 ]

53 голосов
/ 02 сентября 2010

Роб Олмос правильно объяснил:

Аннотации в основном позволяют вводить поведение и могут способствовать разъединению.

По моим словам, я быскажем, что эти аннотации особенно полезны в контексте отражения , где вы собираете (дополнительные) метаданные о проверяемом классе / методе / свойстве.

Другой пример вместо ORM: Dependency Injection рамки.Например, в готовящейся инфраструктуре FLOW3 для определения объектов, внедряемых в экземпляр, созданный из контейнера DI, вместо указания его в файле конфигурации XML используются docComments / annotations.

Пример упрощенного примера следующий:

У вас есть два класса, один класс Soldier и класс Weapon.Экземпляр Weapon внедряется в экземпляр Soldier.Посмотрите на определение двух классов:

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

class Soldier {
    private $weapon;

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

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

Если бы вы использовали этот класс и внедрили все зависимости вручную, вы сделали бы это так:

$weapon = new Weapon();

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

Хорошо.Это было много стандартного кода (терпите меня, я собираюсь объяснить, какие аннотации полезны для довольно скоро).Платформы Dependency Injection могут сделать для вас абстрагирование создания таких составных объектов и автоматически внедрить все зависимости, вы просто делаете:

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

Правильно, но Container должен знать, какие зависимости Soldier класс имеет.Таким образом, большинство распространенных сред используют XML в качестве формата конфигурации.Пример конфигурации:

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

Но FLOW3 использует вместо XML аннотации непосредственно в коде PHP для определения этих зависимостей.В FLOW3 ваш класс Soldier будет выглядеть следующим образом (синтаксис только в качестве примера):

class Soldier {
    ...

    // ---> this

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

    ...

Таким образом, для пометки зависимости Soldier от Weapon для контейнера DI не требуется XML,

FLOW 3 использует эти аннотации также в контексте AOP , чтобы пометить методы, которые должны быть «сотканы» (означает введение поведения до или после метода).

Что касается меня, я не слишком уверен в полезности этих аннотаций.Я не знаю, облегчает ли это ситуацию или хуже, «скрывая» этот вид зависимостей и настройки в коде PHP вместо использования отдельного файла.

Я работал, например, в Spring.NET, NHibernate и с DI-инфраструктурой (не FLOW3) в PHP как на основе файлов конфигурации XML, так и не могу сказать, что это было слишком сложно.Поддерживать эти установочные файлы тоже было нормально.

Но, возможно, будущий проект с FLOW3 доказывает обратное, и аннотации - это реальный путь.

7 голосов
/ 02 сентября 2010

Точно, для чего это хорошо?

Аннотации в основном позволяют вводить поведение и могут способствовать разделению. Одним из примеров может быть Доктрина ОРМ. Из-за использования аннотаций вам не нужно наследовать от Doctrine-специфичного класса в отличие от Propel ORM.

Трудно отладить динамическую кодировку с отложенной загрузкой?

К сожалению, это побочный эффект, такой как большинство / все действия развязки, такие как шаблоны проектирования, переводы данных и т. Д.

Хм. Мой мозг все еще не работает. - hopeseekr

Если вы не наследовали от класса Doctrine, вам, скорее всего, придется использовать какую-то другую спецификацию метаданных, например файл конфигурации, чтобы указать, что конкретное свойство является идентификатором записи. В этом случае это будет слишком далеко от синтаксиса, который описывает аннотация (метаданные).

3 голосов
/ 01 мая 2014

Для полноты картины приведем рабочий пример использования аннотаций, а также того, как расширить язык PHP для их поддержки, все в одном файле.

Это «настоящие» аннотации, то есть объявленные на уровне языка и не скрытые в комментариях. Преимущество использования таких аннотаций в стиле «Java» заключается в том, что они не могут быть пропущены парсерами, игнорирующими комментарии.

Верхняя часть, перед __halt_compiler(); - это процессор, расширяющий язык PHP простой аннотацией метода, которая кэширует вызовы метода.

Класс внизу является примером использования аннотации @cache для метода.

(этот код лучше всего читать снизу вверх).

<code><?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) ) . "
"; // модифицируем токены, обрабатываем аннотации $ newtokens = process_tokens ($ tokens); // форматировать новый исходный код $ newcode = fmt ($ newtokens); echo "
". htmlentities ($ newcode). "
"; // выполнить модифицированный код Eval ($ newcode); // прекращаем обработку этого php-файла, чтобы мы могли получить данные в конце __HALT_COMPILER (); class AnnotationExample { @cache приватная функция foo ($ arg = 'default') { echo " (трудоемкий код) "; вернуть $ arg. «: 1»; } публичная функция __construct () { echo "

". get_class (). "

"; echo $ this-> foo ("A"). "
"; echo $ this-> foo ("A"). "
"; echo $ this-> foo (). "
"; echo $ this-> foo (). "
"; } } новый AnnotationExample ();

Оставаясь на примере DI-контейнера (который практически не имеет ничего общего с аннотациями), описанный выше подход также можно использовать для изменения конструкторов классов, чтобы позаботиться о внедрении любых зависимостей, что делает использование компонентов полностью прозрачным , Подход изменения исходного кода до его оценки примерно эквивалентен «инструментарию байт-кода» в пользовательских загрузчиках классов Java. (Я упоминаю Java начиная с AFAIK, именно здесь впервые были представлены аннотации).

Полезность этого конкретного примера заключается в том, что вместо того, чтобы вручную писать код кэширования для каждого метода, вы можете просто пометить метод как необходимый для кэширования, уменьшив объем повторяющихся операций и сделав код более понятным. Кроме того, эффекты любой аннотации в любом месте можно включать и выключать во время выполнения.

0 голосов
/ 09 сентября 2015

phpDocumentor и современные IDE используют аннотации для определения типов параметров метода (@param), возвращаемых значений (@return) и т. Д.

PhpUnit Testing использует аннотацию для группировки тестов, определения зависимостей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...