Skip to content

Commit

Permalink
[DDC-13] Provide easy customization of the annotation creation proces…
Browse files Browse the repository at this point in the history
…s. Additionally, rudimentary support for inheriting from Lexer and Parser has been added. Lastly, removed some more _ prefixes.
  • Loading branch information
romanb committed Aug 1, 2010
1 parent 65dd2cd commit 66dad3e
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 22 deletions.
29 changes: 24 additions & 5 deletions lib/Doctrine/Common/Annotations/AnnotationReader.php
Expand Up @@ -19,9 +19,10 @@

namespace Doctrine\Common\Annotations;

use \ReflectionClass,
\ReflectionMethod,
\ReflectionProperty,
use Closure,
ReflectionClass,
ReflectionMethod,
ReflectionProperty,
Doctrine\Common\Cache\Cache;

/**
Expand Down Expand Up @@ -61,10 +62,11 @@ class AnnotationReader
* Constructor. Initializes a new AnnotationReader that uses the given Cache provider.
*
* @param Cache $cache The cache provider to use. If none is provided, ArrayCache is used.
* @param Parser $parser The parser to use. If none is provided, the default parser is used.
*/
public function __construct(Cache $cache = null)
public function __construct(Cache $cache = null, Parser $parser = null)
{
$this->parser = new Parser;
$this->parser = $parser ?: new Parser;
$this->cache = $cache ?: new \Doctrine\Common\Cache\ArrayCache;
}

Expand All @@ -89,6 +91,23 @@ public function setDefaultAnnotationNamespace($defaultNamespace)
$this->parser->setDefaultAnnotationNamespace($defaultNamespace);
}

/**
* Sets the custom function to use for creating new annotations on the
* underlying parser.
*
* The function is supplied two arguments. The first argument is the name
* of the annotation and the second argument an array of values for this
* annotation. The function is assumed to return an object or NULL.
* Whenever the function returns NULL for an annotation, the implementation falls
* back to the default annotation creation process of the underlying parser.
*
* @param Closure $func
*/
public function setAnnotationCreationFunction(Closure $func)
{
$this->parser->setAnnotationCreationFunction($func);
}

/**
* Sets an alias for an annotation namespace.
*
Expand Down
7 changes: 6 additions & 1 deletion lib/Doctrine/Common/Annotations/Lexer.php
Expand Up @@ -22,6 +22,11 @@
/**
* Simple lexer for docblock annotations.
*
* This Lexer can be subclassed to customize certain aspects of the annotation
* lexing (token recognition) process. Note though that currently no special care
* is taken to maintain full backwards compatibility for subclasses. Implementation
* details of the default Lexer can change without explicit notice.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
Expand Down Expand Up @@ -70,7 +75,7 @@ protected function getNonCatchablePatterns()
/**
* @inheritdoc
*/
protected function _getType(&$value)
protected function getType(&$value)

This comment has been minimized.

Copy link
@fabpot

fabpot Aug 2, 2010

Member

You should also update Doctrine\ORM\Query\Parser, which should now have a getType() method.

This comment has been minimized.

Copy link
@romanb

romanb Aug 2, 2010

Author Contributor

Dependencies between Doctrine projects are based on tags for more stability and to allow the projects to diverge during development. The ORM still depends on Common BETA3. The ORM will be updated once the dependency is bumped.

{
$type = self::T_NONE;
$newVal = $this->getNumeric($value);
Expand Down
80 changes: 75 additions & 5 deletions lib/Doctrine/Common/Annotations/Parser.php
Expand Up @@ -19,11 +19,16 @@

namespace Doctrine\Common\Annotations;

use Doctrine\Common\ClassLoader;
use Closure, Doctrine\Common\ClassLoader;

/**
* A simple parser for docblock annotations.
*
* This Parser can be subclassed to customize certain aspects of the annotation
* parsing and/or creation process. Note though that currently no special care
* is taken to maintain full backwards compatibility for subclasses. Implementation
* details of the default Parser can change without explicit notice.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
Expand Down Expand Up @@ -54,7 +59,7 @@ class Parser
*
* @var boolean
*/
private $isNestedAnnotation = false;
protected $isNestedAnnotation = false;

/**
* Default namespace for annotations.
Expand All @@ -80,12 +85,27 @@ class Parser
*/
private $autoloadAnnotations = false;

/**
* @var Closure The custom function used to create new annotations, if any.
*/
private $annotationCreationFunction;

/**
* Constructs a new AnnotationParser.
*/
public function __construct()
public function __construct(Lexer $lexer = null)
{
$this->lexer = $lexer ?: new Lexer;
}

/**
* Gets the lexer used by this parser.
*
* @return Lexer The lexer.
*/
public function getLexer()
{
$this->lexer = new Lexer;
return $this->lexer;
}

/**
Expand All @@ -102,6 +122,25 @@ public function setAutoloadAnnotations($bool)
$this->autoloadAnnotations = $bool;
}

/**
* Sets the custom function to use for creating new annotations.
*
* The function is supplied two arguments. The first argument is the name
* of the annotation and the second argument an array of values for this
* annotation. The function is assumed to return an object or NULL.
* Whenever the function returns NULL for an annotation, the parser falls
* back to the default annotation creation process.
*
* Whenever the function returns NULL for an annotation, the implementation falls
* back to the default annotation creation process.
*
* @param Closure $func
*/
public function setAnnotationCreationFunction(Closure $func)
{
$this->annotationCreationFunction = $func;
}

/**
* Gets a flag whether to try to autoload annotation classes.
*
Expand Down Expand Up @@ -135,6 +174,16 @@ public function setAnnotationNamespaceAlias($namespace, $alias)
$this->namespaceAliases[$alias] = $namespace;
}

/**
* Gets the namespace alias mappings used by this parser.
*
* @return array The namespace alias mappings.
*/
public function getNamespaceAliases()
{
return $this->namespaceAliases;
}

/**
* Parses the given docblock string for annotations.
*
Expand Down Expand Up @@ -296,7 +345,12 @@ public function Annotation()
$this->match(Lexer::T_CLOSE_PARENTHESIS);
}

return new $name($values);
if ($this->annotationCreationFunction !== null) {
$func = $this->annotationCreationFunction;
$annot = $func($name, $values);
}

return isset($annot) ? $annot : $this->newAnnotation($name, $values);
}

/**
Expand Down Expand Up @@ -469,4 +523,20 @@ public function ArrayEntry()

return array(null, $this->Value());
}

/**
* Constructs a new annotation with a given map of values.
*
* The default construction procedure is to instantiate a new object of a class
* with the same name as the annotation. Subclasses can override this method to
* change the construction process of new annotations.
*
* @param string The name of the annotation.
* @param array The map of annotation values.
* @return mixed The new annotation with the given values.
*/
protected function newAnnotation($name, array $values)
{
return new $name($values);
}
}
10 changes: 5 additions & 5 deletions lib/Doctrine/Common/Lexer.php
Expand Up @@ -67,7 +67,7 @@ public function setInput($input)
{
$this->tokens = array();
$this->reset();
$this->_scan($input);
$this->scan($input);
}

/**
Expand Down Expand Up @@ -152,7 +152,7 @@ public function skipUntil($type)
*/
public function isA($value, $token)
{
return $this->_getType($value) === $token;
return $this->getType($value) === $token;
}

/**
Expand Down Expand Up @@ -186,7 +186,7 @@ public function glimpse()
*
* @param string $input a query string
*/
protected function _scan($input)
protected function scan($input)
{
static $regex;

Expand All @@ -200,7 +200,7 @@ protected function _scan($input)

foreach ($matches as $match) {
// Must remain before 'value' assignment since it can change content
$type = $this->_getType($match[0]);
$type = $this->getType($match[0]);

$this->tokens[] = array(
'value' => $match[0],
Expand Down Expand Up @@ -251,5 +251,5 @@ abstract protected function getNonCatchablePatterns();
* @param string $value
* @return integer
*/
abstract protected function _getType(&$value);
abstract protected function getType(&$value);
}
42 changes: 36 additions & 6 deletions tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php
Expand Up @@ -2,7 +2,7 @@

namespace Doctrine\Tests\Common\Annotations;

use Doctrine\Common\Annotations\AnnotationReader;
use ReflectionClass, Doctrine\Common\Annotations\AnnotationReader;

require_once __DIR__ . '/../../TestInit.php';

Expand All @@ -19,7 +19,7 @@ public function testAnnotations()
$reader->setAutoloadAnnotations(false);
$this->assertFalse($reader->getAutoloadAnnotations());

$class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\DummyClass');
$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\DummyClass');
$classAnnots = $reader->getClassAnnotations($class);

$annotName = 'Doctrine\Tests\Common\Annotations\DummyAnnotation';
Expand Down Expand Up @@ -72,7 +72,7 @@ public function testClassSyntaxErrorContext()
"Doctrine\Tests\Common\Annotations\DummyClassSyntaxError."
);

$class = new \ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClassSyntaxError');
$class = new ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClassSyntaxError');

$reader = $this->createAnnotationReader();
$reader->getClassAnnotations($class);
Expand All @@ -86,7 +86,7 @@ public function testMethodSyntaxErrorContext()
"method Doctrine\Tests\Common\Annotations\DummyClassMethodSyntaxError::foo()."
);

$class = new \ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClassMethodSyntaxError');
$class = new ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClassMethodSyntaxError');
$method = $class->getMethod('foo');

$reader = $this->createAnnotationReader();
Expand All @@ -101,7 +101,7 @@ public function testPropertySyntaxErrorContext()
"property Doctrine\Tests\Common\Annotations\DummyClassPropertySyntaxError::\$foo."
);

$class = new \ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClassPropertySyntaxError');
$class = new ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClassPropertySyntaxError');
$property = $class->getProperty('foo');

$reader = $this->createAnnotationReader();
Expand All @@ -114,11 +114,29 @@ public function testPropertySyntaxErrorContext()
public function testMultipleAnnotationsOnSameLine()
{
$reader = $this->createAnnotationReader();
$class = new \ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClass2');
$class = new ReflectionClass('\Doctrine\Tests\Common\Annotations\DummyClass2');
$annotations = $reader->getPropertyAnnotations($class->getProperty('id'));
$this->assertEquals(3, count($annotations));
}

public function testCustomAnnotationCreationFunction()
{
$reader = $this->createAnnotationReader();
$reader->setAnnotationCreationFunction(function($name, $values) {
if ($name == 'Doctrine\Tests\Common\Annotations\DummyAnnotation') {
$a = new CustomDummyAnnotationClass;
$a->setDummyValue($values['dummyValue']);
return $a;
}
});

$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\DummyClass');
$classAnnots = $reader->getClassAnnotations($class);
$this->assertTrue(isset($classAnnots['Doctrine\Tests\Common\Annotations\CustomDummyAnnotationClass']));
$annot = $classAnnots['Doctrine\Tests\Common\Annotations\CustomDummyAnnotationClass'];
$this->assertEquals('hello', $annot->getDummyValue());
}

public function createAnnotationReader()
{
$reader = new AnnotationReader(new \Doctrine\Common\Cache\ArrayCache);
Expand All @@ -127,6 +145,18 @@ public function createAnnotationReader()
}
}

class CustomDummyAnnotationClass {
private $dummyValue;

public function setDummyValue($value) {
$this->dummyValue = $value;
}

public function getDummyValue() {
return $this->dummyValue;
}
}

/**
* A description of this class.
*
Expand Down

0 comments on commit 66dad3e

Please sign in to comment.