Completed
Push — master ( 4e8206...51bdf3 )
by Peter
02:16
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

Importance

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