Completed
Push — master ( e6039a...13e566 )
by Peter
06:01
created

Builder   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 92.52%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 11
dl 0
loc 317
ccs 99
cts 107
cp 0.9252
rs 8.96
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A build() 0 21 4
C buildOne() 0 65 10
A getRelated() 0 13 6
A getPartials() 0 28 4
C instantiateAnnotation() 0 64 11
A normalizeFqn() 0 6 1
A parse() 0 22 3
A getDocComment() 0 4 1
A clearCache() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Builder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Builder, and based on these observations, apply Extract Interface, too.

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 https://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\Annotations\TargetAnnotation;
20
use Maslosoft\Addendum\Cache\BuildOneCache;
21
use Maslosoft\Addendum\Collections\AnnotationsCollection;
22
use Maslosoft\Addendum\Collections\MatcherConfig;
23
use Maslosoft\Addendum\Helpers\CoarseChecker;
24
use Maslosoft\Addendum\Interfaces\AnnotationInterface;
25
use Maslosoft\Addendum\Matcher\AnnotationsMatcher;
26
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass;
27
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod;
28
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty;
29
use Maslosoft\Addendum\Utilities\Blacklister;
30
use Maslosoft\Addendum\Utilities\ClassChecker;
31
use Maslosoft\Addendum\Utilities\NameNormalizer;
32
use Maslosoft\Addendum\Utilities\ReflectionName;
33
use ReflectionClass;
34
use ReflectionMethod;
35
use ReflectionProperty;
36
use Reflector;
37
38
/**
39
 * @Label("Annotations builder")
40
 */
41
class Builder
42
{
43
44
	/**
45
	 * Cached values of parsing
46
	 * @var string[][][]
47
	 */
48
	private static $cache = [];
49
50
	/**
51
	 * Addendum instance
52
	 * @var Addendum
53
	 */
54
	private $addendum = null;
55
56
	/**
57
	 * One entity build cache
58
	 * @var BuildOneCache
59
	 */
60
	private $buildCache = null;
61
62 59
	public function __construct(Addendum $addendum = null)
63
	{
64 59
		$this->addendum = $addendum ?: new Addendum();
65 59
		$this->buildCache = new BuildOneCache(static::class, null, $this->addendum);
66 59
	}
67
68
	/**
69
	 * Build annotations collection
70
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $targetReflection
71
	 * @return AnnotationsCollection
72
	 */
73 59
	public function build($targetReflection)
74
	{
75 59
		$annotations = [];
76
77 59
		$data = $this->buildOne($targetReflection);
78
79
		// Get annotations from current entity
80 59
		foreach ($data as $class => $parameters)
81
		{
82 56
			foreach ($parameters as $params)
83
			{
84 56
				$annotation = $this->instantiateAnnotation($class, $params, $targetReflection);
85 56
				if ($annotation !== false)
86
				{
87 56
					$annotations[$class][] = $annotation;
88
				}
89
			}
90
		}
91
92 59
		return new AnnotationsCollection($annotations);
93
	}
94
95 59
	private function buildOne($targetReflection)
96
	{
97 59
		if (empty($targetReflection))
98
		{
99 24
			return [];
100
		}
101
		// Decide where from take traits and base classes.
102
		// Either from class if it's being processed reflection class
103
		// or from declaring class if it's being processed for properties and
104
		// methods
105 59
		if ($targetReflection instanceof ReflectionClass)
106
		{
107 59
			$targetClass = $targetReflection;
108
		}
109
		else
110
		{
111 31
			assert($targetReflection instanceof ReflectionMethod || $targetReflection instanceof ReflectionProperty);
112 31
			$targetClass = $targetReflection->getDeclaringClass();
113
		}
114
115
		// Check if currently reflected component is cached
116 59
		$this->buildCache->setComponent(ReflectionName::createName($targetReflection));
117 59
		$cached = $this->buildCache->get();
118
119
		// Check if currently reflected component *class* is cached
120 59
		$this->buildCache->setComponent(ReflectionName::createName($targetClass));
121 59
		$cachedClass = $this->buildCache->get();
122
123
		// Cache is valid only if class cache is valid,
124
		// this is required for Conflicts and Target checks
125 59
		if (false !== $cached && false !== $cachedClass)
126
		{
127 59
			return $cached;
128
		}
129
130
		// Partials annotation data
131 54
		$partialsData = [];
132
133
		// Extract annotations from all partials
134 54
		foreach ($this->getPartials($targetClass) as $partial)
135
		{
136
			// Need to recurse here, as:
137
			//
138
			// * Interface might extend from other interfaces
139
			// * Parent class, might have traits or interfaces
140
			// * Trait might have traits
141 52
			$partData = $this->buildOne($this->getRelated($targetReflection, $partial));
142
143
			// Check if data is present and proper
144 52
			if (false === $partData || empty($partData) || !is_array($partData))
145
			{
146 47
				continue;
147
			}
148
149 17
			$partialsData = array_merge_recursive($partialsData, $partData);
150
		}
151
152
		// Merge data from traits etc.
153
		// with data from class
154 54
		$data = array_merge_recursive($partialsData, $this->parse($targetReflection));
0 ignored issues
show
Documentation introduced by
$targetReflection is of type object<ReflectionMethod>...object<ReflectionClass>, but the function expects a object<Maslosoft\Addendu...ctionAnnotatedProperty>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
155
156 54
		$this->buildCache->setComponent(ReflectionName::createName($targetReflection));
157 54
		$this->buildCache->set($data);
158 54
		return $data;
159
	}
160
161
	/**
162
	 * Get reflection for related target reflection.
163
	 * Will return class, property or method reflection from related reflection,
164
	 * based on type of target reflection.
165
	 *
166
	 * Will return false target reflection id for method or property, and
167
	 * method or property does not exists on related reflection.
168
	 *
169
	 * @param Reflector $targetReflection
170
	 * @param ReflectionClass $relatedReflection
171
	 * @return ReflectionClass|ReflectionProperty|ReflectionMethod|bool
172
	 */
173 52
	private function getRelated(Reflector $targetReflection, ReflectionClass $relatedReflection)
174
	{
175
		switch (true)
176
		{
177 52
			case $targetReflection instanceof ReflectionClass:
178 44
				return $relatedReflection;
179 27
			case $targetReflection instanceof ReflectionProperty && $relatedReflection->hasProperty($targetReflection->name):
180 9
				return $relatedReflection->getProperty($targetReflection->name);
181 26
			case $targetReflection instanceof ReflectionMethod && $relatedReflection->hasMethod($targetReflection->name):
182 6
				return $relatedReflection->getMethod($targetReflection->name);
183
		}
184 24
		return false;
185
	}
186
187
	/**
188
	 * Get partials of class, this includes:
189
	 *
190
	 * * Parent class
191
	 * * Interfaces
192
	 * * Traits
193
	 *
194
	 * @param ReflectionClass $targetClass
195
	 * @return ReflectionAnnotatedClass[]
196
	 */
197 54
	private function getPartials(ReflectionClass $targetClass)
198
	{
199
		// Partial reflections
200
		/* @var ReflectionAnnotatedClass[] */
201 54
		$partials = [];
202
203
		// Collect current class interfaces
204 54
		foreach ($targetClass->getInterfaces() as $targetInterface)
205
		{
206
			/* @var $targetInterface ReflectionAnnotatedClass */
207 49
			$partials[] = $targetInterface;
208
		}
209
210
		// Collect current class parent class
211 54
		$targetParent = $targetClass->getParentClass();
212 54
		if (!empty($targetParent))
213
		{
214 28
			$partials[] = $targetParent;
215
		}
216
217
		// Collect current class traits
218 54
		foreach ($targetClass->getTraits() as $trait)
219
		{
220
			/* @var $trait ReflectionAnnotatedClass */
221 10
			$partials[] = $trait;
222
		}
223 54
		return $partials;
224
	}
225
226
	/**
227
	 * Create new instance of annotation
228
	 * @param string $class
229
	 * @param mixed[] $parameters
230
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty|bool $targetReflection
231
	 * @return boolean|object
232
	 */
233 56
	public function instantiateAnnotation($class, $parameters, $targetReflection = false)
234
	{
235 56
		$class = ucfirst($class) . "Annotation";
236
237
		// If namespaces are empty assume global namespace
238 56
		$fqn = $this->normalizeFqn('\\', $class);
239 56
		$mandatoryNs = TargetAnnotation::Ns;
240 56
		$namespaces = $this->addendum->namespaces;
241 56
		if (!in_array($mandatoryNs, $namespaces))
242
		{
243
			$namespaces[] = $mandatoryNs;
244
		}
245 56
		foreach ($namespaces as $ns)
246
		{
247 56
			$fqn = $this->normalizeFqn($ns, $class);
248 56
			if (Blacklister::ignores($fqn))
249
			{
250 47
				continue;
251
			}
252
			try
253
			{
254 56
				if (!ClassChecker::exists($fqn))
255
				{
256 16
					$this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]);
257 16
					Blacklister::ignore($fqn);
258
				}
259
				else
260
				{
261
					// Class exists, exit loop
262 56
					break;
263
				}
264
			}
265
			catch (Exception $e)
266 16
			{
267
				// Ignore class autoloading errors
268
			}
269
		}
270 56
		if (Blacklister::ignores($fqn))
271
		{
272 2
			return false;
273
		}
274
		try
275
		{
276
			// NOTE: was class_exists here, however should be safe to use ClassChecker::exists here
277 55
			if (!ClassChecker::exists($fqn))
278
			{
279
				$this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]);
280
				Blacklister::ignore($fqn);
281 55
				return false;
282
			}
283
		}
284
		catch (Exception $e)
285
		{
286
			// Ignore autoload errors and return false
287
			Blacklister::ignore($fqn);
288
			return false;
289
		}
290 55
		$resolvedClass = Addendum::resolveClassName($fqn);
291 55
		if ((new ReflectionClass($resolvedClass))->implementsInterface(AnnotationInterface::class) || $resolvedClass == AnnotationInterface::class)
292
		{
293 55
			return new $resolvedClass($parameters, $targetReflection);
294
		}
295
		return false;
296
	}
297
298
	/**
299
	 * Normalize class name and namespace to proper fully qualified name
300
	 * @param string $ns
301
	 * @param string $class
302
	 * @return string
303
	 */
304 56
	private function normalizeFqn($ns, $class)
305
	{
306 56
		$fqn = "\\$ns\\$class";
307 56
		NameNormalizer::normalize($fqn);
308 56
		return $fqn;
309
	}
310
311
	/**
312
	 * Get doc comment
313
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection
314
	 * @return array
315
	 */
316 54
	private function parse($reflection)
317
	{
318 54
		$key = sprintf('%s@%s', $this->addendum->getInstanceId(), ReflectionName::createName($reflection));
319 54
		if (!isset(self::$cache[$key]))
320
		{
321
			//
322 54
			if (!CoarseChecker::mightHaveAnnotations($reflection))
323
			{
324 28
				self::$cache[$key] = [];
325 28
				return self::$cache[$key];
326
			}
327 51
			$parser = new AnnotationsMatcher;
328 51
			$data = [];
329 51
			$parser->setPlugins(new MatcherConfig([
330 51
				'addendum' => $this->addendum,
331 51
				'reflection' => $reflection
332
			]));
333 51
			$parser->matches($this->getDocComment($reflection), $data);
334 51
			self::$cache[$key] = $data;
335
		}
336 51
		return self::$cache[$key];
337
	}
338
339
	/**
340
	 * Get doc comment
341
	 * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection
342
	 * @return mixed[]
343
	 */
344 51
	protected function getDocComment($reflection)
345
	{
346 51
		return Addendum::getDocComment($reflection);
347
	}
348
349
	/**
350
	 * Clear local parsing cache
351
	 */
352 3
	public static function clearCache()
353
	{
354 3
		self::$cache = [];
355 3
	}
356
357
}
358