Completed
Push — master ( 5a3b36...5d92a3 )
by Peter
25:03
created

Signal::emit()   D

Complexity

Conditions 10
Paths 10

Size

Total Lines 44
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 18.5184

Importance

Changes 21
Bugs 2 Features 3
Metric Value
c 21
b 2
f 3
dl 0
loc 44
ccs 14
cts 25
cp 0.56
rs 4.8196
cc 10
eloc 24
nc 10
nop 1
crap 18.5184

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This software package is licensed under `AGPL, Commercial` license[s].
5
 *
6
 * @package maslosoft/signals
7
 * @license AGPL, Commercial
8
 *
9
 * @copyright Copyright (c) Peter Maselkowski <[email protected]>
10
 *
11
 */
12
13
namespace Maslosoft\Signals;
14
15
use Maslosoft\Addendum\Utilities\ClassChecker;
16
use Maslosoft\Addendum\Utilities\NameNormalizer;
17
use Maslosoft\Cli\Shared\ConfigReader;
18
use Maslosoft\EmbeDi\EmbeDi;
19
use Maslosoft\Signals\Builder\Addendum;
20
use Maslosoft\Signals\Builder\IO\PhpFile;
21
use Maslosoft\Signals\Factories\FilterFactory;
22
use Maslosoft\Signals\Factories\SlotFactory;
23
use Maslosoft\Signals\Helpers\PostFilter;
24
use Maslosoft\Signals\Helpers\PreFilter;
25
use Maslosoft\Signals\Interfaces\BuilderIOInterface;
26
use Maslosoft\Signals\Interfaces\ExtractorInterface;
27
use Maslosoft\Signals\Interfaces\FilterInterface;
28
use Maslosoft\Signals\Interfaces\PostFilterInterface;
29
use Maslosoft\Signals\Interfaces\PreFilterInterface;
30
use Maslosoft\Signals\Interfaces\SignalAwareInterface;
31
use Psr\Log\LoggerAwareInterface;
32
use Psr\Log\LoggerInterface;
33
use Psr\Log\NullLogger;
34
use UnexpectedValueException;
35
36
/**
37
 * Main signals components
38
 *
39
 * @author Piotr
40
 * @property LoggerInterface $logger Logger, set this to log warnings, notices errors. This is shorthand for `get/setLogger`.
41
 */
42
class Signal implements LoggerAwareInterface
43
{
44
45
	const Slots = 'slots';
1 ignored issue
show
Coding Style introduced by
Constant Slots should be defined in uppercase
Loading history...
46
	const Signals = 'signals';
1 ignored issue
show
Coding Style introduced by
Constant Signals should be defined in uppercase
Loading history...
47
48
	/**
49
	 * Generated signals name.
50
	 * Name of this constant is confusing.
51
	 * @internal description
52
	 */
53
	const ConfigFilename = 'signals-definition.php';
1 ignored issue
show
Coding Style introduced by
Constant ConfigFilename should be defined in uppercase
Loading history...
54
55
	/**
56
	 * Config file name
57
	 */
58
	const ConfigName = "signals";
1 ignored issue
show
Coding Style introduced by
Constant ConfigName should be defined in uppercase
Loading history...
59
60
	/**
61
	 * Runtime path.
62
	 * This is path where config from yml will be stored.
63
	 * Path is relative to project root.
64
	 * @var string
65
	 */
66
	public $runtimePath = 'runtime';
67
68
	/**
69
	 * This aliases will be searched for SlotFor and SignalFor annotations
70
	 * TODO Autodetect based on composer autoload
71
	 * @var string[]
72
	 */
73
	public $paths = [
74
		'vendor',
75
	];
76
77
	/**
78
	 * Filters configuration.
79
	 * This filters will be applied to every emit.
80
	 * @var string[]|object[]
81
	 */
82
	public $filters = [];
83
84
	/**
85
	 * Sorters configuration.
86
	 * @var string[]|object[]
87
	 */
88
	public $sorters = [];
89
90
	/**
91
	 * Extractor configuration
92
	 * @var string|[]|object
93
	 */
94
	public $extractor = Addendum::class;
95
96
	/**
97
	 * Input/Output configuration
98
	 * @var string|[]|object
99
	 */
100
	public $io = PhpFile::class;
101
102
	/**
103
	 * Whenever component is initialized
104
	 * @var bool
105
	 */
106
	public $isInitialized = false;
107
108
	/**
109
	 * Configuration of signals and slots
110
	 * @var string[][]
111
	 */
112 14
	private static $config = [];
113
114 14
	/**
115
	 * Logger
116
	 * @var LoggerInterface
117
	 */
118
	private $logger = null;
119 14
120 14
	/**
121 14
	 *
122 14
	 * @var EmbeDi
123 14
	 */
124
	private $di = null;
125
126
	/**
127
	 * Version
128
	 * @var string
129
	 */
130
	private $version = null;
131
132
	/**
133
	 * Current filters
134
	 * @var PreFilterInterface[]|PostFilterInterface[]
135
	 */
136
	private $currentFilters = [];
137
138
	public function __construct($configName = self::ConfigName)
139
	{
140
		$this->logger = new NullLogger;
141
142
		/**
143
		 * TODO This should be made as embedi adapter, currently unsupported
144
		 */
145
		$config = new ConfigReader($configName);
146
		$this->di = EmbeDi::fly($configName);
147
		$this->di->apply($config->toArray(), $this);
148
		$this->di->configure($this);
149
	}
150
151
	/**
152
	 * Getter
153
	 * @param string $name
154
	 * @return mixed
155
	 */
156
	public function __get($name)
157
	{
158
		return $this->{'get' . ucfirst($name)}();
159
	}
160
161
	/**
162
	 * Setter
163
	 * @param string $name
164
	 * @param mixed $value
165
	 * @return mixed
166
	 */
167
	public function __set($name, $value)
168
	{
169
		return $this->{'set' . ucfirst($name)}($value);
170
	}
171
172
	/**
173
	 * Get current signals version
174
	 *
175
	 * @codeCoverageIgnore
176
	 * @return string
177
	 */
178 14
	public function getVersion()
179
	{
180 12
		if (null === $this->version)
181 12
		{
182
			$this->version = require __DIR__ . '/version.php';
183
		}
184 12
		return $this->version;
185 12
	}
186 12
187 12
	public function init()
188
	{
189
		if (!$this->isInitialized)
190
		{
191 12
			$this->reload();
192 12
		}
193
		if (!$this->di->isStored($this))
194
		{
195 12
			$this->di->store($this);
196 12
		}
197
	}
198
199
	/**
200
	 * Apply filter to current emit.
201 12
	 * @param FilterInterface|string|mixed $filter
202
	 * @return Signal
203 12
	 * @throws UnexpectedValueException
204
	 */
205
	public function filter($filter)
206
	{
207
		// Instantiate from string or array
208 12
		if (!is_object($filter))
209 12
		{
210 2
			$filter = $this->di->apply($filter);
211
		}
212
		if (!$filter instanceof PreFilterInterface && !$filter instanceof PostFilterInterface)
213 2
		{
214 2
			throw new UnexpectedValueException(sprintf('$filter must implement either `%s` or `%s` interface', PreFilterInterface::class, PostFilterInterface::class));
215
		}
216
		$this->currentFilters[] = $filter;
217 2
		return $this;
218 2
	}
219
220
	/**
221
	 * Emit signal to inform slots
222 10
	 * @param object|string $signal
223 10
	 * @return object[]
224
	 */
225
	public function emit($signal)
226
	{
227
		$result = [];
228
		if (is_string($signal))
229 10
		{
230
			$signal = new $signal;
231
		}
232 10
		$name = get_class($signal);
233 10
		NameNormalizer::normalize($name);
234 2
		if (!isset(self::$config[self::Signals][$name]))
235 14
		{
236
			self::$config[self::Signals][$name] = [];
237 14
			$this->logger->debug('No slots found for signal `{name}`, skipping', ['name' => $name]);
238 10
			return $result;
239
		}
240 10
241 10
		foreach (self::$config[self::Signals][$name] as $fqn => $injections)
0 ignored issues
show
Bug introduced by
The expression self::$config[self::Signals][$name] of type string is not traversable.
Loading history...
242 10
		{
243
			// Skip
244
			if (false === $injections || count($injections) == 0)
245
			{
246
				continue;
247
			}
248 10
			if (!PreFilter::filter($this, $fqn, $signal))
249 12
			{
250 12
				continue;
251 12
			}
252
			foreach ($injections as $injection)
253
			{
254
				$injected = SlotFactory::create($this, $signal, $fqn, $injection);
255
				if (false === $injected)
256
				{
257
					continue;
258
				}
259
				if (!PostFilter::filter($this, $injected, $signal))
260
				{
261
					continue;
262
				}
263
				$result[] = $injected;
264
			}
265
		}
266
		$this->currentFilters = [];
267
		return $result;
268
	}
269
270
	/**
271
	 * Call for signals from slot
272
	 * @param object $slot
273
	 * @param string $interface Interface, which must be implemented to get into slot
274
	 */
275
	public function gather($slot, $interface = null)
276
	{
277
		$name = get_class($slot);
278
		NameNormalizer::normalize($name);
279
		if (!isset(self::$config[self::Slots][$name]))
280
		{
281
			self::$config[self::Slots][$name] = [];
282
			$this->logger->debug('No signals found for slot `{name}`, skipping', ['name' => $name]);
283
		}
284
		$result = [];
285
		foreach ((array) self::$config[self::Slots][$name] as $fqn => $emit)
286
		{
287
			if (false === $emit)
288
			{
289
				continue;
290
			}
291
			// Check if class exists and log if doesn't
292
			if (!ClassChecker::exists($fqn))
293
			{
294
				$this->logger->debug(sprintf("Class `%s` not found while gathering slot `%s`", $fqn, get_class($slot)));
295
				continue;
296
			}
297
			if (null === $interface)
298
			{
299
				$result[] = new $fqn;
300
				continue;
301
			}
302
303
			// Check if class implements interface
304
			if (isset(class_implements($fqn)[$interface]))
305
			{
306
				$result[] = new $fqn;
307
			}
308
		}
309
		return $result;
310
	}
311
312
	/**
313
	 * Get filters
314
	 * @param string $interface
315
	 * @return PreFilterInterface[]|PostFilterInterface[]
316
	 */
317
	public function getFilters($interface)
318
	{
319
		$filters = FilterFactory::create($this, $interface);
320
		foreach ($this->currentFilters as $filter)
321
		{
322
			if (!$filter instanceof $interface)
323
			{
324
				continue;
325
			}
326
			$filters[] = $filter;
327
		}
328
		return $filters;
329
	}
330
331
	/**
332
	 * Get logger
333
	 * @codeCoverageIgnore
334
	 * @return LoggerInterface
335
	 */
336
	public function getLogger()
337
	{
338
		return $this->logger;
339
	}
340
341
	/**
342
	 * Set logger
343
	 * @codeCoverageIgnore
344
	 * @param LoggerInterface $logger
345
	 * @return Signal
346
	 */
347
	public function setLogger(LoggerInterface $logger)
348
	{
349
		$this->logger = $logger;
350
		return $this;
351
	}
352
353
	/**
354
	 * Get dependency injection container
355
	 * @return EmbeDi
356
	 */
357
	public function getDi()
358
	{
359
		return $this->di;
360
	}
361
362
	public function setDi(EmbeDi $di)
363
	{
364
		$this->di = $di;
365
		return $this;
366
	}
367
368
	/**
369
	 * Get Input/Output adapter
370
	 * @codeCoverageIgnore
371
	 * @return BuilderIOInterface I/O Adapter
372
	 */
373
	public function getIO()
374
	{
375
		return $this->getConfigured('io');
376 2
	}
377
378 2
	/**
379 2
	 * Set Input/Output interface
380 2
	 * @codeCoverageIgnore
381 2
	 * @param BuilderIOInterface $io
382
	 * @return Signal
383
	 */
384
	public function setIO(BuilderIOInterface $io)
385
	{
386 2
		return $this->setConfigured($io, 'io');
387 2
	}
388 2
389 2
	/**
390 2
	 * @codeCoverageIgnore
391
	 * @return ExtractorInterface
392
	 */
393
	public function getExtractor()
394
	{
395
		return $this->getConfigured('extractor');
396
	}
397
398
	/**
399
	 * @codeCoverageIgnore
400
	 * @param ExtractorInterface $extractor
401
	 * @return Signal
402
	 */
403
	public function setExtractor(ExtractorInterface $extractor)
404
	{
405
		return $this->setConfigured($extractor, 'extractor');
406
	}
407
408
	/**
409
	 * Reloads signals cache and reinitializes component.
410
	 */
411
	public function resetCache()
412
	{
413
		$this->reload();
414
	}
415
416
	private function reload()
417
	{
418
		self::$config = $this->getIO()->read();
419
	}
420
421
	/**
422
	 * Get configured property
423
	 * @param string $property
424
	 * @return SignalAwareInterface
425
	 */
426
	private function getConfigured($property)
427
	{
428
		if (is_object($this->$property))
429
		{
430
			$object = $this->$property;
431
		}
432
		else
433
		{
434
			$object = $this->di->apply($this->$property);
435
		}
436
		if ($object instanceof SignalAwareInterface)
437
		{
438
			$object->setSignal($this);
439
		}
440
		return $object;
441
	}
442
443
	/**
444
	 * Set signal aware property
445
	 * @param SignalAwareInterface $object
446
	 * @param string $property
447
	 * @return Signal
448
	 */
449
	private function setConfigured(SignalAwareInterface $object, $property)
450
	{
451
		$object->setSignal($this);
452
		$this->$property = $object;
453
		return $this;
454
	}
455
456
}
457