Issues (6)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Signal.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
531
		}
532
	}
533
534
	/**
535
	 * Get configured property
536
	 * @param string $property
537
	 * @return SignalAwareInterface|ExtractorInterface|BuilderIOInterface
538
	 */
539 1
	private function getConfigured($property)
540
	{
541 1
		if (is_object($this->$property))
542
		{
543 1
			$object = $this->$property;
544
		}
545
		else
546
		{
547
			$object = $this->di->apply($this->$property);
548
		}
549 1
		if ($object instanceof SignalAwareInterface)
550
		{
551 1
			$object->setSignal($this);
552
		}
553 1
		return $object;
554
	}
555
556
	/**
557
	 * Set signal aware property
558
	 * @param SignalAwareInterface $object
559
	 * @param string $property
560
	 * @return Signal
561
	 */
562
	private function setConfigured(SignalAwareInterface $object, $property)
563
	{
564
		$object->setSignal($this);
565
		$this->$property = $object;
566
		return $this;
567
	}
568
569
}
570