2010-09-02 10 views
32

W jaki sposób adnotacja jest przydatna w PHP? i nie mam na myśli ogólnie PHPDoc.W jaki sposób adnotacja jest przydatna w PHP?

Po prostu chcę przykład z prawdziwego świata lub coś podobnego.


Więc według @ odpowiedź Maxa: Adnotacje osiągnąć to samo jak streszczenie fabryki, tylko poprzez jedną linię specjalistycznych PHPDoc. - hopeseekr 0 sekund temu edytuj

+7

prawdopodobnie powinieneś być bardziej konkretny. –

+2

... lub przynajmniej link do konkretnego przykładu. Zaczerpnięte z twojej odpowiedzi [tutaj] (http://stackoverflow.com/questions/3623355/php-annotation-library/3623493#3623493): http://code.google.com/p/addendum/wiki/ShortTutorialByEprzykład – deceze

+0

Jeśli Zapytałem, do czego ORM był przydatny, dostałbym milion odpowiedzi. Patrzę na przykłady adnotacji, ale nie w to wpadło. Dokładnie to, co jest dobre? Trudno debugować leniwy ładowanie dynamicznego kodowania? –

Odpowiedz

49

Rob Olmos wyjaśnił to dobrze:

Adnotacje w zasadzie pozwalają wstrzyknąć zachowanie i może promować oddzielenie.

W moich słowach powiedziałbym, że te adnotacje są cenne zwłaszcza w kontekście reflection gdzie zbierają (dodatkowy) metadane dotyczące klasy/metody/nieruchomość jesteś kontrolnych.

Inny przykład zamiast ORM: Dependency Injection frameworków. Na przykład nadchodzący FLOW3 framework używa docComments/adnotacje do identyfikacji, które obiekty są wstrzykiwane w instancję utworzoną z kontenera DI, zamiast określać je w pliku konfiguracyjnym XML.

uproszczony przykład następujący:

Masz dwie klasy, jeden Soldier klasę i klasę Weapon. Instancja Weapon zostanie wstrzyknięta do instancji Soldier. Spojrzeć na definicję dwóch klas:

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

class Soldier { 
    private $weapon; 

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

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

Jeśli chcesz korzystać z tej klasy i wstrzyknąć wszystkie zależności od strony, you'd zrobić to tak:

$weapon = new Weapon(); 

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

Dobrze, że był dużo kodu standardowego (weź ze mną, przybędę wyjaśnić, jakie adnotacje są przydatne wkrótce).Co Zależność ramy wtryskowe może zrobić dla Ciebie jest abstrakcyjnych tworzeniu takich obiektów złożonych i wstrzyknąć wszystkie zależności automatycznie, po prostu zrobić:

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

Prawo, ale Container musi wiedzieć, które Zależności a klasa Soldier ma. Większość szkieletów używa XML jako formatu konfiguracji. Przykładowa konfiguracja:

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

Ale co FLOW3 używa zamiast adnotacji XML jest bezpośrednio w kodzie PHP w celu zdefiniowania tych zależności. W FLOW3, klasa Soldier będzie wyglądać następująco (składni tylko jako przykład):

class Soldier { 
    ... 

    // ---> this 

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

    ... 

Więc, nie wymaga XML oznaczyć zależność Soldier do Weapon dla kontenera DI.

FLOW 3 używa tych adnotacji również w kontekście AOP, do oznaczania metod, które powinny być "tkane" (tj. Zachowanie podczas wstrzykiwania przed lub po metodzie).


Jeśli chodzi o mnie, nie jestem zbyt pewien co do przydatności tych adnotacji. Nie wiem, czy to ułatwia lub utrudnia "ukrywanie" tego rodzaju zależności i konfigurację w kodzie PHP zamiast używania osobnego pliku.

Pracowałem e. sol. w Spring.NET, NHibernate i ze strukturą DI (nie FLOW3) w PHP zarówno na podstawie plików konfiguracyjnych XML i nie mogę powiedzieć, że to było zbyt trudne. Utrzymanie tych plików instalacyjnych również było w porządku.

Ale być może przyszły projekt z FLOW3 udowodni, że jest odwrotnie, a adnotacje to prawdziwa droga.

+0

Adnotacje wykonują to samo co Abstract Factories, tylko za pośrednictwem jednej linii wyspecjalizowanego PHPDoc. –

+0

Wybrałem tę odpowiedź, ponieważ dało to konkretny przypadek testowy jednego z potencjalnych zastosowań adnotacji. –

+0

Przez "zachowanie" mówimy o tradycyjnym Wzorcu "Gang of Four" 'Behavior' lub czymś luźniejszym? – Snowcrash

6

Dokładnie to, co jest dobre dla?

Adnotacje zasadniczo umożliwiają wstrzykiwanie zachowań i mogą promować oddzielenie. Jednym z przykładów może być Docrine ORM. Z powodu użycia adnotacji nie musisz dziedziczyć z klasy specyficznej dla Doctrine w przeciwieństwie do ORM Propel.

Trudne do debugowania leniwego ładowania dynamicznego kodowania?

Niestety jest to efekt uboczny jak większości/wszystkich działań oddzielenie takich jak wzorce projektowe, tłumaczenia danych itp

Hmm. Mój mózg wciąż go nie grzebał. - hopeseekr

Jeśli nie dziedziczą z klasy doktryny ci, że najprawdopodobniej trzeba użyć innej specyfikacji metadanych, jak plik konfiguracyjny, aby określić, że dana nieruchomość jest identyfikator rekordu. W takim przypadku byłoby zbyt daleko od składni opisanej w adnotacji (metadanych).

+0

Hmm. Mój mózg wciąż go nie grzebał. –

+0

@hopeseekr - zobacz moją ostatnią edycję –

2

Dla kompletności, oto przykład działania zarówno przy użyciu adnotacji, jak i jak rozszerzyć język PHP w celu ich obsługi, wszystko w jednym pliku.

Są to "prawdziwe" adnotacje, co oznacza, zadeklarowane na poziomie językowym i niewidoczne w komentarzach. Zaletą korzystania z adnotacji w stylu Java, takich jak te, jest to, że nie mogą być przeoczone przez parsery ignorujące komentarze.

Górna część, przed __halt_compiler(); jest procesorem, rozszerzającym język PHP o prostą adnotację metody, która buforuje wywołania metod.

Klasa na dole jest przykładem użycia adnotacji @cache dla metody.

(ten kod najlepiej przeczytać z dołu do góry).

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

Pozostając przy przykładzie DI Container (który ma w zasadzie nic wspólnego z adnotacjami), powyższe podejście może być również stosowany do modyfikacji konstruktorów klasy dbać o wstrzykiwanie wszelkie zależności, co sprawia, że ​​korzystanie części całkowicie przezroczyste. Podejście polegające na modyfikacji kodu źródłowego przed jego oceną jest w przybliżeniu równoznaczne z "instrumentacją kodu bajtowego" w niestandardowych modułach ładujących klasy Java. (Wspominam o Javie od czasu AFAIK, to tam po raz pierwszy wprowadzono adnotacje).

Przydatność tego szczególnego przykładu polega na tym, że zamiast ręcznie zapisywać kod pamięci podręcznej dla każdej metody, można po prostu oznaczyć metodę jako wymagającą buforowania, zmniejszając liczbę powtarzających się czynności i czyniąc kod jaśniejszym. Ponadto efekty dowolnej adnotacji w dowolnym miejscu można włączać i wyłączać w czasie wykonywania.

+0

To jest całkiem interesujące –

+0

W jaki sposób można zrobić, aby IDE to wspierały? – tonix

0

phpDocumentor i nowoczesne IDE używają adnotacji do określania typów parametrów metod (@param), wartości zwracanych (@return) i tak dalej.

Testowanie PhpUnit za pomocą adnotacji do grupowania testów, definiowania zależności.

Powiązane problemy