Completed
Push — v2 ( 357b8a...035655 )
by Peter
13:42 queued 09:14
created

Signal::getDi()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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';
46
	const Signals = 'signals';
47
48
	/**
49
	 * Generated signals name.
50
	 * Name of this constant is confusing.
51
	 * @internal description
52
	 */
53
	const ConfigFilename = 'signals-definition.php';
54
55
	/**
56
	 * Config file name
57
	 */
58
	const ConfigName = "signals";
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
	private static $config = [];
113
114
	/**
115
	 * Logger
116
	 * @var LoggerInterface
117
	 */
118
	private $logger = null;
119
120
	/**
121
	 *
122
	 * @var EmbeDi
123
	 */
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 17
	public function __construct($configName = self::ConfigName)
139
	{
140 17
		$this->logger = new NullLogger;
141
142
		/**
143
		 * TODO This should be made as embedi adapter, currently unsupported
144
		 */
145 17
		$config = new ConfigReader($configName);
146 17
		$this->di = EmbeDi::fly($configName);
147 17
		$this->di->apply($config->toArray(), $this);
148 17
		$this->di->configure($this);
149 17
	}
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
	public function getVersion()
179
	{
180
		if (null === $this->version)
181
		{
182
			$this->version = require __DIR__ . '/version.php';
183
		}
184
		return $this->version;
185
	}
186
187
	public function init()
188
	{
189
		if (!$this->isInitialized)
190
		{
191
			$this->reload();
192
		}
193
		if (!$this->di->isStored($this))
194
		{
195
			$this->di->store($this);
196
		}
197
	}
198
199
	/**
200
	 * Apply filter to current emit.
201
	 * @param FilterInterface|string|mixed $filter
202
	 * @return Signal
203
	 * @throws UnexpectedValueException
204
	 */
205 3
	public function filter($filter)
206
	{
207
		// Instantiate from string or array
208 3
		if (!is_object($filter))
209
		{
210 1
			$filter = $this->di->apply($filter);
211
		}
212 3
		if (!$filter instanceof PreFilterInterface && !$filter instanceof PostFilterInterface)
213
		{
214
			throw new UnexpectedValueException(sprintf('$filter must implement either `%s` or `%s` interface', PreFilterInterface::class, PostFilterInterface::class));
215
		}
216 3
		$this->currentFilters[] = $filter;
217 3
		return $this;
218
	}
219
220
	/**
221
	 * Emit signal to inform slots
222
	 * @param object|string $signal
223
	 * @return object[]
224
	 */
225 16
	public function emit($signal)
226
	{
227 16
		$result = [];
228 16
		if (is_string($signal))
229
		{
230
			$signal = new $signal;
231
		}
232 16
		$name = get_class($signal);
233 16
		NameNormalizer::normalize($name);
234 16
		if (empty(self::$config))
235
		{
236
			$this->init();
237
		}
238 16
		if (!isset(self::$config[self::Signals][$name]))
239
		{
240
			self::$config[self::Signals][$name] = [];
241
			$this->logger->debug('No slots found for signal `{name}`, skipping', ['name' => $name]);
242
			return $result;
243
		}
244
245 16
		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...
246
		{
247
			// Skip
248 16
			if (false === $injections || count($injections) == 0)
249
			{
250
				continue;
251
			}
252 16
			if (!PreFilter::filter($this, $fqn, $signal))
253
			{
254 3
				continue;
255
			}
256 13
			foreach ($injections as $injection)
257
			{
258 13
				$injected = SlotFactory::create($this, $signal, $fqn, $injection);
259 13
				if (false === $injected)
260
				{
261
					continue;
262
				}
263 13
				if (!PostFilter::filter($this, $injected, $signal))
264
				{
265 2
					continue;
266
				}
267 13
				$result[] = $injected;
268
			}
269
		}
270 16
		$this->currentFilters = [];
271 16
		return $result;
272
	}
273
274
	/**
275
	 * Call for signals from slot
276
	 * @param object $slot
277
	 * @param string $interface Interface, which must be implemented to get into slot
278
	 */
279
	public function gather($slot, $interface = null)
280
	{
281
		$name = get_class($slot);
282
		NameNormalizer::normalize($name);
283
		if (empty(self::$config))
284
		{
285
			$this->init();
286
		}
287
		if (!isset(self::$config[self::Slots][$name]))
288
		{
289
			self::$config[self::Slots][$name] = [];
290
			$this->logger->debug('No signals found for slot `{name}`, skipping', ['name' => $name]);
291
		}
292
		$result = [];
293
		foreach ((array) self::$config[self::Slots][$name] as $fqn => $emit)
294
		{
295
			if (false === $emit)
296
			{
297
				continue;
298
			}
299
			// Check if class exists and log if doesn't
300
			if (!ClassChecker::exists($fqn))
301
			{
302
				$this->logger->debug(sprintf("Class `%s` not found while gathering slot `%s`", $fqn, get_class($slot)));
303
				continue;
304
			}
305
			if (null === $interface)
306
			{
307
				$result[] = new $fqn;
308
				continue;
309
			}
310
311
			// Check if class implements interface
312
			if (isset(class_implements($fqn)[$interface]))
313
			{
314
				$result[] = new $fqn;
315
			}
316
		}
317
		return $result;
318
	}
319
320
	/**
321
	 * Get filters
322
	 * @param string $interface
323
	 * @return PreFilterInterface[]|PostFilterInterface[]
324
	 */
325 16
	public function getFilters($interface)
326
	{
327 16
		$filters = FilterFactory::create($this, $interface);
328 16
		foreach ($this->currentFilters as $filter)
329
		{
330 3
			if (!$filter instanceof $interface)
331
			{
332 1
				continue;
333
			}
334 3
			$filters[] = $filter;
335
		}
336 16
		return $filters;
337
	}
338
339
	/**
340
	 * Get logger
341
	 * @codeCoverageIgnore
342
	 * @return LoggerInterface
343
	 */
344
	public function getLogger()
345
	{
346
		return $this->logger;
347
	}
348
349
	/**
350
	 * Set logger
351
	 * @codeCoverageIgnore
352
	 * @param LoggerInterface $logger
353
	 * @return Signal
354
	 */
355
	public function setLogger(LoggerInterface $logger)
356
	{
357
		$this->logger = $logger;
358
		return $this;
359
	}
360
361
	/**
362
	 * Get dependency injection container
363
	 * @return EmbeDi
364
	 */
365 16
	public function getDi()
366
	{
367 16
		return $this->di;
368
	}
369
370
	public function setDi(EmbeDi $di)
371
	{
372
		$this->di = $di;
373
		return $this;
374
	}
375
376
	/**
377
	 * Get Input/Output adapter
378
	 * @codeCoverageIgnore
379
	 * @return BuilderIOInterface I/O Adapter
380
	 */
381
	public function getIO()
382
	{
383
		return $this->getConfigured('io');
384
	}
385
386
	/**
387
	 * Set Input/Output interface
388
	 * @codeCoverageIgnore
389
	 * @param BuilderIOInterface $io
390
	 * @return Signal
391
	 */
392
	public function setIO(BuilderIOInterface $io)
393
	{
394
		return $this->setConfigured($io, 'io');
395
	}
396
397
	/**
398
	 * @codeCoverageIgnore
399
	 * @return ExtractorInterface
400
	 */
401
	public function getExtractor()
402
	{
403
		return $this->getConfigured('extractor');
404
	}
405
406
	/**
407
	 * @codeCoverageIgnore
408
	 * @param ExtractorInterface $extractor
409
	 * @return Signal
410
	 */
411
	public function setExtractor(ExtractorInterface $extractor)
412
	{
413
		return $this->setConfigured($extractor, 'extractor');
414
	}
415
416
	/**
417
	 * Reloads signals cache and reinitializes component.
418
	 */
419
	public function resetCache()
420
	{
421
		$this->reload();
422
	}
423
424
	private function reload()
425
	{
426
		self::$config = $this->getIO()->read();
427
	}
428
429
	/**
430
	 * Get configured property
431
	 * @param string $property
432
	 * @return SignalAwareInterface
433
	 */
434 1
	private function getConfigured($property)
435
	{
436 1
		if (is_object($this->$property))
437
		{
438 1
			$object = $this->$property;
439
		}
440
		else
441
		{
442
			$object = $this->di->apply($this->$property);
443
		}
444 1
		if ($object instanceof SignalAwareInterface)
445
		{
446 1
			$object->setSignal($this);
447
		}
448 1
		return $object;
449
	}
450
451
	/**
452
	 * Set signal aware property
453
	 * @param SignalAwareInterface $object
454
	 * @param string $property
455
	 * @return Signal
456
	 */
457
	private function setConfigured(SignalAwareInterface $object, $property)
458
	{
459
		$object->setSignal($this);
460
		$this->$property = $object;
461
		return $this;
462
	}
463
464
}
465