注释如何在PHP中有用?

时间:2010-09-02 02:40:51

标签: php annotations

注释如何在PHP中有用?我并不是指一般的PHPDoc。

我只是想要一个真实世界的例子,我想。


所以,根据@ Max的答案:Annotations与Abstract Factories完成相同的事情,只通过一行专门的PHPDoc。 - hopeseekr 0秒前编辑

4 个答案:

答案 0 :(得分:53)

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

如果你要使用这个类并手动注入所有依赖项,你会这样做:

$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来标记DI容器的SoldierWeapon的依赖关系。

FLOW 3也在AOP的上下文中使用这些注释来标记应该“编织”的方法(意味着在方法之前或之后注入行为)。


就我而言,我不太确定这些注释的用处。我不知道它是否使事情更容易或更糟糕地“隐藏”这种依赖关系并在PHP代码中设置而不是使用单独的文件。

我工作了。 G。在Spring.NET,NHibernate和PHP中的DI框架(不是FLOW3)都基于XML配置文件而且不能说它太难了。维护这些安装文件也没问题。

但是,使用FLOW3的未来项目可能证明相反,注释是真正的方法。

答案 1 :(得分:7)

  

究竟有什么好处?

注释基本上可以让您注入行为,并可以促进解耦。一个例子是Doctrine ORM。由于使用了注释,因此与Propel ORM不同,您不必继承特定于Doctrine的类。

  

难以调试延迟加载动态编码?

不幸的是,这是像大多数/所有解耦操作一样的副作用,例如设计模式,数据翻译等。

  

嗯。我的大脑仍然没有去研究它。 - hopeseekr

如果您没有从Doctrine类继承,则很可能必须使用其他元数据规范(如配置文件)来指定特定属性是记录的ID。在这种情况下,它与注释(元数据)描述的语法相距太远。

答案 2 :(得分:3)

为了完整起见,这里有一个使用注释以及如何扩展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这是首次引入注释的地方)。

这个特定示例的用处是,不必手动为每个方法编写缓存代码,您只需将方法标记为必须缓存,减少重复工作量,并使代码更清晰。此外,任何注释的效果都可以在运行时打开和关闭。

答案 3 :(得分:0)

phpDocumentor和现代IDE使用注释来确定方法参数类型(@param),返回值(@return)等。

PhpUnit Testing使用注释对测试进行分组,定义依赖关系。