Skip to content

Commit

Permalink
adds parsing for some PHPDoc annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
schmittjoh committed May 27, 2011
1 parent 31e5f19 commit 6bad124
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 19 deletions.
15 changes: 15 additions & 0 deletions lib/Doctrine/Common/Annotations/Annotation/Author.php
@@ -0,0 +1,15 @@
<?php

namespace Doctrine\Common\Annotations\Annotation;

final class Author
{
public $name;

public function __construct(array $values)
{
if (isset($values['value'])) {
$this->name = $values['value'];
}
}
}
15 changes: 15 additions & 0 deletions lib/Doctrine/Common/Annotations/Annotation/Since.php
@@ -0,0 +1,15 @@
<?php

namespace Doctrine\Common\Annotations\Annotation;

final class Since
{
public $version;

public function __construct(array $values)
{
if (isset($values['value'])) {
$this->version = $values['value'];
}
}
}
38 changes: 38 additions & 0 deletions lib/Doctrine/Common/Annotations/Annotation/Type.php
@@ -0,0 +1,38 @@
<?php

namespace Doctrine\Common\Annotations\Annotation;

final class Type
{
const TYPE_INTEGER = 'integer';
const TYPE_DOUBLE = 'double';
const TYPE_BOOLEAN = 'boolean';
const TYPE_ARRAY = 'array';
const TYPE_UNKNOWN = 'unknown';

public $name;
public $type;

public function __construct(array $values)
{
if (isset($values['value'])) {
$name = $values['value'];
if (false !== $pos = strpos($name, ' ')) {
$name = substr($name, 0, $pos);
}
$this->name = $name;

if ('integer' === $name || 'int' === $name) {
$this->type = self::TYPE_INTEGER;
} else if ('double' === $name || 'float' === $name) {
$this->type = self::TYPE_FLOAT;
} else if ('string' === $name) {
$this->type = self::TYPE_STRING;
} else if ('array' === $name) {
$this->type = self::TYPE_ARRAY;
} else {
$this->type = self::TYPE_UNKNOWN;
}

This comment has been minimized.

Copy link
@Vrtak-CZ

Vrtak-CZ May 30, 2011

Contributor

forgotten boolean? :-)

This comment has been minimized.

Copy link
@kdambekalns

kdambekalns Oct 12, 2011

TYPE_STRING is not defined...

And what about @var SomeObject? I'd opt for splitting at whitespace and simply use type and (optional) description from that.

}
}
}
9 changes: 5 additions & 4 deletions lib/Doctrine/Common/Annotations/AnnotationReader.php
Expand Up @@ -43,6 +43,9 @@ final class AnnotationReader implements Reader
*/
private static $globalImports = array(
'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation',
'var' => 'Doctrine\Common\Annotations\Annotation\Type',
'author' => 'Doctrine\Common\Annotations\Annotation\Author',
'since' => 'Doctrine\Common\Annotations\Annotation\Since',
);

/**
Expand Down Expand Up @@ -110,9 +113,7 @@ static public function addGlobalIgnoredName($name)
private $ignoredAnnotationNames = array();

/**
* Constructor. Initializes a new AnnotationReader that uses the given Cache provider.
*
* @param DocParser $parser The parser to use. If none is provided, the default parser is used.
* Constructor. Initializes a new AnnotationReader.
*/
public function __construct()
{
Expand Down Expand Up @@ -307,8 +308,8 @@ private function collectParsingMetadata(ReflectionClass $class)

$name = $class->getName();
$this->imports[$name] = array_merge(
self::$globalImports,
$this->phpParser->parseClass($class),
self::$globalImports,
array('__NAMESPACE__' => $class->getNamespaceName())
);
$this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
Expand Down
56 changes: 44 additions & 12 deletions lib/Doctrine/Common/Annotations/DocParser.php
Expand Up @@ -274,20 +274,11 @@ private function Annotations()
continue;
}

// make sure the @ is preceded by non-catchable pattern
if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
if (!$this->isAtMarkingAnnotation()) {
$this->lexer->moveNext();
continue;
}

// make sure the @ is followed by either a namespace separator, or
// an identifier token
if ((null === $peek = $this->lexer->glimpse())
|| (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
|| $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
$this->lexer->moveNext();
continue;
}

$this->isNestedAnnotation = false;
if (false !== $annot = $this->Annotation()) {
Expand All @@ -298,8 +289,26 @@ private function Annotations()
return $annotations;
}

private function isAtMarkingAnnotation()
{
// make sure the @ is preceded by non-catchable pattern
if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
return false;
}

// make sure the @ is followed by either a namespace separator, or
// an identifier token
if ((null === $peek = $this->lexer->glimpse())
|| (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
|| $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
return false;
}

return true;
}

/**
* Annotation ::= "@" AnnotationName ["(" [Values] ")"]
* Annotation ::= "@" AnnotationName ( ["(" [Values] ")"] | { ¬ @ }* )
* AnnotationName ::= QualifiedName | SimpleName
* QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
* NameSpacePart ::= identifier | null | false | true
Expand Down Expand Up @@ -357,17 +366,40 @@ private function Annotation()
// that it is loaded

// Next will be nested
$this->isNestedAnnotation = true;

$values = array();
if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
$this->match(DocLexer::T_OPEN_PARENTHESIS);

$this->isNestedAnnotation = true;
if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
$values = $this->Values();
}

$this->match(DocLexer::T_CLOSE_PARENTHESIS);
} else if (!$this->isNestedAnnotation) {
// take everything until the next @
$value = '';
while (null !== $this->lexer->lookahead
&& (DocLexer::T_AT !== $this->lexer->lookahead['type'] || !$this->isAtMarkingAnnotation())) {
$this->lexer->moveNext();

if (DocLexer::T_STRING === $this->lexer->token['type']) {
$value .= '"'.$this->lexer->token['value'].'"';

if ($this->lexer->lookahead['position'] - 2 !== $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
$value .= ' ';
}
} else {
$value .= $this->lexer->token['value'];

if ($this->lexer->lookahead['position'] !== $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
$value .= ' ';
}
}
}

$values['value'] = '' === $value ? null : trim($value);
}

return new $name($values);
Expand Down
Expand Up @@ -16,13 +16,13 @@ public function testAnnotations()
$reader = $this->getReader();

$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\DummyClass');
$this->assertEquals(1, count($reader->getClassAnnotations($class)));
$this->assertEquals(4, count($reader->getClassAnnotations($class)));
$this->assertInstanceOf($annotName = 'Doctrine\Tests\Common\Annotations\DummyAnnotation', $annot = $reader->getClassAnnotation($class, $annotName));
$this->assertEquals("hello", $annot->dummyValue);

$field1Prop = $class->getProperty('field1');
$propAnnots = $reader->getPropertyAnnotations($field1Prop);
$this->assertEquals(1, count($propAnnots));
$this->assertEquals(2, count($propAnnots));
$this->assertInstanceOf($annotName, $annot = $reader->getPropertyAnnotation($field1Prop, $annotName));
$this->assertEquals("fieldHello", $annot->dummyValue);

Expand Down Expand Up @@ -93,7 +93,7 @@ public function testMultipleAnnotationsOnSameLine()
{
$reader = $this->getReader();
$annots = $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\DummyClass2', 'id'));
$this->assertEquals(3, count($annots));
$this->assertEquals(4, count($annots));
}

public function testNonAnnotationProblem()
Expand Down
3 changes: 3 additions & 0 deletions tests/Doctrine/Tests/Common/Annotations/CachedReaderTest.php
Expand Up @@ -2,6 +2,8 @@

namespace Doctrine\Tests\Common\Annotations;

use Doctrine\Common\Annotations\Annotation\Author;

use Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Route;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\CachedReader;
Expand Down Expand Up @@ -45,6 +47,7 @@ public function testIgnoresStaleCache()
$reader = new CachedReader(new AnnotationReader(), $cache, true);
$this->assertEquals(array(
new Route(array('value' => '/someprefix')),
new Author(array('value' => 'Johannes M. Schmitt <schmittjoh@gmail.com>'))
), $reader->getClassAnnotations(new \ReflectionClass($name)));
}

Expand Down

0 comments on commit 6bad124

Please sign in to comment.