2010-09-02 58 views
32

註釋如何在PHP中有用?我一般不是指PHPDoc。註釋在PHP中如何有用?

我只是想要一個真實世界的例子或者什麼的,我想。


因此,根據@最多的回答是:註解完成同樣的事情抽象工廠,只能通過專門的PHPDoc的一條線。 - hopeseekr 0秒前編輯

+7

我想你應該更具體些。 – 2010-09-02 02:50:58

+2

...或至少鏈接到一個具體的例子。從你的答案[這裏]採取(http://stackoverflow.com/questions/3623355/php-annotation-library/3623493#3623493):http://code.google.com/p/addendum/wiki/ShortTutorialByExample – deceze 2010-09-02 02:54:18

+0

如果我問了ORM的用處是什麼,我會收到100萬份回覆。我看看註釋的例子,但它沒有陷入。究竟有什麼好處?難以調試懶加載動態編碼? – 2010-09-02 03:09:06

回答

49

Rob Olmos解釋是正確的:

註解基本上讓你注入行爲,並能促進脫鉤。

我的話我會說,這些註釋是有價值尤其是在reflection,你收集類/方法/屬性,你正在檢查(附加)的元數據環境。

另一個例子代替ORM:Dependency Injection框架。例如,即將到來的FLOW3 framework使用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(); 
    } 
} 

如果你想使用這個類,並用手注入所有的依賴關係,you'd像這樣做:

$weapon = new Weapon(); 

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

好吧,這是一個許多樣板代碼(與我一起,我將盡快解釋什麼註釋很有用)。什麼依賴注入框架能爲你做的是抽象的創作等組成的物體,並自動注入所有的依賴,你只是做:

$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標記的SoldierWeapon爲DI容器的依賴。

FLOW 3在AOP的背景下也使用這些註釋來標記應該「編織」的方法(意思是在方法之前或之後注入行爲)。


就我而言,我不太確定這些註釋的用處。我不知道它是否會讓事情變得更容易或者更糟糕,從而「隱藏」這種依賴和設置,而不是使用單獨的文件。

我工作e。 G。在Spring.NET中,NHibernate和PHP中的DI框架(而不是FLOW3)都基於XML配置文件,並且不能說這太難了。維護這些設置文件也是可以的。

但是也許未來的項目與FLOW3證明相反,註釋是真正的路要走。

+0

所以,註釋只能通過一個專門的PHPDoc完成與Abstract Factories相同的功能。 – 2010-09-02 10:55:07

+0

我選擇了這個答案,因爲它給出了註解潛在用途之一的具體測試用例。 – 2010-09-02 11:33:37

+0

通過'行爲'我們是在談論傳統的'四人幫'行爲'設計模式還是比較鬆散的東西? – Snowcrash 2016-09-30 15:26:49

6

究竟有什麼用途?

註解基本上讓你注入行爲,並可以促進解耦。 Doctrine ORM就是一個例子。由於使用了註釋,因此您不必像Propel ORM那樣從Doctrine特定的類繼承。

難以調試懶加載動態編碼?

不幸的是像解耦如設計模式,數據轉換的大多數/所有動作的副作用等

嗯。我的大腦仍然沒有跟它交流。 - hopeseekr

如果您沒有從學說類繼承,你最有可能使用一些其他的元數據規範,像一個配置文件,指定一個特定的屬性是記錄的ID。在這種情況下,它將遠離註釋(元數據)描述的語法。

+0

嗯。我的大腦仍然沒有跟它交流。 – 2010-09-02 04:01:09

+0

@hopeseekr - 請參閱我的最新編輯 – 2010-09-02 04:56:47

2

爲了完整起見,以下是使用註釋以及如何擴展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 Classloaders中的'字節碼檢測'。 (我提到Java,因爲AFAIK是第一次引入註釋的地方)。

這個特殊例子的用處在於,不必手動爲每個方法編寫緩存代碼,只需將方法標記爲必須緩存,減少重複工作量並使代碼更清晰即可。此外,任何地方的註釋效果都可以在運行時打開和關閉。

+0

這很有趣 – 2014-06-26 05:50:58

+0

你如何讓IDE支持這個? – tonix 2015-02-03 14:52:28

0

phpDocumentor和現代IDE使用註釋來確定方法參數類型(@參數),返回值(@返回)等。

PhpUnit測試使用註釋來分組測試,定義依賴關係。