Completed
Push — master ( baeee5...a6123a )
by Peter
04:33
created

Addendum::addNamespace()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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