注释在 PHP 中有什么用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3623499/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 10:28:28  来源:igfitidea点击:

How is annotation useful in PHP?

phpannotations

提问by Theodore R. Smith

How is annotation useful in PHP? and I don't mean PHPDoc generically.

注释在 PHP 中有什么用?我不是一般意义上的 PHPDoc。

I just want a real-world example or something, I guess.

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



So, according to @Max's answer: Annotations accomplish the same thing as Abstract Factories, only via one line of specialized PHPDoc. – hopeseekr 0 secs ago edit

因此,根据@Max 的回答:注释与抽象工厂完成相同的事情,仅通过一行专门的 PHPDoc。– Hopeseekr 0 秒前 编辑

回答by Max

Rob Olmosexplained it right:

罗伯·奥尔莫斯 (Rob Olmos)解释得对:

Annotations basically let you inject behavior and can promote decoupling.

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

In my words I'd say that these annotations are valuable especially in context of reflectionwhere you gather (additional) metadata about the class/method/property you are inspecting.

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

Another example instead of ORM: Dependency Injectionframeworks. The upcoming FLOW3 frameworkfor example uses docComments/annotations to identify which objects are injected in an instance created from a DI container instead of specifying it in an XML configuration file.

另一个替代 ORM 的示例:依赖注入框架。例如,即将推出的FLOW3 框架使用 docComments/annotations 来识别哪些对象被注入到从 DI 容器创建的实例中,而不是在 XML 配置文件中指定它。

Oversimplified example following:

过度简化的例子如下:

You have two classes, one Soldierclass and a Weaponclass. A Weaponinstance gets injected in a Soldierinstance. Look at the definition of the two classes:

你有两个班级,一个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();
    }
}

If you would use this class and inject all dependencies by hand, you′d do it like this:

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

$weapon = new Weapon();

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

All right, that was a lot of boilerplate code (bear with me, I am coming to explain what annotations are useful for pretty soon). What Dependency Injection frameworks can do for you is to abstract the creation such composed objects and inject all dependencies automatically, you simply do:

好吧,那是很多样板代码(请耐心等待,我很快就会解释哪些注释是有用的)。依赖注入框架可以为您做的是抽象创建这样的组合对象并自动注入所有依赖项,您只需:

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

Right, but the Containerhas to know which dependencies a Soldierclass has. So, most of the common frameworks use XML as configuration format. Example configuration:

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

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

But what FLOW3 uses instead of XML is annotations directly in the PHP code in order to define these dependencies. In FLOW3, your Soldierclass would look like this (syntax only as an example):

但是 FLOW3 使用的代替 XML 的是直接在 PHP 代码中的注释,以便定义这些依赖项。在 FLOW3 中,您的Soldier类将如下所示(语法仅作为示例):

class Soldier {
    ...

    // ---> this

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

    ...

So, no XML required to mark the dependency of Soldierto Weaponfor the DI container.

因此,不需要使用 XML 来标记DI 容器的Soldierto依赖项Weapon

FLOW 3 uses these annotations also in the context of AOP, to mark methods which should be "weaved" (means injecting behaviour before or after a method).

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



As far as I am concerned, I am not too sure about the usefulness about these annotations. I dont know if it makes things easier or worse "hiding" this kind of dependencies and setup in PHP code instead of using a separate file.

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

I worked e. g. in Spring.NET, NHibernate and with a DI framework (not FLOW3) in PHP both based on XML configuration files and cant say it was too difficult. Maintaining these setup files was ok, too.

我曾在 Spring.NET、NHibernate 和 PHP 中的 DI 框架(不是 FLOW3)中工作,这两者都基于 XML 配置文件,但不能说这太难了。维护这些设置文件也可以。

But maybe a future project with FLOW3 proves the opposite and annotations are the real way to go.

但也许未来的 FLOW3 项目证明了相反的情况,注释才是真正的方法。

回答by Rob Olmos

Exactly what is it good for?

它究竟有什么好处?

Annotations basically let you inject behavior and can promote decoupling. One example would be the Doctrine ORM. Because of the use of annotations you do not have to inherit from a Doctrine-specific class unlike the Propel ORM.

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

Hard to debug lazy loading dynamic coding?

懒加载动态编码难调试?

Unfortunately that is a side effect like most/all actions of decoupling such as design patterns, data translations, etc.

不幸的是,这是一种副作用,就像大多数/所有解耦操作(例如设计模式、数据转换等)一样。

Hmm. My brain still isn't groking it. – hopeseekr

唔。我的大脑仍然没有摸索它。– 希望者

If you didn't inherit from a Doctrine class, you'd most likely have to use some other metadata specification, like a configuration file, to specify that a particular property is the ID of the record. In that case, it would be too far removed from the syntax that the annotation (metadata) describes.

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

回答by Kenney

For completeness sake, here's a working example of both using annotations aswell as how to extend the PHP language to support them, all in a single file.

为了完整起见,这里有一个使用注释以及如何扩展 PHP 语言以支持它们的工作示例,所有这些都在一个文件中。

These are 'real' annotations, meaning, declared at the language level, and not hidden in comments. The advantage of using 'Java' style annotations like these is that they cannot be overlooked by parsers ignoring comments.

这些是“真正的”注释,意思是在语言级别声明的,而不是隐藏在注释中。使用像这样的“Java”风格的注释的优点是它们不会被忽略注释的解析器忽略。

The top part, before __halt_compiler();is the processor, extending the PHP language with a simple method annotation that caches method calls.

最上面的部分,之前__halt_compiler();是处理器,通过一个简单的方法注释扩展 PHP 语言,该注释缓存方法调用。

The class at the bottom is an example of using the @cacheannotation on a method.

底部的类是@cache在方法上使用注解的示例。

(this code is best read bottom-up).

(此代码最好自下而上阅读)。

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

Staying with the example of a DI Container (which has basically nothing whatsoever to do with annotations), the above approach can also be used to modify class constructors to take care of injecting any dependencies, which makes the use of components completely transparent. The approach of modifying the source code before it is evaluated is roughly equivalent to 'bytecode instrumentation' in custom Java Classloaders. (I mention Java since AFAIK this is where annotations were first introduced).

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

The usefulness of this particular example is that instead of manually having to write caching code for each method, you can simply mark a method as having to be cached, reducing the amount of repetitive work, and making the code clearer. Also, the effects of any annotation anywhere can be turned on and off at runtime.

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

回答by kta

phpDocumentor and modern IDEs use annotations to determine method parameter types (@param), return values (@return) and so on.

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

PhpUnit Testing use annotation to group tests, define dependencies.

PhpUnit 测试使用注解对测试进行分组,定义依赖项。