Completed
Push — master ( e6a0d8...015d17 )
by Peter
62:09
created

Addendum::annotate()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

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