Completed
Push — master ( 49a630...afaeb1 )
by Peter
02:50 queued 48s
created

Addendum   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 396
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 13

Test Coverage

Coverage 69.39%

Importance

Changes 0
Metric Value
wmc 39
lcom 3
cbo 13
dl 0
loc 396
ccs 68
cts 98
cp 0.6939
rs 9.28
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getInstanceId() 0 4 1
A getVersion() 0 8 2
A hasAnnotations() 0 4 1
A annotate() 0 10 3
A setLogger() 0 5 1
A getLogger() 0 8 2
A addNamespaces() 0 8 2
A cacheClear() 0 8 1
A getDocComment() 0 17 2
A setRawMode() 0 4 1
B resolveClassName() 0 38 9
A getDeclaredAnnotations() 0 15 5
A addNamespace() 0 26 4
A fly() 0 8 2
A init() 0 9 2
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;
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\Collections\Meta;
23
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
24
use Maslosoft\Addendum\Interfaces\AnnotationInterface;
25
use Maslosoft\Addendum\Matcher\AnnotationsMatcher;
26
use Maslosoft\Addendum\Matcher\ClassLiteralMatcher;
27
use Maslosoft\Addendum\Matcher\StaticConstantMatcher;
28
use Maslosoft\Addendum\Plugins\Matcher\ClassErrorSilencer;
29
use Maslosoft\Addendum\Plugins\Matcher\DefencerDecorator;
30
use Maslosoft\Addendum\Plugins\Matcher\MagicConstantDecorator;
31
use Maslosoft\Addendum\Plugins\Matcher\SelfKeywordDecorator;
32
use Maslosoft\Addendum\Plugins\Matcher\UseResolverDecorator;
33
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass;
34
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod;
35
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty;
36
use Maslosoft\Addendum\Signals\NamespacesSignal;
37
use Maslosoft\Addendum\Utilities\Blacklister;
38
use Maslosoft\Addendum\Utilities\NameNormalizer;
39
use Maslosoft\EmbeDi\EmbeDi;
40
use Maslosoft\Signals\Signal;
41
use Psr\Log\LoggerAwareInterface;
42
use Psr\Log\LoggerInterface;
43
use Psr\Log\NullLogger;
44
use ReflectionClass;
45
use ReflectionException;
46
use Reflector;
47
48
class Addendum implements LoggerAwareInterface
49
{
50
51
	const DefaultInstanceId = 'addendum';
52
53
	/**
54
	 * Runtime path
55
	 * @var string
56
	 */
57
	public $runtimePath = 'runtime';
58
59
	/**
60
	 * Whether to check modification time of file to invalidate cache.
61
	 * Set this to true for development, and false for production.
62
	 * @var bool
63
	 */
64
	public $checkMTime = false;
65
66
	/**
67
	 * Namespaces to check for annotations.
68
	 * By default global and addendum namespace is included.
69
	 * @var string[]
70
	 */
71
	public $namespaces = [
72
		'\\',
73
		TargetAnnotation::Ns
74
	];
75
76
	/**
77
	 * @var bool[]
78
	 * @internal Do not use, this for performance only
79
	 */
80
	public $nameKeys = [];
81
82
	/**
83
	 * Translatable annotations
84
	 * TODO This should be moved to `maslosoft/addendum-i18n-extractor`
85
	 * @var string[]
86
	 */
87
	public $i18nAnnotations = [
88
		'Label',
89
		'Description'
90
	];
91
92
	/**
93
	 * Plugins collection
94
	 * @var AddendumPlugins|mixed[]
95
	 */
96
	public $plugins = [
97
		'matcher' => [
98
			ClassLiteralMatcher::class => [
99
				SelfKeywordDecorator::class,
100
				UseResolverDecorator::class,
101
				[
102
					'class' => ClassErrorSilencer::class,
103
					'classes' => [
104
						'PHPMD'
105
					]
106
				]
107
			],
108
			StaticConstantMatcher::class => [
109
				UseResolverDecorator::class,
110
				MagicConstantDecorator::class
111
			],
112
			AnnotationsMatcher::class => [
113
				DefencerDecorator::class
114
			]
115
		]
116
	];
117
118
	/**
119
	 * Current instance id
120
	 * @var string
121
	 */
122
	protected $instanceId = self::DefaultInstanceId;
123
124
	/**
125
	 * DI
126
	 * @var EmbeDi
127
	 */
128
	protected $di = null;
129
130
	/**
131
	 * Logger
132
	 * @var LoggerInterface
133
	 */
134
	private $loggerInstance;
135
136
	/**
137
	 * Cache for resolved annotations class names.
138
	 * Key is short annotation name.
139
	 * @var string[]
140
	 */
141
	private static $classNames = [];
142
143
	/**
144
	 * This holds information about all declared classes implementing AnnotatedInterface.
145
	 * @see AnnotatedInterface
146
	 * @var string[]
147
	 */
148
	private static $annotations = [];
149
150
	/**
151
	 * Version holder
152
	 * @var string
153
	 */
154
	private static $versionNumber = null;
155
156
	/**
157
	 * Addendum instances
158
	 * @var Addendum[]
159
	 */
160
	private static $addendums = [];
161
162
	/**
163
	 *
164
	 * @param string $instanceId
165
	 */
166 68
	public function __construct($instanceId = self::DefaultInstanceId)
167
	{
168 68
		$this->instanceId = $instanceId;
169 68
		$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...
170
171 68
		$this->di = new EmbeDi($instanceId);
172 68
		$this->di->configure($this);
173 68
	}
174
175
	/**
176
	 * Get flyweight addendum instance of `Addendum`.
177
	 * Only one instance will be created for each `$instanceId`
178
	 *
179
	 * @param string $instanceId
180
	 * @return Addendum
181
	 */
182 61
	public static function fly($instanceId = self::DefaultInstanceId)
183
	{
184 61
		if (empty(self::$addendums[$instanceId]))
185
		{
186
			self::$addendums[$instanceId] = (new Addendum($instanceId))->init();
187
		}
188 61
		return self::$addendums[$instanceId];
189
	}
190
191 58
	public function getInstanceId()
192
	{
193 58
		return $this->instanceId;
194
	}
195
196
	/**
197
	 * Get current addendum version.
198
	 *
199
	 * @return string
200
	 */
201
	public function getVersion()
202
	{
203
		if (null === self::$versionNumber)
204
		{
205
			self::$versionNumber = require __DIR__ . '/version.php';
206
		}
207
		return self::$versionNumber;
208
	}
209
210
	/**
211
	 * Initialize addendum and store configuration.
212
	 * This should be called upon first instance creation.
213
	 *
214
	 * @return Addendum
215
	 */
216
	public function init()
217
	{
218
		if (!$this->di->isStored($this))
219
		{
220
			(new Signal())->emit(new NamespacesSignal($this));
221
		}
222
		$this->di->store($this);
223
		return $this;
224
	}
225
226
	/**
227
	 * Check if class could have annotations.
228
	 * **NOTE:**
229
	 * > This does not check check if class have annotations. It only checks if it implements `AnnotatedInterface`
230
	 * @param string|object $class
231
	 * @return bool
232
	 */
233 37
	public function hasAnnotations($class)
234
	{
235 37
		return (new ReflectionClass($class))->implementsInterface(AnnotatedInterface::class);
236
	}
237
238
	/**
239
	 * Use $class name or object to annotate class
240
	 * @param string|object $class
241
	 * @return ReflectionAnnotatedMethod|ReflectionAnnotatedProperty|ReflectionAnnotatedClass
242
	 */
243 37
	public function annotate($class)
244
	{
245 37
		$className = is_object($class) ? get_class($class) : $class;
246 37
		if (!$this->hasAnnotations($class))
247
		{
248
			throw new ReflectionException(sprintf('To annotate class "%s", it must implement interface %s', $className, AnnotatedInterface::class));
249
		}
250 37
		$reflection = new ReflectionAnnotatedClass($class, $this);
251 32
		return $reflection;
252
	}
253
254
	/**
255
	 * Set logger
256
	 *
257
	 * @param LoggerInterface $logger
258
	 * @return Addendum
259
	 */
260
	public function setLogger(LoggerInterface $logger)
261
	{
262
		$this->loggerInstance = $logger;
263
		return $this;
264
	}
265
266
	/**
267
	 * Get logger
268
	 *
269
	 * @return LoggerInterface logger
270
	 */
271 52
	public function getLogger()
272
	{
273 52
		if (null === $this->loggerInstance)
274
		{
275 35
			$this->loggerInstance = new NullLogger;
276
		}
277 52
		return $this->loggerInstance;
278
	}
279
280
	/**
281
	 * Add annotations namespace.
282
	 * Every added namespace will be included in annotation name resolving for current instance.
283
	 *
284
	 * @param string $ns
285
	 * @return Addendum
286
	 */
287 4
	public function addNamespace($ns)
288
	{
289 4
		NameNormalizer::normalize($ns, false);
290 4
		if (!in_array($ns, $this->namespaces))
291
		{
292
			$before = count($this->namespaces);
293
			$this->namespaces[] = $ns;
294
			$this->namespaces = array_unique($this->namespaces);
295
			$after = count($this->namespaces);
296
			if ($after !== $before)
297
			{
298
				$this->nameKeys = array_flip($this->namespaces);
299
				Cache\NsCache::$addedNs = true;
300
			}
301
302
			$this->di->store($this, [], true);
303
304
			// Reconfigure flyweight instances if present
305
			if (!empty(self::$addendums[$this->instanceId]))
306
			{
307
				self::$addendums[$this->instanceId]->di->configure(self::$addendums[$this->instanceId]);
308
			}
309
		}
310 4
		Meta::$addNs = true;
311 4
		return $this;
312
	}
313
314
	/**
315
	 * Add many annotation namespaces.
316
	 * This is same as `addNamespace`, in that difference that many namespaces at once can be added.
317
	 *
318
	 * It accepts array of namespaces as param:
319
	 * ```php
320
	 * $nss = [
321
	 * 		'Maslosoft\Addendum\Annotations',
322
	 * 		'Maslosoft\Mangan\Annotations'
323
	 * ];
324
	 * ```
325
	 *
326
	 * @param string[] $nss
327
	 * @return Addendum
328
	 */
329 37
	public function addNamespaces($nss)
330
	{
331 37
		foreach ($nss as $ns)
332
		{
333 4
			$this->addNamespace($ns);
334
		}
335 37
		return $this;
336
	}
337
338
	/**
339
	 * Clear entire annotations cache.
340
	 */
341 3
	public static function cacheClear()
342
	{
343 3
		self::$annotations = [];
344 3
		self::$classNames = [];
345 3
		Blacklister::reset();
346 3
		Builder::clearCache();
347 3
		(new MetaCache())->clear();
348 3
	}
349
350
	/**
351
	 * @param Reflector $reflection
352
	 * @return mixed[]
353
	 */
354 51
	public static function getDocComment(Reflector $reflection)
355
	{
356
		// NOTE: Due to a nature of traits, raw doc parsing is always needed
357
		// When using reflection's method `getDocComment` it will return
358
		// doc comment like if it was pasted in code. Thus use statement
359
		// from traits would be omitted. See https://github.com/Maslosoft/Addendum/issues/24
360 51
		$docComment = new DocComment();
361 51
		if ($reflection instanceof ReflectionClass)
362
		{
363 44
			$commentString = $docComment->get($reflection)['class'];
364
		}
365
		else
366
		{
367 26
			$commentString = $docComment->get($reflection);
368
		}
369 51
		return $commentString;
370
	}
371
372
	/**
373
	 * This method has no effect
374
	 * @deprecated Since 4.0.4 this has no effect
375
	 * @param bool $enabled
376
	 */
377
	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...
378
	{
379
		// Deprecated
380
	}
381
382
	/**
383
	 * Resolve annotation class name to prefixed annotation class name
384
	 *
385
	 * @param string|bool $class
386
	 * @return string
387
	 */
388 58
	public static function resolveClassName($class)
389
	{
390 58
		if (false === $class)
391
		{
392 58
			return null;
393
		}
394 55
		if (isset(self::$classNames[$class]))
395
		{
396 42
			return self::$classNames[$class];
397
		}
398 55
		$matching = [];
399 55
		foreach (self::getDeclaredAnnotations() as $declared)
400
		{
401 55
			if ($declared == $class)
402
			{
403
				$matching[] = $declared;
404
			}
405
			else
406
			{
407 55
				$pos = strrpos($declared, "_$class");
408 55
				if ($pos !== false && ($pos + strlen($class) == strlen($declared) - 1))
409
				{
410 55
					$matching[] = $declared;
411
				}
412
			}
413
		}
414 55
		$result = null;
415 55
		switch (count($matching))
416
		{
417 55
			case 0: $result = $class;
418 55
				break;
419
			case 1: $result = $matching[0];
420
				break;
421
			default: trigger_error("Cannot resolve class name for '$class'. Possible matches: " . join(', ', $matching), E_USER_ERROR);
422
		}
423 55
		self::$classNames[$class] = $result;
424 55
		return $result;
425
	}
426
427 55
	private static function getDeclaredAnnotations()
428
	{
429 55
		if (empty(self::$annotations))
430
		{
431 55
			self::$annotations = [];
432 55
			foreach (get_declared_classes() as $class)
433
			{
434 55
				if ((new ReflectionClass($class))->implementsInterface(AnnotationInterface::class) || $class == AnnotationInterface::class)
435
				{
436 55
					self::$annotations[] = $class;
437
				}
438
			}
439
		}
440 55
		return self::$annotations;
441
	}
442
443
}
444