代码之家  ›  专栏  ›  技术社区  ›  Theodore R. Smith

注释在PHP中如何有用?

  •  37
  • Theodore R. Smith  · 技术社区  · 14 年前

    注释在PHP中如何有用?我不是泛指phpdoc。

    我想我只是想要一个现实世界的例子或者什么。


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

    4 回复  |  直到 7 年前
        1
  •  53
  •   Community CDub    7 年前

    Rob Olmos 解释正确:

    注释基本上允许您注入行为并可以促进去耦。

    用我的话说,这些注释尤其在 reflection 在那里收集(附加的)有关正在检查的类/方法/属性的元数据。

    另一个代替ORM的示例: Dependency Injection 框架。即将到来的 FLOW3 framework 例如,使用doccomments/annotations标识从DI容器创建的实例中注入的对象,而不是在XML配置文件中指定。

    过于简单的例子如下:

    你有两个班,一个 Soldier 类与A Weapon 班级。一 武器 实例被注入到 士兵 实例。看看这两个类的定义:

    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 必须知道哪些依赖项a 士兵 班级有。因此,大多数通用框架使用XML作为配置格式。配置示例:

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

    但flow3使用的不是XML,而是直接在PHP代码中定义这些依赖项的注释。在F3中,你的 士兵 类如下(语法仅作为示例):

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

    因此,不需要XML来标记 士兵 武器 对于DI容器。

    流3也在以下上下文中使用这些注释: AOP 标记应“编织”的方法(指在方法之前或之后注入行为)。


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

    我在Spring.net、nhibernate和PHP中使用了DI框架(而不是flow3),这两者都基于XML配置文件,不能说这太难了。维护这些安装文件也是可以的。

    但是,也许未来的一个带有flow3的项目证明了这一点,注释是真正的方法。

        2
  •  7
  •   Rob Olmos    14 年前

    它到底有什么用?

    注释基本上允许您注入行为并可以促进去耦。一个例子是ORM条令。由于使用注释,您不必继承与推进ORM不同的条令特定类。

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

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

    嗯。我的大脑还是没有摸索。_

    如果您不是从一个条令类继承的,那么您很可能需要使用其他一些元数据规范,比如配置文件,来指定一个特定的属性是记录的ID。在这种情况下,从注释(元数据)描述的语法中删除太多。

        3
  •  3
  •   Kenney    10 年前

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

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

        4
  •  0
  •   kta    9 年前

    phpdocumentor和modern ides使用注释来确定方法参数类型(@param)、返回值(@return)等等。

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