Skip to content

Commit

Permalink
Merge branch 'typeValidation'
Browse files Browse the repository at this point in the history
  • Loading branch information
schmittjoh committed Aug 14, 2011
2 parents d6ef9b5 + bbaae52 commit 3655b6c
Show file tree
Hide file tree
Showing 7 changed files with 452 additions and 48 deletions.
23 changes: 23 additions & 0 deletions lib/Doctrine/Common/Annotations/AnnotationException.php
Expand Up @@ -64,4 +64,27 @@ public static function creationError($message)
{
return new self('[Creation Error] ' . $message);
}

/**
* Creates a new AnnotationException describing an type error of an attribute.
*
* @since 2.2
* @param string $attributeName
* @param string $annotationName
* @param string $context
* @param string $expected
* @param mixed $actual
* @return AnnotationException
*/
public static function typeError($attributeName, $annotationName, $context, $expected, $actual)
{
return new self(sprintf(
'[Type Error] Attribute "%s" of @%s declared on %s expects %s, but got %s.',
$attributeName,
$annotationName,
$context,
$expected,
is_object($actual) ? 'an instance of '.get_class($actual) : gettype($actual)
));
}
}
73 changes: 70 additions & 3 deletions lib/Doctrine/Common/Annotations/DocParser.php
Expand Up @@ -126,12 +126,24 @@ final class DocParser
'default_property' => null,
'has_constructor' => true,
'properties' => array(),
'attribute_types' => array(),
'targets_literal' => 'ANNOTATION_CLASS',
'targets' => Target::TARGET_ALL,
'targets' => Target::TARGET_CLASS,
'is_annotation' => true,
),
);

/**
* Hash-map for handle types declaration
*
* @var array
*/
private static $typeMap = array(
'float' => 'double',
'bool' => 'boolean',
'int' => 'integer',
);

/**
* Constructs a new DocParser.
*/
Expand Down Expand Up @@ -308,6 +320,7 @@ private function collectAnnotationMetadata($name)
{
if(self::$metadataParser == null){
self::$metadataParser = new self();
self::$metadataParser->setTarget(Target::TARGET_CLASS);
self::$metadataParser->setIgnoreNotImportedAnnotations(true);
self::$metadataParser->setImports(array(
'target' => 'Doctrine\Common\Annotations\Annotation\Target'
Expand All @@ -323,6 +336,7 @@ private function collectAnnotationMetadata($name)
'default_property' => null,
'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0,
'properties' => array(),
'property_types' => array(),
'targets_literal' => null,
'targets' => Target::TARGET_ALL,
'is_annotation' => false !== strpos($docComment, '@Annotation'),
Expand All @@ -339,9 +353,38 @@ private function collectAnnotationMetadata($name)

// if not has a constructor will inject values into public properties
if (false === $metadata['has_constructor']) {
//collect all public properties
// collect all public properties
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$metadata['properties'][$property->name] = $property->name;

// checks if the property has @var annotation
if ((false !== $propertyComment = $property->getDocComment())
&& false !== strpos($propertyComment, '@var')
&& preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) {
// literal type declaration
$value = $matches[1];

// handle internal type declaration
$type = isset(self::$typeMap[$value]) ? self::$typeMap[$value] : $value;

// handle the case if the property type is mixed
if ('mixed' !== $type) {
// Checks if the property has @var array<type> annotation
if (false !== $pos = strpos($type, '<')) {
$arrayType = substr($type, $pos+1, -1);
$type = 'array';

if (isset(self::$typeMap[$arrayType])) {
$arrayType = self::$typeMap[$arrayType];
}

$metadata['attribute_types'][$property->name]['array_type'] = $arrayType;
}

$metadata['attribute_types'][$property->name]['type'] = $type;
$metadata['attribute_types'][$property->name]['value'] = $value;
}
}
}

// choose the first property as default property
Expand Down Expand Up @@ -470,7 +513,7 @@ private function Annotation()
}

// verify that the class is really meant to be an annotation and not just any ordinary class
if (!self::$annotationMetadata[$name]['is_annotation'] === true) {
if (self::$annotationMetadata[$name]['is_annotation'] === false) {
if (isset($this->ignoredAnnotationNames[$originalName])) {
return false;
}
Expand Down Expand Up @@ -522,6 +565,30 @@ private function Annotation()
}
}

// checks if the attribute type matches
if (null !== $value && isset(self::$annotationMetadata[$name]['attribute_types'][$property])) {
$type = self::$annotationMetadata[$name]['attribute_types'][$property]['type'];

if ($type === 'array') {
// Handle the case of a single value
if (!is_array($value)) {
$value = array($value);
}

// checks if the attribute has array type declaration, such as "array<string>"
if (isset(self::$annotationMetadata[$name]['attribute_types'][$property]['array_type'])) {
$arrayType = self::$annotationMetadata[$name]['attribute_types'][$property]['array_type'];
foreach ($value as $item) {
if (gettype($item) !== $arrayType && !$item instanceof $arrayType) {
throw AnnotationException::typeError($property, $originalName, $this->context, 'either a(n) '.$arrayType.', or an array of '.$arrayType.'s', $item);
}
}
}
} elseif (gettype($value) !== $type && !$value instanceof $type) {
throw AnnotationException::typeError($property, $originalName, $this->context, 'a(n) '.self::$annotationMetadata[$name]['attribute_types'][$property]['value'], $value);
}
}

$instance->{$property} = $value;
}

Expand Down
38 changes: 37 additions & 1 deletion tests/Doctrine/Tests/Common/Annotations/AbstractReaderTest.php
Expand Up @@ -64,7 +64,7 @@ public function testAnnotations()
$this->assertEquals('hello', $classAnnot->dummyValue);
}

public function testAnnotationsWithValidMarkers()
public function testAnnotationsWithValidTargets()
{
$reader = $this->getReader();
$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithValidAnnotationTarget');
Expand All @@ -75,6 +75,18 @@ public function testAnnotationsWithValidMarkers()
$this->assertEquals(1,count($reader->getPropertyAnnotations($class->getProperty('nested'))));
}

public function testAnnotationsWithVarType()
{
$reader = $this->getReader();
$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType');

$this->assertEquals(1,count($fooAnnot = $reader->getPropertyAnnotations($class->getProperty('foo'))));
$this->assertEquals(1,count($barAnnot = $reader->getMethodAnnotations($class->getMethod('bar'))));

$this->assertInternalType('string', $fooAnnot[0]->string);
$this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', $barAnnot[0]->annotation);
}

/**
* @expectedException Doctrine\Common\Annotations\AnnotationException
* @expectedExceptionMessage [Semantical Error] Annotation @AnnotationTargetPropertyMethod is not allowed to be declared on class Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtClass. You may only use this annotation on these code elements: METHOD, PROPERTY
Expand Down Expand Up @@ -145,6 +157,30 @@ public function testClassWithAnnotationWithTargetSyntaxErrorAtMethodDocBlock()
$reader->getMethodAnnotations(new \ReflectionMethod('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithTargetSyntaxError','bar'));
}

/**
* @expectedException Doctrine\Common\Annotations\AnnotationException
* @expectedExceptionMessage [Type Error] Attribute "string" of @AnnotationWithVarType declared on property Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType::$invalidProperty expects a(n) string, but got integer.
*/
public function testClassWithPropertyInvalidVarTypeError()
{
$reader = $this->getReader();
$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType');

$reader->getPropertyAnnotations($class->getProperty('invalidProperty'));
}

/**
* @expectedException Doctrine\Common\Annotations\AnnotationException
* @expectedExceptionMessage [Type Error] Attribute "annotation" of @AnnotationWithVarType declared on method Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType::invalidMethod() expects a(n) Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll, but got an instance of Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation.
*/
public function testClassWithMethodInvalidVarTypeError()
{
$reader = $this->getReader();
$class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType');

$reader->getMethodAnnotations($class->getMethod('invalidMethod'));
}

/**
* @expectedException Doctrine\Common\Annotations\AnnotationException
* @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 18 in class Doctrine\Tests\Common\Annotations\DummyClassSyntaxError.
Expand Down

0 comments on commit 3655b6c

Please sign in to comment.