Completed
Push — master ( 19c544...b7a61b )
by Peter
24:39
created

Builder::buildOne()   C

Complexity

Conditions 10
Paths 38

Size

Total Lines 74
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 10

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 74
ccs 31
cts 31
cp 1
rs 5.8102
cc 10
eloc 32
nc 38
nop 1
crap 10

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Cache\BuildOneCache;
20
use Maslosoft\Addendum\Collections\AnnotationsCollection;
21
use Maslosoft\Addendum\Collections\MatcherConfig;
22
use Maslosoft\Addendum\Helpers\CoarseChecker;
23
use Maslosoft\Addendum\Interfaces\AnnotationInterface;
24
use Maslosoft\Addendum\Matcher\AnnotationsMatcher;
25
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass;
26
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod;
27
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty;
28
use Maslosoft\Addendum\Utilities\Blacklister;
29
use Maslosoft\Addendum\Utilities\ClassChecker;
30
use Maslosoft\Addendum\Utilities\NameNormalizer;
31
use Maslosoft\Addendum\Utilities\ReflectionName;
32
use ReflectionClass;
33
use ReflectionMethod;
34
use ReflectionProperty;
35
36
/**
37
 * @Label("Annotations builder")
38
 */
39
class Builder
40
{
41
42
	/**
43
	 * Cached values of parsing
44
	 * @var string[][][]
45
	 */
46
	private static $cache = [];
47
48
	/**
49
	 * Addendum instance
50
	 * @var Addendum
51
	 */
52
	private $addendum = null;
53
54
	/**
55
	 * One entity build cache
56
	 * @var BuildOneCache
57
	 */
58
	private $buildCache = null;
59
60 46
	public function __construct(Addendum $addendum = null)
61
	{
62 46
		$this->addendum = $addendum ?: new Addendum();
63 46
		$this->buildCache = new BuildOneCache(static::class, null, $this->addendum);
64 46
	}
65
66
	/**
67
	 * Build annotations collection
68
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $targetReflection
69
	 * @return AnnotationsCollection
70
	 */
71 46
	public function build($targetReflection)
72
	{
73 46
		$annotations = [];
74
75 46
		$data = $this->buildOne($targetReflection);
76
77
		// Get annotations from current entity
78 46
		foreach ($data as $class => $parameters)
79
		{
80 44
			foreach ($parameters as $params)
81
			{
82 44
				$annotation = $this->instantiateAnnotation($class, $params, $targetReflection);
83 44
				if ($annotation !== false)
84
				{
85 44
					$annotations[$class][] = $annotation;
86
				}
87
			}
88
		}
89
90
		return new AnnotationsCollection($annotations);
91 46
	}
92
93
	private function buildOne($targetReflection)
94 46
	{
95
		// Decide where from take traits and base classes.
96
		// Either from class if it's being processed reflection class
97
		// or from declaring class if it's being processed for properties and
98
		// methods
99
		if ($targetReflection instanceof ReflectionClass)
100 46
		{
101
			$targetClass = $targetReflection;
102 46
		}
103
		else
104
		{
105
			$targetClass = $targetReflection->getDeclaringClass();
106 26
		}
107
108
		// Check if currently reflected component is cached
109
		$this->buildCache->setComponent(ReflectionName::createName($targetReflection));
110 46
		$cached = $this->buildCache->get();
111 46
112
		// Check if currently reflected component *class* is cached
113
		$this->buildCache->setComponent(ReflectionName::createName($targetClass));
114 46
		$cachedClass = $this->buildCache->get();
115 46
116
		// Cache is valid only if class cache is valid,
117
		// this is required for Conflicts and Target checks
118
		if (false !== $cached && false !== $cachedClass)
119 46
		{
120
			return $cached;
121 46
		}
122
		$traits = $targetClass->getTraits();
123 43
124
		// Get annotations from interfaces
125
		$interfacesData = [];
126 43
		foreach ($targetClass->getInterfaces() as $targetInterface)
127 43
		{
128
			// Recurse as interface might extend from other interfaces
129
			$interfaceData = $this->buildOne($targetInterface);
130 38
			if (empty($interfaceData))
131 38
			{
132
				continue;
133 36
			}
134
			$interfacesData = array_merge($interfacesData, $interfaceData);
135 2
		}
136
137
		// Get annotations from parent classes
138
		$parentData = [];
139 43
		$targetParent = $targetClass->getParentClass();
140 43
		if (!empty($targetParent))
141 43
		{
142
			// Recurse if has parent class, as it might have traits 
143
			// or interfaces too
144
			$parentData = $this->buildOne($targetParent);
145 23
		}
146
147
		// Get annotations from traits
148
		$traitsData = [];
149 43
		foreach ($traits as $trait)
150 43
		{
151
			$traitData = $this->getDataFor($targetReflection, $trait->name);
152 7
			if (false === $traitData || empty($traitData))
153 7
			{
154
				continue;
155 4
			}
156
			$traitsData = array_merge($traitsData, $traitData);
157 5
		}
158
159
		// Merge data from traits etc.
160
		// Data from class
161 43
		$data = array_merge($interfacesData, $parentData, $traitsData, $this->parse($targetReflection));
162
163 43
		$this->buildCache->setComponent(ReflectionName::createName($targetReflection));
164 43
		$this->buildCache->set($data);
165
		return $data;
166 43
	}
167
168
	/**
169
	 *
170
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $targetReflection
171
	 * @param string $name
172
	 * @return boolean|mixed
173
	 */
174
	private function getDataFor($targetReflection, $name)
175 7
	{
176
		$target = new ReflectionAnnotatedClass($name, $this->addendum);
177 7
		$annotations = null;
178 7
179
		// Try to get annotations from entity, be it method, property or trait itself
180
		switch (true)
181
		{
182
			case $targetReflection instanceof ReflectionProperty && $target->hasProperty($targetReflection->name):
183 7
				$annotations = new ReflectionAnnotatedProperty($target->name, $targetReflection->name, $this->addendum);
184 4
				break;
185 3
			case $targetReflection instanceof ReflectionMethod && $target->hasMethod($targetReflection->name):
186 6
				$annotations = new ReflectionAnnotatedMethod($target->name, $targetReflection->name, $this->addendum);
187 1
				break;
188 1
			case $targetReflection instanceof ReflectionClass:
189 1
				$annotations = $target;
190 5
				break;
191 5
		}
192
193
		// Does not have property or method
194
		if (null === $annotations)
195 7
		{
196
			return false;
197 1
		}
198
199
		// Data from target
200
		return $this->parse($annotations);
201 7
	}
202
203
	/**
204
	 * Create new instance of annotation
205
	 * @param string $class
206
	 * @param mixed[] $parameters
207
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty|bool $targetReflection
208
	 * @return boolean|object
209
	 */
210
	public function instantiateAnnotation($class, $parameters, $targetReflection = false)
211 44
	{
212
		$class = ucfirst($class) . "Annotation";
213 44
214
		// If namespaces are empty assume global namespace
215
		$fqn = $this->normalizeFqn('\\', $class);
216 44
		foreach ($this->addendum->namespaces as $ns)
217 44
		{
218
			$fqn = $this->normalizeFqn($ns, $class);
219 44
			if (Blacklister::ignores($fqn))
220 44
			{
221
				continue;
222 38
			}
223
			try
224
			{
225
				if (!ClassChecker::exists($fqn))
226 44
				{
227
					$this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]);
228 11
					Blacklister::ignore($fqn);
229 11
				}
230
				else
231
				{
232
					// Class exists, exit loop
233
					break;
234 44
				}
235
			}
236
			catch (Exception $e)
237
			{
238 11
				// Ignore class autoloading errors
239
			}
240
		}
241
		if (Blacklister::ignores($fqn))
242 44
		{
243
			return false;
244 1
		}
245
		try
246
		{
247
			// NOTE: was class_exists here, however should be safe to use ClassChecker::exists here
248
			if (!ClassChecker::exists($fqn))
249 44
			{
250
				$this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]);
251
				Blacklister::ignore($fqn);
252
				return false;
253 44
			}
254
		}
255
		catch (Exception $e)
256
		{
257
			// Ignore autoload errors and return false
258
			Blacklister::ignore($fqn);
259
			return false;
260
		}
261
		$resolvedClass = Addendum::resolveClassName($fqn);
262 44
		if ((new ReflectionClass($resolvedClass))->implementsInterface(AnnotationInterface::class) || $resolvedClass == AnnotationInterface::class)
263 44
		{
264
			return new $resolvedClass($parameters, $targetReflection);
265 44
		}
266
		return false;
267
	}
268
269
	/**
270
	 * Normalize class name and namespace to proper fully qualified name
271
	 * @param string $ns
272
	 * @param string $class
273
	 * @return string
274
	 */
275
	private function normalizeFqn($ns, $class)
276 44
	{
277
		$fqn = "\\$ns\\$class";
278 44
		NameNormalizer::normalize($fqn);
279 44
		return $fqn;
280 44
	}
281
282
	/**
283
	 * Get doc comment
284
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection
285
	 * @return mixed[]
286
	 */
287
	private function parse($reflection)
288 43
	{
289
		$key = sprintf('%s@%s', $this->addendum->getInstanceId(), ReflectionName::createName($reflection));
290 43
		if (!isset(self::$cache[$key]))
291 43
		{
292
			//
293
			if (!CoarseChecker::mightHaveAnnotations($reflection))
294 42
			{
295
				self::$cache[$key] = [];
296 23
				return self::$cache[$key];
297 23
			}
298
			$parser = new AnnotationsMatcher;
299 39
			$data = [];
300 39
			$parser->setPlugins(new MatcherConfig([
301 39
				'addendum' => $this->addendum,
302 39
				'reflection' => $reflection
303 39
			]));
304
			$parser->matches($this->getDocComment($reflection), $data);
305 39
			self::$cache[$key] = $data;
306 39
		}
307
		return self::$cache[$key];
308 40
	}
309
310
	/**
311
	 * Get doc comment
312
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection
313
	 * @return mixed[]
314
	 */
315
	protected function getDocComment($reflection)
316 39
	{
317
		return Addendum::getDocComment($reflection);
318 39
	}
319
320
	/**
321
	 * Clear local parsing cache
322
	 */
323
	public static function clearCache()
324 3
	{
325
		self::$cache = [];
326 3
	}
327 3
328
}
329