注释如何在PHP中有用?

The*_*ith 39 php annotations

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

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


因此,根据@ Max的回答:注释与Abstract Factories完成相同的事情,只通过一行专门的PHPDoc. - hopeseekr 0秒前编辑

Max*_*Max 54

Rob Olmos解释说得对:

注释基本上可以让您注入行为,并可以促进解耦.

用我的话来说,我会说这些注释很有价值,特别是在反射的上下文中,你收集(附加)有关你正在检查的类/方法/属性的元数据.

另一个例子是代替ORM:依赖注入框架.例如,即将推出的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

$weapon = new Weapon();

$soldier = new Soldier();
$soldier->setWeapon($weapon); 
$soldier->fight();
Run Code Online (Sandbox Code Playgroud)

好吧,这是很多样板代码(请耐心等待,我即将解释注释对于很快有用).依赖注入框架可以为您做的是抽象创建这样的组合对象并自动注入所有依赖项,您只需执行以下操作:

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

是的,但Container必须知道一个Soldier类有哪些依赖项.因此,大多数常见框架都使用XML作为配置格式.配置示例:

<class name="Soldier">
    <!-- call setWeapon, inject new Weapon instance -->
    <call method="setWeapon">
        <argument name="Weapon" />
    </call>
</class>
Run Code Online (Sandbox Code Playgroud)

但是,为了定义这些依赖关系,FLOW3使用而不是XML是直接在PHP代码中的注释.在FLOW3中,您的Soldier类看起来像这样(语法仅作为示例):

class Soldier {
    ...

    // ---> this

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

    ...
Run Code Online (Sandbox Code Playgroud)

所以,不需要XML标记的依赖SoldierWeapon了DI容器.

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


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

我在Spring.NET,NHibernate以及基于XML配置文件的PHP中的DI框架(不是FLOW3)上工作过,并且说它太难了.维护这些安装文件也没问题.

但也许未来的FLOW3项目证明相反,注释是真正的方法.

  • 因此,注释只需通过一行专门的 PHPDoc 即可完成与抽象工厂相同的事情。 (2认同)

Rob*_*mos 7

究竟有什么好处?

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

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

不幸的是,这是一种副作用,如大多数/所有解耦操作,如设计模式,数据转换等.

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

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


Ken*_*ney 5

为了完整起见,这里有一个使用注释以及如何扩展 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();
Run Code Online (Sandbox Code Playgroud)

继续以 DI 容器为例(基本上与注解无关),上述方法也可用于修改类构造函数以处理注入任何依赖项,这使得组件的使用完全透明。在评估之前修改源代码的方法大致相当于自定义 Java 类加载器中的“字节码检测”。(我提到 Java,因为 AFAIK 这是首次引入注释的地方)。

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