Как полезная аннотация для PHP? и я не имею в виду PHPDoc в целом.
Я просто хочу, чтобы на самом деле был реальный пример или что-то в этом роде.
Итак, в соответствии с ответом @ Max: Аннотации выполняют то же самое, что и абстрактные фабрики, только через одну строку специализированного PHPDoc. – hopeeekr 0 сек назад редактировать
Роб Олмос объяснил это правильно:
Аннотации в основном позволяют вводить поведение и могут способствовать развязыванию.
Я бы сказал, что эти аннотации ценны, особенно в контексте рефлексии, где вы собираете (дополнительные) метаданные об исследуемом классе / методе / свойстве.
Еще один пример вместо ORM: Framework Injection Dependency . Предстоящая среда FLOW3, например, использует docComments / annotations для определения того, какие объекты вводятся в экземпляр, созданный из контейнера DI, вместо указания его в файле конфигурации XML.
Следующий пример:
У вас есть два класса: один класс 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();
-$weapon = new Weapon(); $soldier = new Soldier(); $soldier->setWeapon($weapon); $soldier->fight();
Хорошо, это было много шаблонов кода (медведь со мной, я собираюсь объяснить, какие аннотации полезны довольно скоро). Что могут сделать для вас рамки зависимостей 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; } ...
Таким образом, XML не должен отмечать зависимость Soldier
от Weapon
для контейнера DI.
FLOW 3 использует эти аннотации также в контексте АОП , чтобы отметить методы, которые должны быть «сотканными» (означает поведение инъекции до или после метода).
Насколько мне известно, я не слишком уверен в полезности этих аннотаций. Я не знаю, облегчает или затрудняет «скрытие» этих зависимостей и настройку в PHP-коде вместо использования отдельного файла.
Я работал, например, в Spring.NET, NHibernate и с картой DI (не FLOW3) в PHP, как на основе файлов конфигурации XML, так и не могу сказать, что это было слишком сложно. Поддержание этих файлов настроек было в порядке.
Но, возможно, будущий проект с FLOW3 докажет обратное, а аннотации – реальный путь.
Для чего это хорошо?
Аннотации в основном позволяют вводить поведение и могут способствовать развязыванию. Одним из примеров может служить ORM доктрины. Из-за использования аннотаций вам не нужно наследовать от класса, специфичного для Doctrine, в отличие от Propel ORM.
Трудно отладить ленивую загрузку динамического кодирования?
К сожалению, это побочный эффект, такой как большинство / все действия развязки, такие как шаблоны проектирования, переводы данных и т. Д.
Хм. Мой мозг все еще не замалчивает его. – hopeeekr
Если вы не наследовали класс Doctrine, вам, скорее всего, придется использовать некоторые другие спецификации метаданных, такие как файл конфигурации, чтобы указать, что конкретное свойство является идентификатором записи. В этом случае он будет слишком удален от синтаксиса, который описывает аннотация (метаданные).
Для полноты, вот рабочий пример использования аннотаций, а также как расширить язык PHP для их поддержки, все в одном файле.
Это «настоящие» аннотации, означающие, объявленные на уровне языка, а не скрытые в комментариях. Преимущество использования аннотаций типа «Java» подобно тому, что они не могут быть упущены парсерами, игнорирующими комментарии.
Верхняя часть, перед __halt_compiler();
это процессор, расширяющий язык PHP с помощью простой аннотации метода, которая кэширует вызовы методов.
Класс внизу – пример использования аннотации @cache
для метода.
(этот код лучше всего читать снизу вверх).
<?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();
Оставаясь на примере контейнера DI (который в основном не имеет никакого отношения к аннотациям), вышеупомянутый подход также может использоваться для модификации конструкторов классов, чтобы заботиться о введении любых зависимостей, что делает использование компонентов полностью прозрачным. Подход к модификации исходного кода до его оценки примерно эквивалентен «методу байт-кода» в пользовательских загрузчиках классов Java. (Я упоминаю Java с AFAIK, где впервые были введены аннотации).
Полезность этого конкретного примера заключается в том, что вместо того, чтобы вручную писать код кэширования для каждого метода, вы можете просто отметить метод, который должен быть кэширован, уменьшая количество повторяющихся действий и делая код более понятным. Кроме того, эффекты любой аннотации в любом месте можно включать и выключать во время выполнения.
phpDocumentor и современные IDE используют аннотации для определения типов параметров метода (@param), возвращаемых значений (@return) и т. д.
Тестирование PhpUnit использует аннотацию для групповых тестов, определения зависимостей.