2010-09-02 76 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测试使用注释来分组测试,定义依赖关系。