Completed
Push — master ( 52abb6...21e8df )
by Peter
12:51
created

Signal::getIO()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
ccs 0
cts 0
cp 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
	 * Directories to ignore while scanning
79
	 * @var string[]
80
	 */
81
	public $ignoreDirs = [
82
		'vendor', // Vendors in vendors
83
		'generated', // Generated data, including signals
84
		'runtime', // Runtime data
85
	];
86
87
	/**
88
	 * Filters configuration.
89
	 * This filters will be applied to every emit.
90
	 * @var string[]|object[]
91
	 */
92
	public $filters = [];
93
94
	/**
95
	 * Sorters configuration.
96
	 * @var string[]|object[]
97
	 */
98
	public $sorters = [];
99
100
	/**
101
	 * Extractor configuration
102
	 * @var string|[]|object
103
	 */
104
	public $extractor = Addendum::class;
105
106
	/**
107
	 * Input/Output configuration
108
	 * @var string|[]|object
109
	 */
110
	public $io = PhpFile::class;
111
112
	/**
113
	 * Whenever component is initialized
114
	 * @var bool
115
	 */
116
	public $isInitialized = false;
117
118
	/**
119
	 * Configuration of signals and slots
120
	 * @var string[][]
121
	 */
122
	private static $config = [];
123
124
	/**
125
	 * Logger instance holder
126
	 * NOTE: There is property annotation with `logger` name,
127
	 * thus this name is a bit longer
128
	 * @var LoggerInterface
129
	 */
130
	private $loggerInstance = null;
131
132
	/**
133
	 * Embedded dependency injection
134
	 * @var EmbeDi
135
	 */
136
	private $di = null;
137
138
	/**
139
	 * Version
140
	 * @var string
141
	 */
142
	private $version = null;
143
144
	/**
145
	 * Current filters
146
	 * @var PreFilterInterface[]|PostFilterInterface[]
147
	 */
148
	private $currentFilters = [];
149
150 17
	public function __construct($configName = self::ConfigName)
151
	{
152 17
		$this->loggerInstance = new NullLogger;
153
154
		/**
155
		 * TODO This should be made as embedi adapter, currently unsupported
156
		 */
157 17
		$config = new ConfigReader($configName);
158 17
		$this->di = EmbeDi::fly($configName);
159 17
		$this->di->configure($this);
160 17
		$this->di->apply($config->toArray(), $this);
161 17
	}
162
163
	/**
164
	 * Getter
165
	 * @param string $name
166
	 * @return mixed
167
	 */
168
	public function __get($name)
169
	{
170
		return $this->{'get' . ucfirst($name)}();
171
	}
172
173
	/**
174
	 * Setter
175
	 * @param string $name
176
	 * @param mixed $value
177
	 * @return mixed
178
	 */
179
	public function __set($name, $value)
180
	{
181
		return $this->{'set' . ucfirst($name)}($value);
182
	}
183
184
	/**
185
	 * Get current signals version
186
	 *
187
	 * @codeCoverageIgnore
188
	 * @return string
189
	 */
190
	public function getVersion()
191
	{
192
		if (null === $this->version)
193
		{
194
			$this->version = require __DIR__ . '/version.php';
195
		}
196
		return $this->version;
197
	}
198
199
	public function init()
200
	{
201
		if (!$this->isInitialized)
202
		{
203
			$this->reload();
204
		}
205
		if (!$this->di->isStored($this))
206
		{
207
			$this->di->store($this);
208
		}
209
	}
210
211
	/**
212
	 * Apply filter to current emit.
213
	 * @param FilterInterface|string|mixed $filter
214
	 * @return Signal
215
	 * @throws UnexpectedValueException
216
	 */
217 3
	public function filter($filter)
218
	{
219
		// Instantiate from string or array
220 3
		if (!is_object($filter))
221 3
		{
222 1
			$filter = $this->di->apply($filter);
223 1
		}
224 3
		if (!$filter instanceof PreFilterInterface && !$filter instanceof PostFilterInterface)
225 3
		{
226
			throw new UnexpectedValueException(sprintf('$filter must implement either `%s` or `%s` interface', PreFilterInterface::class, PostFilterInterface::class));
227
		}
228 3
		$this->currentFilters[] = $filter;
229 3
		return $this;
230
	}
231
232
	/**
233
	 * Emit signal to inform slots
234
	 * @param object|string $signal
235
	 * @return object[]
236
	 */
237 17
	public function emit($signal)
238
	{
239 16
		$result = [];
240 16
		if (is_string($signal))
241 16
		{
242
			$signal = new $signal;
243
		}
244 16
		$name = get_class($signal);
245 16
		NameNormalizer::normalize($name);
246 16
		if (empty(self::$config))
247 16
		{
248
			$this->init();
249
		}
250 16
		if (!isset(self::$config[self::Signals][$name]))
251 16
		{
252
			self::$config[self::Signals][$name] = [];
253
			$this->loggerInstance->debug('No slots found for signal `{name}`, skipping', ['name' => $name]);
254
			return $result;
255
		}
256
257 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...
258
		{
259
			// Skip
260 17
			if (false === $injections || count($injections) == 0)
261 16
			{
262 15
				continue;
263
			}
264 16
			if (!PreFilter::filter($this, $fqn, $signal))
265 16
			{
266 3
				continue;
267
			}
268 13
			foreach ($injections as $injection)
269
			{
270 13
				$injected = SlotFactory::create($this, $signal, $fqn, $injection);
271 13
				if (false === $injected)
272 13
				{
273
					continue;
274
				}
275 13
				if (!PostFilter::filter($this, $injected, $signal))
276 13
				{
277 3
					continue;
278
				}
279 11
				$result[] = $injected;
280 13
			}
281 16
		}
282 16
		$this->currentFilters = [];
283 16
		return $result;
284
	}
285
286
	/**
287
	 * Call for signals from slot
288
	 * @param object $slot
289
	 * @param string $interface Interface, which must be implemented to get into slot
290
	 */
291
	public function gather($slot, $interface = null)
292
	{
293
		$name = get_class($slot);
294
		NameNormalizer::normalize($name);
295
		if (empty(self::$config))
296
		{
297
			$this->init();
298
		}
299
		if (!isset(self::$config[self::Slots][$name]))
300
		{
301
			self::$config[self::Slots][$name] = [];
302
			$this->loggerInstance->debug('No signals found for slot `{name}`, skipping', ['name' => $name]);
303
		}
304
		$result = [];
305
		foreach ((array) self::$config[self::Slots][$name] as $fqn => $emit)
306
		{
307
			if (false === $emit)
308
			{
309
				continue;
310
			}
311
			// Check if class exists and log if doesn't
312
			if (!ClassChecker::exists($fqn))
313
			{
314
				$this->loggerInstance->debug(sprintf("Class `%s` not found while gathering slot `%s`", $fqn, get_class($slot)));
315
				continue;
316
			}
317
			if (null === $interface)
318
			{
319
				$result[] = new $fqn;
320
				continue;
321
			}
322
323
			// Check if class implements interface
324
			if (isset(class_implements($fqn)[$interface]))
325
			{
326
				$result[] = new $fqn;
327
			}
328
		}
329
		return $result;
330
	}
331
332
	/**
333
	 * Get filters
334
	 * @param string $interface
335
	 * @return PreFilterInterface[]|PostFilterInterface[]
336
	 */
337 16
	public function getFilters($interface)
338
	{
339 16
		$filters = FilterFactory::create($this, $interface);
340 16
		foreach ($this->currentFilters as $filter)
341
		{
342 3
			if (!$filter instanceof $interface)
343 3
			{
344 1
				continue;
345
			}
346 3
			$filters[] = $filter;
347 16
		}
348 16
		return $filters;
349
	}
350
351
	/**
352
	 * Get logger
353
	 * @codeCoverageIgnore
354
	 * @return LoggerInterface
355
	 */
356
	public function getLogger()
357
	{
358
		return $this->loggerInstance;
359
	}
360
361
	/**
362
	 * Set logger
363
	 * @codeCoverageIgnore
364
	 * @param LoggerInterface $logger
365
	 * @return Signal
366
	 */
367
	public function setLogger(LoggerInterface $logger)
368
	{
369
		$this->loggerInstance = $logger;
370
		return $this;
371
	}
372
373
	/**
374
	 * Get dependency injection container
375
	 * @return EmbeDi
376
	 */
377 16
	public function getDi()
378
	{
379 16
		return $this->di;
380
	}
381
382
	public function setDi(EmbeDi $di)
383
	{
384
		$this->di = $di;
385
		return $this;
386
	}
387
388
	/**
389
	 * Get Input/Output adapter
390
	 * @codeCoverageIgnore
391
	 * @return BuilderIOInterface I/O Adapter
392
	 */
393
	public function getIO()
394
	{
395
		return $this->getConfigured('io');
396
	}
397
398
	/**
399
	 * Set Input/Output interface
400
	 * @codeCoverageIgnore
401
	 * @param BuilderIOInterface $io
402
	 * @return Signal
403
	 */
404
	public function setIO(BuilderIOInterface $io)
405
	{
406
		return $this->setConfigured($io, 'io');
407
	}
408
409
	/**
410
	 * @codeCoverageIgnore
411
	 * @return ExtractorInterface
412
	 */
413
	public function getExtractor()
414
	{
415
		return $this->getConfigured('extractor');
416
	}
417
418
	/**
419
	 * @codeCoverageIgnore
420
	 * @param ExtractorInterface $extractor
421
	 * @return Signal
422
	 */
423
	public function setExtractor(ExtractorInterface $extractor)
424
	{
425
		return $this->setConfigured($extractor, 'extractor');
426
	}
427
428
	/**
429
	 * Reloads signals cache and reinitializes component.
430
	 */
431
	public function resetCache()
432
	{
433
		$this->reload();
434
	}
435
436
	private function reload()
437
	{
438
		self::$config = $this->getIO()->read();
439
	}
440
441
	/**
442
	 * Get configured property
443
	 * @param string $property
444
	 * @return SignalAwareInterface
445
	 */
446 1
	private function getConfigured($property)
447
	{
448 1
		if (is_object($this->$property))
449 1
		{
450 1
			$object = $this->$property;
451 1
		}
452
		else
453
		{
454
			$object = $this->di->apply($this->$property);
455
		}
456 1
		if ($object instanceof SignalAwareInterface)
457 1
		{
458 1
			$object->setSignal($this);
459 1
		}
460 1
		return $object;
461
	}
462
463
	/**
464
	 * Set signal aware property
465
	 * @param SignalAwareInterface $object
466
	 * @param string $property
467
	 * @return Signal
468
	 */
469
	private function setConfigured(SignalAwareInterface $object, $property)
470
	{
471
		$object->setSignal($this);
472
		$this->$property = $object;
473
		return $this;
474
	}
475
476
}
477