Completed
Push — master ( 3b9fde...4aaf35 )
by Peter
38:20 queued 18:30
created

Addendum::fly()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 6
cp 0.6667
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2.1481
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;
16
17
use Maslosoft\Addendum\Annotations\TargetAnnotation;
18
use Maslosoft\Addendum\Builder\Builder;
19
use Maslosoft\Addendum\Builder\DocComment;
20
use Maslosoft\Addendum\Cache\MetaCache;
21
use Maslosoft\Addendum\Collections\AddendumPlugins;
22
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
23
use Maslosoft\Addendum\Interfaces\AnnotationInterface;
24
use Maslosoft\Addendum\Matcher\AnnotationsMatcher;
25
use Maslosoft\Addendum\Matcher\ClassLiteralMatcher;
26
use Maslosoft\Addendum\Matcher\StaticConstantMatcher;
27
use Maslosoft\Addendum\Plugins\Matcher\DefencerDecorator;
28
use Maslosoft\Addendum\Plugins\Matcher\UseResolverDecorator;
29
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass;
30
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod;
31
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty;
32
use Maslosoft\Addendum\Signals\NamespacesSignal;
33
use Maslosoft\Addendum\Utilities\Blacklister;
34
use Maslosoft\Addendum\Utilities\NameNormalizer;
35
use Maslosoft\EmbeDi\EmbeDi;
36
use Maslosoft\Signals\Signal;
37
use Psr\Log\LoggerAwareInterface;
38
use Psr\Log\LoggerInterface;
39
use Psr\Log\NullLogger;
40
use ReflectionClass;
41
use ReflectionException;
42
use Reflector;
43
44
class Addendum implements LoggerAwareInterface
45
{
46
47
	const DefaultInstanceId = 'addendum';
48
49
	/**
50
	 * Runtime path
51
	 * @var string
52
	 */
53
	public $runtimePath = 'runtime';
54
55
	/**
56
	 * Namespaces to check for annotations.
57
	 * By default global and addendum namespace is included.
58
	 * @var string[]
59
	 */
60
	public $namespaces = [
61
		'\\',
62
		TargetAnnotation::Ns
63
	];
64
65
	/**
66
	 * Translatable annotations
67
	 * TODO This should be moved to `maslosoft/addendum-i18n-extractor`
68
	 * @var string[]
69
	 */
70
	public $i18nAnnotations = [
71
		'Label',
72
		'Description'
73
	];
74
75
	/**
76
	 * Plugins collection
77
	 * @var AddendumPlugins|mixed[]
78
	 */
79
	public $plugins = [
80
		'matcher' => [
81
			ClassLiteralMatcher::class => [
82
				UseResolverDecorator::class
83
			],
84
			StaticConstantMatcher::class => [
85
				UseResolverDecorator::class
86
			],
87
			AnnotationsMatcher::class => [
88
				DefencerDecorator::class
89
			]
90
		]
91
	];
92
93
	/**
94
	 * Current instance id
95
	 * @var string
96
	 */
97
	protected $instanceId = self::DefaultInstanceId;
98
99
	/**
100
	 * DI
101
	 * @var EmbeDi
102
	 */
103
	protected $di = null;
104
105
	/**
106
	 * Logger
107
	 * @var LoggerInterface
108
	 */
109
	private $_logger;
110
111
	/**
112
	 * Cache for resolved annotations class names.
113
	 * Key is shor annotation name.
114
	 * @var string[]
115
	 */
116
	private static $_classnames = [];
117
118
	/**
119
	 * This holds information about all declared classes implementing AnnotatedInterface.
120
	 * @see AnnotatedInterface
121
	 * @var string[]
122
	 */
123
	private static $_annotations = [];
124
125
	/**
126
	 * Reflection annotated class cache
127
	 * @var ReflectionAnnotatedClass[]
128
	 */
129
	private static $_localCache = [];
130
131
	/**
132
	 * Version holder
133
	 * @var string
134
	 */
135
	private static $_version = null;
136
137
	/**
138
	 * Addendum instances
139
	 * @var Addendum[]
140
	 */
141
	private static $_a = [];
142
143
	/**
144
	 *
145
	 * @param string $instanceId
146
	 */
147 42
	public function __construct($instanceId = self::DefaultInstanceId)
148
	{
149 42
		$this->instanceId = $instanceId;
150 42
		$this->plugins = new AddendumPlugins($this->plugins);
0 ignored issues
show
Bug introduced by
It seems like $this->plugins can also be of type object<Maslosoft\Addendu...ctions\AddendumPlugins>; however, Maslosoft\Gazebo\ConfigContainer::__construct() does only seem to accept array<integer,*>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
151
152 42
		$this->di = new EmbeDi($instanceId);
153 42
		$this->di->configure($this);
154 42
	}
155
156
	/**
157
	 * Get flyweight addendum instance of `Addendum`.
158
	 * Only one instance will be created for each `$instanceId`
159
	 *
160
	 * @param string $instanceId
161
	 * @return Addendum
162
	 */
163 30
	public static function fly($instanceId = self::DefaultInstanceId)
164
	{
165 30
		if (empty(self::$_a[$instanceId]))
166 30
		{
167
			self::$_a[$instanceId] = (new Addendum($instanceId))->init();
168
		}
169 30
		return self::$_a[$instanceId];
170
	}
171
172
	/**
173
	 * Get current addendum version.
174
	 *
175
	 * @return string
176
	 */
177
	public function getVersion()
178
	{
179
		if (null === self::$_version)
180
		{
181
			self::$_version = require __DIR__ . '/version.php';
182
		}
183
		return self::$_version;
184
	}
185
186
	/**
187
	 * Initialize addendum and store configuration.
188
	 * This should be called upon first intstance creation.
189
	 *
190
	 * @return Addendum
191
	 */
192
	public function init()
193
	{
194
		if (!$this->di->isStored($this))
195
		{
196
			(new Signal())->emit(new NamespacesSignal($this));
197
		}
198
		$this->di->store($this);
199
		return $this;
200
	}
201
202
	/**
203
	 * Chech if class could have annotations.
204
	 * **NOTE:**
205
	 * > This does not check check if class have annotations. It only checks if it implements `AnnotatedInterface`
206
	 * @param string|object $class
207
	 * @return bool
208
	 */
209 28
	public function hasAnnotations($class)
210
	{
211 28
		return (new ReflectionClass($class))->implementsInterface(AnnotatedInterface::class);
212
	}
213
214
	/**
215
	 * Use $class name or object to annotate class
216
	 * @param string|object $class
217
	 * @return ReflectionAnnotatedMethod|ReflectionAnnotatedProperty|ReflectionAnnotatedClass
218
	 */
219 28
	public function annotate($class)
220
	{
221 28
		$className = is_object($class) ? get_class($class) : $class;
222 28
		if (!$this->hasAnnotations($class))
223 28
		{
224
			throw new ReflectionException(sprintf('To annotate class "%s", it must implement interface %s', $className, AnnotatedInterface::class));
225
		}
226 28
		$reflection = new ReflectionAnnotatedClass($class, $this);
227 23
		return $reflection;
228
	}
229
230
	/**
231
	 * Set logger
232
	 *
233
	 * @param LoggerInterface $logger
234
	 * @return Addendum
235
	 */
236 42
	public function setLogger(LoggerInterface $logger)
237 42
	{
238
		$this->_logger = $logger;
239
		return $this;
240
	}
241
242
	/**
243
	 * Get logger
244
	 *
245
	 * @return LoggerInterface logger
246
	 */
247
	public function getLogger()
248
	{
249
		if (null === $this->_logger)
250
		{
251
			$this->_logger = new NullLogger;
252
		}
253
		return $this->_logger;
254
	}
255
256
	/**
257
	 * Add annotations namespace.
258
	 * Every added namespace will be included in annotation name resolving for current instance.
259
	 *
260
	 * @param string $ns
261
	 * @renturn Addendum
262
	 */
263 3
	public function addNamespace($ns)
264
	{
265 3
		NameNormalizer::normalize($ns);
266 3
		if (!in_array($ns, $this->namespaces))
267 3
		{
268 2
			$this->namespaces[] = $ns;
269 2
			array_unique($this->namespaces);
270
271 2
			$this->di->store($this, [], true);
272
273
			// Reconfigure flyweight instances if present
274 2
			if (!empty(self::$_a[$this->instanceId]))
275 2
			{
276 2
				self::$_a[$this->instanceId]->di->configure(self::$_a[$this->instanceId]);
277 2
			}
278 2
		}
279 3
		return $this;
280
	}
281
282
	/**
283
	 * Add many annotaion namespaces.
284
	 * This is same as `addNamespace`, in that difference that many namespaces at once can be added.
285
	 *
286
	 * It accepts array of namespaces as param:
287
	 * ```php
288
	 * $nss = [
289
	 * 		'Maslosoft\Addendum\Annotations',
290
	 * 		'Maslosoft\Mangan\Annotations'
291
	 * ];
292
	 * ```
293
	 *
294
	 * @param string[] $nss
295
	 * @return Addendum
296
	 */
297 28
	public function addNamespaces($nss)
298
	{
299 28
		foreach ($nss as $ns)
300
		{
301 3
			$this->addNamespace($ns);
302 28
		}
303 28
		return $this;
304
	}
305
306
	/**
307
	 * Clear entire annotations cache.
308
	 */
309 3
	public static function cacheClear()
310
	{
311 3
		self::$_annotations = [];
312 3
		self::$_classnames = [];
313 3
		self::$_localCache = [];
314 3
		Blacklister::reset();
315 3
		Builder::clearCache();
316 3
		(new MetaCache())->clear();
317 3
	}
318
319
	/**
320
	 * TODO This should not be static
321
	 * @param Reflector $reflection
322
	 * @return mixed[]
323
	 */
324 35
	public static function getDocComment(Reflector $reflection)
325
	{
326
		// NOTE: Due to a nature of traits, raw doc parsing is always needed
327
		// When using reflection's method `getDocComment` it will return
328
		// doc comment like if it was pasted in code. Thus use statement
329
		// from traits would be omited. See https://github.com/Maslosoft/Addendum/issues/24
330 35
		$docComment = new DocComment();
331 35
		if ($reflection instanceof ReflectionClass)
332 35
		{
333 35
			$commentString = $docComment->get($reflection)['class'];
334 35
		}
335
		else
336
		{
337 19
			$commentString = $docComment->get($reflection);
338
		}
339 35
		return $commentString;
340
	}
341
342
	/**
343
	 * Set raw parsing mode
344
	 * @deprecated Since 4.0.4 this has no effect
345
	 * @param bool $enabled
346
	 */
347
	public static function setRawMode($enabled = true)
0 ignored issues
show
Unused Code introduced by
The parameter $enabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
348
	{
349
		// Deprecated
350
	}
351
352
	/**
353
	 * Resolve annotation class name to prefixed annotation class name
354
	 *
355
	 * @param string|bool $class
356
	 * @return string
357
	 */
358 35
	public static function resolveClassName($class)
359
	{
360 35
		if (false === $class)
361 35
		{
362 35
			return null;
363
		}
364 34
		if (isset(self::$_classnames[$class]))
365 34
		{
366 26
			return self::$_classnames[$class];
367
		}
368 34
		$matching = [];
369 34
		foreach (self::_getDeclaredAnnotations() as $declared)
370
		{
371 34
			if ($declared == $class)
372 34
			{
373
				$matching[] = $declared;
374
			}
375
			else
376
			{
377 34
				$pos = strrpos($declared, "_$class");
378 34
				if ($pos !== false && ($pos + strlen($class) == strlen($declared) - 1))
379 34
				{
380
					$matching[] = $declared;
381
				}
382
			}
383 34
		}
384 34
		$result = null;
385 34
		switch (count($matching))
386
		{
387 34
			case 0: $result = $class;
388 34
				break;
389
			case 1: $result = $matching[0];
390
				break;
391
			default: trigger_error("Cannot resolve class name for '$class'. Possible matches: " . join(', ', $matching), E_USER_ERROR);
392 34
		}
393 34
		self::$_classnames[$class] = $result;
394 34
		return $result;
395
	}
396
397 34
	private static function _getDeclaredAnnotations()
398
	{
399 34
		if (!self::$_annotations)
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$_annotations of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
400 34
		{
401 34
			self::$_annotations = [];
402 34
			foreach (get_declared_classes() as $class)
403
			{
404 34
				if ((new ReflectionClass($class))->implementsInterface(AnnotationInterface::class) || $class == AnnotationInterface::class)
405 34
				{
406 34
					self::$_annotations[] = $class;
407 34
				}
408 34
			}
409 34
		}
410 34
		return self::$_annotations;
411
	}
412
413
}
414