diff --git a/src/AnnotationReader.php b/src/AnnotationReader.php new file mode 100644 index 0000000..d185961 --- /dev/null +++ b/src/AnnotationReader.php @@ -0,0 +1,8 @@ +getNamespaceName(), - $class->getName() + $class->name ); $imports = new ReflectorImports($class); $instance->imports = $imports->getImports(); @@ -116,7 +122,7 @@ public static function fromReflectionMethod(ReflectionMethod $method) : self $instance = new self( Target::TARGET_METHOD, $method->getDeclaringClass()->getNamespaceName(), - "{$method->getDeclaringClass()->getName()}::{$method->getName()}()" + "{$method->getDeclaringClass()->name}::{$method->name}()" ); $imports = new ReflectorImports($method); $instance->imports = $imports->getImports(); @@ -129,7 +135,7 @@ public static function fromReflectionProperty(ReflectionProperty $property) : se $instance = new self( Target::TARGET_PROPERTY, $property->getDeclaringClass()->getNamespaceName(), - "{$property->getDeclaringClass()->getName()}::\${$property->getName()}" + "{$property->getDeclaringClass()->name}::\${$property->name}" ); $imports = new ReflectorImports($property); $instance->imports = $imports->getImports(); @@ -142,7 +148,7 @@ public static function fromReflectionFunction(ReflectionFunction $function) : se $instance = new self( Target::TARGET_FUNCTION, $function->getNamespaceName(), - "{$function->getName()}()" + "{$function->name}()" ); $imports = new ReflectorImports($function); $instance->imports = $imports->getImports(); diff --git a/src/Exception/MetaDataException.php b/src/Exception/MetaDataException.php index 5ad1148..bff0ef4 100644 --- a/src/Exception/MetaDataException.php +++ b/src/Exception/MetaDataException.php @@ -17,4 +17,9 @@ public static function forUndefinedAttribute(MetaData $metaData, string $attribu { return new self("Annotation class {$metaData->getClass()} defines no attribute {$attribute}."); } + + public static function forUnresolvableFailedAttribute(MetaData $metaData) : self + { + return new self("Could not get last failed attribute from {$metaData->getClass()}"); + } } diff --git a/src/Exception/ParserException.php b/src/Exception/ParserException.php index ffca88f..5c264f7 100644 --- a/src/Exception/ParserException.php +++ b/src/Exception/ParserException.php @@ -3,13 +3,13 @@ namespace Igni\Annotation\Exception; use Igni\Annotation\Context; +use Igni\Annotation\MetaData\Attribute; use Igni\Annotation\Token; use Igni\Exception\LogicException; -use Throwable; final class ParserException extends LogicException implements AnnotationException { - public static function forUnexpectedToken(Token $token, Context $context) : Throwable + public static function forUnexpectedToken(Token $token, Context $context) : self { $context = $context->getSymbol() ?: (string) $context; $message = "Unexpected `{$token}` in {$context} at index: {$token->getPosition()}."; @@ -17,7 +17,7 @@ public static function forUnexpectedToken(Token $token, Context $context) : Thro return new self($message); } - public static function forUnknownAnnotationClass(string $name, Context $context) : Throwable + public static function forUnknownAnnotationClass(string $name, Context $context) : self { $message = "Could not find annotation class {$name} used in {$context}." . "Please check your composer settings, or use Parser::registerNamespace."; @@ -25,7 +25,7 @@ public static function forUnknownAnnotationClass(string $name, Context $context) return new self($message); } - public static function forUsingNonAnnotationClassAsAnnotation(string $class, Context $context) : Throwable + public static function forUsingNonAnnotationClassAsAnnotation(string $class, Context $context) : self { $message = "Used {$class} as annotation - class is not marked as annotation. Used in {$context}." . "Please add `@Annotation` annotation to mark class as annotation class."; @@ -33,9 +33,19 @@ public static function forUsingNonAnnotationClassAsAnnotation(string $class, Con return new self($message); } - public static function forUndefinedConstant(Context $context, string $name) : Throwable + public static function forUndefinedConstant(Context $context, string $name) : self { $message = "Using undefined constant `{$name}` in {$context}"; return new self($message); } + + public static function forInvalidAttributeValue(Context $context, string $annotationClass, Attribute $failedAttribute) : self + { + return new self("Failed to validate `{$failedAttribute->getName()}` attribute in @{$annotationClass} used in {$context}"); + } + + public static function forInvalidTarget(Context $context, string $target, string $annotationClass) : self + { + return new self("Invalid target `{$target}`` for annotation `@{$annotationClass}` used in {$context}"); + } } diff --git a/src/MetaData/Attribute.php b/src/MetaData/Attribute.php index 005ed9d..c6b84d2 100644 --- a/src/MetaData/Attribute.php +++ b/src/MetaData/Attribute.php @@ -17,6 +17,11 @@ public function __construct(string $name, $type = 'mixed', bool $required = true $this->required = $required; } + public function getName() : string + { + return $this->name; + } + public function getType() : string { if (is_array($this->type)) { diff --git a/src/MetaData/MetaData.php b/src/MetaData/MetaData.php index 7593a8a..8aa5080 100644 --- a/src/MetaData/MetaData.php +++ b/src/MetaData/MetaData.php @@ -22,17 +22,50 @@ final class MetaData Enum::class => 1, NoValidate::class => 1, ]; + + /** + * @var Parser + */ private $parser; + + /** + * @var Context + */ private $context; - private $target = [Target::TARGET_ALL]; + + /** + * @var array + */ + private $validTargets = [Target::TARGET_ALL]; + + /** + * @var bool + */ private $validate = true; + + /** + * @var bool + */ private $hasConstructor = false; + + /** + * @var bool + */ private $isAnnotation = true; + + /** + * @var string + */ private $className; + /** * @var Attribute[] */ private $attributes = []; + + /** + * @var Attribute|null + */ private $lastFailedAttribute; public function __construct(string $class, Parser $parser = null) @@ -86,11 +119,17 @@ public function getAttribute(string $name) : Attribute public function validateTarget(string $target) : bool { - return in_array(Target::TARGET_ALL, $this->target) || in_array($target, $this->target); + return in_array(Target::TARGET_ALL, $this->validTargets) || in_array($target, $this->validTargets); } public function validateAttributes(array $data) : bool { + if (!$this->validate) { + return true; + } + + $this->lastFailedAttribute = null; + foreach ($this->attributes as $name => $attribute) { if (!isset($data[$name])) { if ($attribute->isRequired()) { @@ -108,6 +147,20 @@ public function validateAttributes(array $data) : bool return true; } + public function hasFailedAttribute() : bool + { + return null !== $this->lastFailedAttribute; + } + + public function getFailedAttribute() : Attribute + { + if (!$this->hasFailedAttribute()) { + throw MetaDataException::forUnresolvableFailedAttribute($this); + } + + return $this->lastFailedAttribute; + } + private function collect(ReflectionClass $class) : void { $this->className = $class->getName(); @@ -138,7 +191,7 @@ private function collectClassMeta(ReflectionClass $class) $this->context ); } - $this->target = $annotation->value; + $this->validTargets = $annotation->value; break; case NoValidate::class: $this->validate = false; diff --git a/src/Parser.php b/src/Parser.php index 47560e4..b51f4b1 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -136,9 +136,15 @@ private function parseAnnotation(Tokenizer $tokenizer, Context $context, $nested } $metaData = $this->getMetaData($annotationClass, $context); - if (!$metaData->hasConstructor()) { - if ($metaData->validateAttributes($arguments)) { + $target = $nested ? Target::TARGET_ANNOTATION : $context->getTarget(); + if (!$metaData->validateTarget($target)) { + throw ParserException::forInvalidTarget($context, $target, $annotationClass); + } + + if (!$metaData->hasConstructor()) { + if (!$metaData->validateAttributes($arguments)) { + throw ParserException::forInvalidAttributeValue($context, $annotationClass, $metaData->getFailedAttribute()); } $annotation = new $annotationClass(); $valueArgs = []; diff --git a/tests/MetaDataTest.php b/tests/MetaDataTest.php index 5d835e4..43f8b26 100644 --- a/tests/MetaDataTest.php +++ b/tests/MetaDataTest.php @@ -72,7 +72,7 @@ public function testValidateAttributes() : void { $meta = new MetaData(SimpleAnnotation::class); self::assertTrue($meta->validateAttributes([])); - self::assertFalse($meta->validateAttributes(['attribute' => 1])); + self::assertTrue($meta->validateAttributes(['attribute' => 1])); self::assertTrue($meta->validateAttributes(['attribute' => 'a'])); self::assertTrue($meta->validateAttributes(['attribute' => 'b'])); self::assertTrue($meta->validateAttributes(['attribute' => 'c']));