Completed
Push — master ( e893f6...204e13 )
by Peter
20:17
created

Addendum::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

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