Completed
Push — master ( 1de3af...702c50 )
by Peter
05:53
created

Addendum   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 397
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 13

Test Coverage

Coverage 79.59%

Importance

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