Passed
Pull Request — master (#34)
by Anatoly
04:23
created

AbstractAnnotationReference::getClassAnnotation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 22
ccs 0
cts 13
cp 0
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
namespace Sunrise\Http\Router\OpenApi;
13
14
/**
15
 * Import classes
16
 */
17
use Doctrine\Common\Annotations\SimpleAnnotationReader;
18
use ReflectionClass;
19
use ReflectionMethod;
20
use ReflectionProperty;
21
use Sunrise\Http\Router\Exception\InvalidAnnotationParameterException;
22
23
/**
24
 * Import functions
25
 */
26
use function hash;
27
use function sprintf;
28
use function class_exists;
29
use function method_exists;
30
use function property_exists;
31
use function get_called_class;
32
33
/**
34
 * AbstractAnnotationReference
35
 *
36
 * @link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#reference-object
37
 */
38
abstract class AbstractAnnotationReference implements ObjectInterface
39
{
40
41
    /**
42
     * Storage for referenced objects
43
     *
44
     * @var ComponentObjectInterface[]
45
     */
46
    private static $cache = [];
47
48
    /**
49
     * @Required
50
     *
51
     * @var string
52
     */
53
    public $class;
54
55
    /**
56
     * @var string
57
     */
58
    public $method;
59
60
    /**
61
     * @var string
62
     */
63
    public $property;
64
65
    /**
66
     * @var ComponentObjectInterface
67
     */
68
    private $referencedObject;
69
70
    /**
71
     * {@inheritDoc}
72
     */
73
    public function toArray() : array
74
    {
75
        // theoretically this condition will never be confirmed...
76
        if (null === $this->referencedObject) {
77
            return ['$ref' => 'undefined'];
78
        }
79
80
        return ['$ref' => sprintf(
81
            '#/components/%s/%s',
82
            $this->referencedObject->getComponentName(),
83
            $this->referencedObject->getReferenceName()
84
        )];
85
    }
86
87
    /**
88
     * The child class must return a class name that implements the `ComponentObjectInterface` interface
89
     *
90
     * @return string
91
     */
92
    abstract public function getAnnotationName() : string;
93
94
    /**
95
     * Tries to find a referenced object that implements the `ComponentObjectInterface` interface
96
     *
97
     * @param SimpleAnnotationReader $annotationReader
98
     *
99
     * @return ComponentObjectInterface
100
     *
101
     * @throws InvalidAnnotationParameterException
102
     */
103
    public function getAnnotation(SimpleAnnotationReader $annotationReader) : ComponentObjectInterface
104
    {
105
        $key = hash(
106
            'md5',
107
            $this->class .
108
            $this->method .
109
            $this->property .
110
            $this->getAnnotationName()
111
        );
112
113
        $this->referencedObject =& self::$cache[$key];
114
115
        if (isset(self::$cache[$key])) {
116
            return self::$cache[$key];
117
        }
118
119
        if (isset($this->method)) {
120
            return $this->referencedObject = $this->getMethodAnnotation($annotationReader);
121
        }
122
123
        if (isset($this->property)) {
124
            return $this->referencedObject = $this->getPropertyAnnotation($annotationReader);
125
        }
126
127
        return $this->referencedObject = $this->getClassAnnotation($annotationReader);
128
    }
129
130
    /**
131
     * Proxy to `SimpleAnnotationReader::getMethodAnnotation()` with validation
132
     *
133
     * @param SimpleAnnotationReader $annotationReader
134
     *
135
     * @return ComponentObjectInterface
136
     *
137
     * @throws InvalidAnnotationParameterException
138
     *
139
     * @see SimpleAnnotationReader::getMethodAnnotation()
140
     */
141
    private function getMethodAnnotation(SimpleAnnotationReader $annotationReader) : ComponentObjectInterface
142
    {
143
        if (!method_exists($this->class, $this->method)) {
144
            $message = 'Annotation %s refers to non-existent method %s::%s()';
145
            throw new InvalidAnnotationParameterException(
146
                sprintf($message, get_called_class(), $this->class, $this->method)
147
            );
148
        }
149
150
        $object = $annotationReader->getMethodAnnotation(
151
            new ReflectionMethod($this->class, $this->method),
152
            $this->getAnnotationName()
153
        );
154
155
        if (null === $object) {
156
            $message = 'Method %s::%s() does not contain the annotation %s';
157
            throw new InvalidAnnotationParameterException(
158
                sprintf($message, $this->class, $this->method, $this->getAnnotationName())
159
            );
160
        }
161
162
        return $object;
163
    }
164
165
    /**
166
     * Proxy to `SimpleAnnotationReader::getPropertyAnnotation()` with validation
167
     *
168
     * @param SimpleAnnotationReader $annotationReader
169
     *
170
     * @return ComponentObjectInterface
171
     *
172
     * @throws InvalidAnnotationParameterException
173
     *
174
     * @see SimpleAnnotationReader::getPropertyAnnotation()
175
     */
176
    private function getPropertyAnnotation(SimpleAnnotationReader $annotationReader) : ComponentObjectInterface
177
    {
178
        if (!property_exists($this->class, $this->property)) {
179
            $message = 'Annotation %s refers to non-existent property %s::$%s';
180
            throw new InvalidAnnotationParameterException(
181
                sprintf($message, get_called_class(), $this->class, $this->property)
182
            );
183
        }
184
185
        $object = $annotationReader->getPropertyAnnotation(
186
            new ReflectionProperty($this->class, $this->property),
187
            $this->getAnnotationName()
188
        );
189
190
        if (null === $object) {
191
            $message = 'Property %s::$%s does not contain the annotation %s';
192
            throw new InvalidAnnotationParameterException(
193
                sprintf($message, $this->class, $this->property, $this->getAnnotationName())
194
            );
195
        }
196
197
        return $object;
198
    }
199
200
    /**
201
     * Proxy to `SimpleAnnotationReader::getClassAnnotation()` with validation
202
     *
203
     * @param SimpleAnnotationReader $annotationReader
204
     *
205
     * @return ComponentObjectInterface
206
     *
207
     * @throws InvalidAnnotationParameterException
208
     *
209
     * @see SimpleAnnotationReader::getClassAnnotation()
210
     */
211
    private function getClassAnnotation(SimpleAnnotationReader $annotationReader) : ComponentObjectInterface
212
    {
213
        if (!class_exists($this->class)) {
214
            $message = 'Annotation %s refers to non-existent class %s';
215
            throw new InvalidAnnotationParameterException(
216
                sprintf($message, get_called_class(), $this->class)
217
            );
218
        }
219
220
        $object = $annotationReader->getClassAnnotation(
221
            new ReflectionClass($this->class),
222
            $this->getAnnotationName()
223
        );
224
225
        if (null === $object) {
226
            $message = 'Class %s does not contain the annotation %s';
227
            throw new InvalidAnnotationParameterException(
228
                sprintf($message, $this->class, $this->getAnnotationName())
229
            );
230
        }
231
232
        return $object;
233
    }
234
}
235