Completed
Push — master ( ab26f3...439ddb )
by Peter
09:17
created

Builder::buildOne()   C

Complexity

Conditions 10
Paths 38

Size

Total Lines 74
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 62.4581

Importance

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

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