How is annotation useful in PHP?

前端 未结 4 2029
死守一世寂寞
死守一世寂寞 2020-12-04 17:49

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

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


So, according to @Max\'s

4条回答
  •  遥遥无期
    2020-12-04 18:36

    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.

    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.

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

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

    (this code is best read bottom-up).

     $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 "unknown instruction '{$instr[1]}'";
            }
          }
    
        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 "
    " . htmlentities( fmt($tokens) ) . "
    "; // modify the tokens, processing annotations $newtokens = process_tokens( $tokens ); // format the new source code $newcode = fmt( $newtokens ); echo "
    " . htmlentities($newcode) . "
    "; // 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 "(timeconsuming code)"; return $arg . ": 1"; } public function __construct() { echo "

    ".get_class()."

    "; echo $this->foo("A")."
    "; echo $this->foo("A")."
    "; echo $this->foo()."
    "; echo $this->foo()."
    "; } } 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).

    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.

提交回复
热议问题