Completed
Push — master ( eb5224...04979f )
by Peter
11:53
created

Addendum   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 375
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 11

Test Coverage

Coverage 85.56%

Importance

Changes 31
Bugs 4 Features 3
Metric Value
wmc 38
c 31
b 4
f 3
lcom 3
cbo 11
dl 0
loc 375
ccs 77
cts 90
cp 0.8556
rs 8.3999

16 Methods

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