Completed
Push — master ( cc1447...eb5224 )
by Peter
25:53
created

Builder   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 90.82%

Importance

Changes 21
Bugs 3 Features 1
Metric Value
wmc 36
c 21
b 3
f 1
lcom 1
cbo 12
dl 0
loc 254
ccs 89
cts 98
cp 0.9082
rs 8.8

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A build() 0 21 4
B buildOne() 0 57 8
C getDataFor() 0 28 7
C instantiateAnnotation() 0 58 10
A normalizeFqn() 0 6 1
A parse() 0 16 2
A getDocComment() 0 4 1
A clearCache() 0 4 1
1
<?php
2
3
/**
4
 * This software package is licensed under AGPL, Commercial license.
5
 *
6
 * @package maslosoft/addendum
7
 * @licence AGPL, Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]> (Meta container, further improvements, bugfixes)
9
 * @copyright Copyright (c) Maslosoft (Meta container, further improvements, bugfixes)
10
 * @copyright Copyright (c) Jan Suchal (Original version, builder, parser)
11
 * @link http://maslosoft.com/addendum/ - maslosoft addendum
12
 * @link https://code.google.com/p/addendum/ - original addendum project
13
 */
14
15
namespace Maslosoft\Addendum\Builder;
16
17
use Exception;
18
use Maslosoft\Addendum\Addendum;
19
use Maslosoft\Addendum\Collections\AnnotationsCollection;
20
use Maslosoft\Addendum\Collections\MatcherConfig;
21
use Maslosoft\Addendum\Interfaces\AnnotationInterface;
22
use Maslosoft\Addendum\Matcher\AnnotationsMatcher;
23
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass;
24
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod;
25
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty;
26
use Maslosoft\Addendum\Utilities\Blacklister;
27
use Maslosoft\Addendum\Utilities\ClassChecker;
28
use Maslosoft\Addendum\Utilities\NameNormalizer;
29
use Maslosoft\Addendum\Utilities\ReflectionName;
30
use ReflectionClass;
31
use ReflectionMethod;
32
use ReflectionProperty;
33
34
/**
35
 * @Label("Annotations builder")
36
 */
37
class Builder
38
{
39
40
	/**
41
	 * Cached values of parsing
42
	 * @var string[][][]
43
	 */
44
	private static $cache = [];
45
46
	/**
47
	 * Addendum instance
48
	 * @var Addendum
49
	 */
50
	private $addendum = null;
51
52 35
	public function __construct(Addendum $addendum = null)
53
	{
54 35
		$this->addendum = $addendum? : new Addendum();
55 35
	}
56
57
	/**
58
	 * Build annotations collection
59
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $targetReflection
60
	 * @return AnnotationsCollection
61
	 */
62 35
	public function build($targetReflection)
63
	{
64 35
		$annotations = [];
65 35
66
67
		$data = $this->buildOne($targetReflection);
68
69
		// Get annotations from current entity
70 35
		foreach ($data as $class => $parameters)
71 35
		{
72 35
			foreach ($parameters as $params)
73 35
			{
74
				$annotation = $this->instantiateAnnotation($class, $params, $targetReflection);
75
				if ($annotation !== false)
76 19
				{
77
					$annotations[$class][] = $annotation;
78
				}
79
			}
80 35
		}
81 35
		return new AnnotationsCollection($annotations);
82
	}
83 4
84 4
	private function buildOne($targetReflection)
85
	{
86
		// Decide where from take traits and base classes.
87 3
		// Either from class if it's being processed reflection class
88
		// or from declaring class if it's being processed for properties and
89 3
		// methods
90 3
		if ($targetReflection instanceof ReflectionClass)
91 2
		{
92 3
			$targetClass = $targetReflection;
93
		}
94
		else
95 3
		{
96 3
			$targetClass = $targetReflection->getDeclaringClass();
97 3
		}
98
		$traits = $targetClass->getTraits();
99
100
		// Get annotations from interfaces
101 3
		$interfacesData = [];
102 3
		foreach ($targetClass->getInterfaces() as $targetInterface)
103 1
		{
104
			// Recurse as interface might extend from other interfaces
105
			$interfaceData = $this->buildOne($targetInterface);
106
			if (empty($interfaceData))
107 3
			{
108 35
				continue;
109
			}
110
			$interfacesData = array_merge($interfacesData, $interfaceData);
111 35
		}
112
113
		// Get annotations from parent classes
114 35
		$parentData = [];
115
		$targetParent = $targetClass->getParentClass();
116
		if (!empty($targetParent))
117 35
		{
118 1
			// Recurse if has parent class, as it might have traits 
119 33
			// or interfaces too
120
			$parentData = $this->buildOne($targetParent);
121 33
		}
122 33
123 33
		// Get annotations from traits
124 33
		$traitsData = [];
125 33
		foreach ($traits as $trait)
126 33
		{
127 35
			$traitData = $this->getDataFor($targetReflection, $trait->name);
128 35
			if (false === $traitData || empty($traitData))
129
			{
130
				continue;
131
			}
132
			$traitsData = array_merge($traitsData, $traitData);
133
		}
134
135
		// Data from class
136
		$data = $this->parse($targetReflection);
137
138 33
		// Merge data from traits
139
		return array_merge($interfacesData, $parentData, $traitsData, $data);
140 33
	}
141
142
	private function getDataFor($targetReflection, $name)
143 33
	{
144 33
		$target = new ReflectionAnnotatedClass($name, $this->addendum);
145
		$annotations = null;
146 33
147 33
		// Try to get annotations from entity, be it method, property or trait itself
148 33
		switch (true)
149 27
		{
150
			case $targetReflection instanceof ReflectionProperty && $target->hasProperty($targetReflection->name):
151
				$annotations = new ReflectionAnnotatedProperty($target->name, $targetReflection->name, $this->addendum);
152
				break;
153 33
			case $targetReflection instanceof ReflectionMethod && $target->hasMethod($targetReflection->name):
154 33
				$annotations = new ReflectionAnnotatedMethod($target->name, $targetReflection->name, $this->addendum);
155 11
				break;
156 11
			case $targetReflection instanceof ReflectionClass:
157 11
				$annotations = $target;
158
				break;
159
		}
160
161 33
		// Does not have property or method
162
		if (null === $annotations)
163
		{
164 11
			return false;
165
		}
166
167
		// Data from traits
168 33
		return $this->parse($annotations);
169 33
	}
170 33
171 1
	/**
172
	 * Create new instance of annotation
173
	 * @param string $class
174
	 * @param mixed[] $parameters
175
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty|bool $targetReflection
176 33
	 * @return boolean|object
177 33
	 */
178
	public function instantiateAnnotation($class, $parameters, $targetReflection = false)
179
	{
180
		$class = ucfirst($class) . "Annotation";
181
182
		// If namespaces are empty assume global namespace
183 33
		$fqn = $this->normalizeFqn('\\', $class);
184
		foreach ($this->addendum->namespaces as $ns)
185
		{
186
			$fqn = $this->normalizeFqn($ns, $class);
187
			if (Blacklister::ignores($fqn))
188
			{
189 33
				continue;
190 33
			}
191 33
			try
192 33
			{
193
				if (!ClassChecker::exists($fqn))
194
				{
195
					$this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]);
196
					Blacklister::ignore($fqn);
197
				}
198
				else
199
				{
200
					// Class exists, exit loop
201
					break;
202
				}
203 33
			}
204
			catch (Exception $e)
205 33
			{
206 33
				// Ignore class autoloading errors
207 33
			}
208
		}
209
		if (Blacklister::ignores($fqn))
210
		{
211
			return false;
212
		}
213
		try
214
		{
215 35
			// NOTE: @ need to be used here or php might complain
216
			if (@!class_exists($fqn))
217 35
			{
218 35
				$this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]);
219 35
				Blacklister::ignore($fqn);
220 31
				return false;
221 31
			}
222 31
		}
223 31
		catch (Exception $e)
224
		{
225 31
			// Ignore autoload errors and return false
226 31
			Blacklister::ignore($fqn);
227 31
			return false;
228 31
		}
229 35
		$resolvedClass = Addendum::resolveClassName($fqn);
230
		if ((new ReflectionClass($resolvedClass))->implementsInterface(AnnotationInterface::class) || $resolvedClass == AnnotationInterface::class)
231
		{
232
			return new $resolvedClass($parameters, $targetReflection);
233
		}
234
		return false;
235
	}
236
237 31
	/**
238
	 * Normalize class name and namespace to proper fully qualified name
239 31
	 * @param string $ns
240
	 * @param string $class
241
	 * @return string
242
	 */
243
	private function normalizeFqn($ns, $class)
244
	{
245 3
		$fqn = "\\$ns\\$class";
246
		NameNormalizer::normalize($fqn);
247 3
		return $fqn;
248 3
	}
249
250
	/**
251
	 * Get doc comment
252
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection
253
	 * @return mixed[]
254
	 */
255
	private function parse($reflection)
256
	{
257
		$key = ReflectionName::createName($reflection);
258
		if (!isset(self::$cache[$key]))
259
		{
260
			$parser = new AnnotationsMatcher;
261
			$data = [];
262
			$parser->setPlugins(new MatcherConfig([
263
				'addendum' => $this->addendum,
264
				'reflection' => $reflection
265
			]));
266
			$parser->matches($this->getDocComment($reflection), $data);
267
			self::$cache[$key] = $data;
268
		}
269
		return self::$cache[$key];
270
	}
271
272
	/**
273
	 * Get doc comment
274
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection
275
	 * @return mixed[]
276
	 */
277
	protected function getDocComment($reflection)
278
	{
279
		return Addendum::getDocComment($reflection);
280
	}
281
282
	/**
283
	 * Clear local parsing cache
284
	 */
285
	public static function clearCache()
286
	{
287
		self::$cache = [];
288
	}
289
290
}
291