Skip to content

Commit 52c4564

Browse files
authored
Merge pull request #3 from fat-code/gh-2-annotation-validation
#2 - Add annotation validation
2 parents 14e3c9f + dad381a commit 52c4564

File tree

12 files changed

+289
-42
lines changed

12 files changed

+289
-42
lines changed

README.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
# FatCode Annotations [![Build Status](https://travis-ci.org/fat-code/annotations.svg?branch=master)](https://travis-ci.org/fat-code/annotations)
1+
# FatCode Annotations [![Build Status](https://travis-ci.org/fat-code/annotations.svg?branch=master)](https://travis-ci.org/fat-code/annotations) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fat-code/annotations/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fat-code/annotations/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/fat-code/annotations/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/fat-code/annotations/?branch=master)
22

33
## Introduction
44
Annotations is an attempt to provide meta data programing for php by extending docblock comments.
55
Syntax is compatible with latest [annotations rfc](https://wiki.php.net/rfc/annotations_v2).
66

7+
### Installation
8+
`composer install fatcode/annotations`
9+
710
### Differences from RFC
811
The following annotations are not supported for various reasons:
912
- `@Compiled` - as there is no compiling
@@ -12,3 +15,69 @@ The following annotations are not supported for various reasons:
1215
- `@Inherited` - same as `@SupressWarning`, there is no simple way to track php's inheritance tree in user-land
1316

1417
### Built-in annotations
18+
- `@Annotation()` - makes class available as annotation
19+
- `@Required()` - ensures that attribute is passed when annotation is used
20+
- `@NoValidate()` - turns off attribute validation
21+
- `@Enum(mixed ...$value)` - defines valid values for annotated property
22+
- `@Target(string ...$target)` - declares valid targets for annotation
23+
24+
25+
## Defining an annotation
26+
27+
```php
28+
<?php declare(strict_types=1);
29+
30+
use FatCode\Annotation\Target;
31+
/**
32+
* @Annotation
33+
* @Target(Target::TARGET_CLASS)
34+
*/
35+
class MyAnnotation
36+
{
37+
/**
38+
* @Required
39+
* @var string
40+
*/
41+
public $name;
42+
}
43+
```
44+
45+
The above example defines annotation that is only valid for php classes (`@Target(Target::TARGET_CLASS)`).
46+
Annotation will accept attribute `name` which is required (`@Required`) and must be of type `string`
47+
48+
## Using annotations
49+
50+
```php
51+
<?php declare(strict_types=1);
52+
53+
/**
54+
* @MyAnnotation(name="Hello World")
55+
*/
56+
class AnnotatedClass
57+
{
58+
}
59+
```
60+
61+
The above example makes usage of newly declared `MyAnnotation` annotation.
62+
63+
## Retrieving annotations
64+
65+
### Retrieving class annotations
66+
```php
67+
<?php declare(strict_types=1);
68+
use FatCode\Annotation\AnnotationReader;
69+
70+
$reader = new AnnotationReader();
71+
$annotations = $reader->readClassAnnotations(AnnotatedClass::class);
72+
var_dump($annotations);
73+
/* will output:
74+
array(1) {
75+
[0] =>
76+
class MyAnnotation#19 (1) {
77+
public $name =>
78+
string(11) "Hello World"
79+
}
80+
}
81+
*/
82+
```
83+
Example can be found [here](examples/get_class_annotations.php)

examples/get_class_annotations.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
<?php declare(strict_types=1);
2+
23
require_once '../vendor/autoload.php';
34

4-
use FatCode\Annotation\Context;
5-
use FatCode\Annotation\Parser;
6-
use IgniTest\Annotation\Fixtures\Annotations\AnnotatedClass;
5+
use FatCode\Annotation\Required;
6+
use FatCode\Annotation\Annotation;
7+
use FatCode\Annotation\Target;
8+
use FatCode\Annotation\AnnotationReader;
9+
10+
/**
11+
* @Annotation
12+
* @Target(Target::TARGET_CLASS)
13+
*/
14+
class MyAnnotation
15+
{
16+
/**
17+
* @Required
18+
* @var string
19+
*/
20+
public $name;
21+
}
22+
23+
/**
24+
* @MyAnnotation(name="Hello World")
25+
*/
26+
class AnnotatedClass
27+
{
28+
}
729

8-
$reflection = new ReflectionClass(AnnotatedClass::class);
9-
$parser = new Parser();
10-
$annotations = $parser->parse($reflection->getDocComment(), Context::fromReflectionClass($reflection));
30+
$reader = new AnnotationReader();
31+
$annotations = $reader->readClassAnnotations(AnnotatedClass::class);
1132

12-
print_r($annotations);
33+
var_dump($annotations);

src/AnnotationReader.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace FatCode\Annotation;
4+
5+
use FatCode\Annotation\Exception\AnnotationReaderException;
6+
use ReflectionClass;
7+
use ReflectionFunction;
8+
9+
/**
10+
* Reads annotation from given context.
11+
*/
12+
class AnnotationReader
13+
{
14+
private $parser;
15+
16+
public function __construct(Parser $parser = null)
17+
{
18+
$this->parser = $parser ?? new Parser();
19+
}
20+
21+
public function readClassAnnotations(string $className) : array
22+
{
23+
if (!class_exists($className)) {
24+
throw AnnotationReaderException::forUndefinedClass($className);
25+
}
26+
$reflectionClass = new ReflectionClass($className);
27+
28+
return $this->parser->parse($reflectionClass->getDocComment(), Context::fromReflectionClass($reflectionClass));
29+
}
30+
31+
public function readMethodAnnotations(string $className, string $methodName) : array
32+
{
33+
if (!class_exists($className)) {
34+
throw AnnotationReaderException::forUndefinedClass($className);
35+
}
36+
$reflectionClass = new ReflectionClass($className);
37+
if (!$reflectionClass->hasMethod($methodName)) {
38+
throw AnnotationReaderException::forUndefinedMethod($reflectionClass->name, $methodName);
39+
}
40+
$reflectionMethod = $reflectionClass->getMethod($methodName);
41+
42+
return $this->parser->parse(
43+
$reflectionMethod->getDocComment(),
44+
Context::fromReflectionMethod($reflectionMethod)
45+
);
46+
}
47+
48+
public function readPropertyAnnotations(string $className, string $propertyName) : array
49+
{
50+
if (!class_exists($className)) {
51+
throw AnnotationReaderException::forUndefinedClass($className);
52+
}
53+
$reflectionClass = new ReflectionClass($className);
54+
if (!$reflectionClass->hasProperty($propertyName)) {
55+
throw AnnotationReaderException::forUndefinedProperty($reflectionClass->name, $propertyName);
56+
}
57+
$reflectionProperty = $reflectionClass->getProperty($propertyName);
58+
59+
return $this->parser->parse(
60+
$reflectionProperty->getDocComment(),
61+
Context::fromReflectionProperty($reflectionProperty)
62+
);
63+
}
64+
65+
public function readFunctionAnnotations(string $functionName) : array
66+
{
67+
if (!function_exists($functionName)) {
68+
throw AnnotationReaderException::forUndefinedFunction($functionName);
69+
}
70+
$reflectionFunction = new ReflectionFunction($functionName);
71+
72+
return $this->parser->parse(
73+
$reflectionFunction->getDocComment(),
74+
Context::fromReflectionFunction($reflectionFunction)
75+
);
76+
}
77+
}

src/Context.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static function fromReflectionClass(ReflectionClass $class) : self
103103
$instance = new self(
104104
Target::TARGET_CLASS,
105105
$class->getNamespaceName(),
106-
$class->getName()
106+
$class->name
107107
);
108108
$imports = new ReflectorImports($class);
109109
$instance->imports = $imports->getImports();
@@ -116,7 +116,7 @@ public static function fromReflectionMethod(ReflectionMethod $method) : self
116116
$instance = new self(
117117
Target::TARGET_METHOD,
118118
$method->getDeclaringClass()->getNamespaceName(),
119-
"{$method->getDeclaringClass()->getName()}::{$method->getName()}()"
119+
"{$method->getDeclaringClass()->name}::{$method->name}()"
120120
);
121121
$imports = new ReflectorImports($method);
122122
$instance->imports = $imports->getImports();
@@ -129,7 +129,7 @@ public static function fromReflectionProperty(ReflectionProperty $property) : se
129129
$instance = new self(
130130
Target::TARGET_PROPERTY,
131131
$property->getDeclaringClass()->getNamespaceName(),
132-
"{$property->getDeclaringClass()->getName()}::\${$property->getName()}"
132+
"{$property->getDeclaringClass()->name}::\${$property->name}"
133133
);
134134
$imports = new ReflectorImports($property);
135135
$instance->imports = $imports->getImports();
@@ -142,7 +142,7 @@ public static function fromReflectionFunction(ReflectionFunction $function) : se
142142
$instance = new self(
143143
Target::TARGET_FUNCTION,
144144
$function->getNamespaceName(),
145-
"{$function->getName()}()"
145+
"{$function->name}()"
146146
);
147147
$imports = new ReflectorImports($function);
148148
$instance->imports = $imports->getImports();

src/Exception/AnnotationException.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
namespace FatCode\Annotation\Exception;
44

5-
use FatCode\Exception\Exception;
6-
7-
interface AnnotationException extends Exception {}
5+
interface AnnotationException
6+
{
7+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace FatCode\Annotation\Exception;
4+
5+
use RuntimeException;
6+
7+
final class AnnotationReaderException extends RuntimeException implements AnnotationException
8+
{
9+
public static function forUndefinedClass(string $className) : self
10+
{
11+
return new self("Class {$className} does not exists or could not be loaded, please check your composer.json.");
12+
}
13+
14+
public static function forUndefinedMethod(string $className, string $methodName) : self
15+
{
16+
return new self("Class {$className} does not define `{$methodName}` method.");
17+
}
18+
19+
public static function forUndefinedProperty(string $className, string $propertyName) : self
20+
{
21+
return new self("Class {$className} does not define `{$propertyName}`` property.");
22+
}
23+
24+
public static function forUndefinedFunction(string $functionName) : self
25+
{
26+
return new self("Function {$functionName} could not be found, please check your composer.json.");
27+
}
28+
}

src/Exception/MetaDataException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
use FatCode\Annotation\Context;
66
use FatCode\Annotation\MetaData\MetaData;
7-
use FatCode\Exception\LogicException;
7+
use RuntimeException;
88

9-
final class MetaDataException extends LogicException implements AnnotationException
9+
final class MetaDataException extends RuntimeException implements AnnotationException
1010
{
1111
public static function forInvalidTarget($target, Context $context) : self
1212
{

src/Exception/ParserException.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,39 @@
44

55
use FatCode\Annotation\Context;
66
use FatCode\Annotation\Token;
7-
use FatCode\Exception\LogicException;
8-
use Throwable;
7+
use LogicException;
98

109
final class ParserException extends LogicException implements AnnotationException
1110
{
12-
public static function forUnexpectedToken(Token $token, Context $context) : Throwable
11+
public static function forUnexpectedToken(Token $token, Context $context) : self
1312
{
1413
$context = $context->getSymbol() ?: (string) $context;
1514
$message = "Unexpected `{$token}` in {$context} at index: {$token->getPosition()}.";
16-
1715
return new self($message);
1816
}
19-
20-
public static function forUnknownAnnotationClass(string $name, Context $context) : Throwable
17+
public static function forUnknownAnnotationClass(string $name, Context $context) : self
2118
{
2219
$message = "Could not find annotation class {$name} used in {$context}." .
2320
"Please check your composer settings, or use Parser::registerNamespace.";
24-
2521
return new self($message);
2622
}
27-
28-
public static function forUsingNonAnnotationClassAsAnnotation(string $class, Context $context) : Throwable
23+
public static function forUsingNonAnnotationClassAsAnnotation(string $class, Context $context) : self
2924
{
3025
$message = "Used {$class} as annotation - class is not marked as annotation. Used in {$context}." .
3126
"Please add `@Annotation` annotation to mark class as annotation class.";
32-
3327
return new self($message);
3428
}
35-
36-
public static function forUndefinedConstant(Context $context, string $name) : Throwable
29+
public static function forUndefinedConstant(Context $context, string $name) : self
3730
{
3831
$message = "Using undefined constant `{$name}` in {$context}";
3932
return new self($message);
4033
}
34+
public static function forInvalidAttributeValue(Context $context, string $annotationClass, Attribute $failedAttribute) : self
35+
{
36+
return new self("Failed to validate `{$failedAttribute->getName()}` attribute in @{$annotationClass} used in {$context}");
37+
}
38+
public static function forInvalidTarget(Context $context, string $target, string $annotationClass) : self
39+
{
40+
return new self("Invalid target `{$target}`` for annotation `@{$annotationClass}` used in {$context}");
41+
}
4142
}

src/Exception/TokenizerException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace FatCode\Annotation\Exception;
44

5-
use FatCode\Exception\LogicException;
5+
use LogicException;
66

77
final class TokenizerException extends LogicException implements AnnotationException
88
{

0 commit comments

Comments
 (0)