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
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.