GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (29)

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.

lib/Module/ModuleCollection.php (11 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 file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ICanBoogie\Module;
13
14
use ICanBoogie\Accessor\AccessorTrait;
15
use ICanBoogie\ActiveRecord\Model;
16
use ICanBoogie\ErrorCollection;
17
use ICanBoogie\Module;
18
use ICanBoogie\Storage\Storage;
19
use ICanBoogie\Module\ModuleCollection\InstallableModulesFilter;
20
21
/**
22
 * A module collection.
23
 *
24
 * @property-read array $config_paths Paths of the enabled modules having a `config` directory.
25
 * @property-read array $locale_paths Paths of the enabled modules having a `locale` directory.
26
 * @property-read array $disabled_modules_descriptors Descriptors of the disabled modules.
27
 * @property-read array $enabled_modules_descriptors Descriptors of the enabled modules.
28
 * @property-read array $index Index for the modules.
29
 */
30
class ModuleCollection implements \ArrayAccess, \IteratorAggregate
31
{
32
	use AccessorTrait;
33
34
	/**
35
	 * May be used with the {@link filter_descriptors_by_users()} method to filter the descriptors
36
	 * of enabled modules.
37
	 */
38
	const ONLY_ENABLED_MODULES = false;
39
40
	/**
41
	 * May be used with the {@link filter_descriptors_by_users()} method to filter the descriptors
42
	 * of all modules, enabled or not.
43
	 */
44
	const ALL_MODULES = true;
45
46
	/**
47
	 * Formats a SQL table name given the module id and the model id.
48
	 *
49
	 * @param string $module_id
50
	 * @param string $model_id
51
	 *
52
	 * @return string
53
	 */
54
	static public function format_model_name($module_id, $model_id = 'primary')
55
	{
56
		return preg_replace('#[^0-9a-zA-Z$_]#', '_', $module_id) . ($model_id == 'primary' ? '' : '__' . $model_id);
57
	}
58
59
	/**
60
	 * The descriptors for the modules.
61
	 *
62
	 * @var array
63
	 */
64
	public $descriptors = [];
65
66
	/**
67
	 * The paths where modules can be found.
68
	 *
69
	 * @var array
70
	 */
71
	protected $paths = [];
72
73
	/**
74
	 * A cache for the modules index.
75
	 *
76
	 * @var Storage
77
	 */
78
	protected $cache;
79
80
	/**
81
	 * Instantiated modules.
82
	 *
83
	 * @var Module[]
84
	 */
85
	protected $modules = [];
86
87
	/**
88
	 * The index for the available modules is created with the accessor object.
89
	 *
90
	 * @param array $paths The paths to look for modules.
91
	 * @param Storage $cache The cache to use for the module indexes.
92
	 */
93
	public function __construct($paths, Storage $cache = null)
94
	{
95
		$this->paths = $paths;
96
		$this->cache = $cache;
97
	}
98
99
	/**
100
	 * Revokes constructions.
101
	 *
102
	 * The following properties are revoked:
103
	 *
104
	 * - {@link $enabled_modules_descriptors}
105
	 * - {@link $disabled_modules_descriptors}
106
	 * - {@link $catalog_paths}
107
	 * - {@link $config_paths}
108
	 *
109
	 * The method is usually invoked when modules state changes, in order to reflect these
110
	 * changes.
111
	 */
112
	protected function revoke_constructions()
113
	{
114
		unset($this->enabled_modules_descriptors);
115
		unset($this->disabled_modules_descriptors);
116
		unset($this->catalog_paths);
117
		unset($this->config_paths);
118
	}
119
120
	/**
121
	 * Enables a module.
122
	 *
123
	 * @param string $module_id Module identifier.
124
	 */
125
	public function enable($module_id)
126
	{
127
		$this->change_module_availability($module_id, false);
128
	}
129
130
	/**
131
	 * Disables a module.
132
	 *
133
	 * @param string $module_id Module identifier.
134
	 */
135
	public function disable($module_id)
136
	{
137
		$this->change_module_availability($module_id, true);
138
	}
139
140
	/**
141
	 * Used to enable or disable a module using the specified offset as a module identifier.
142
	 *
143
	 * @param string $module_id Identifier of the module.
144
	 * @param bool $enable Status of the module: `true` for enabled, `false` for disabled.
145
	 */
146
	public function offsetSet($module_id, $enable)
147
	{
148
		$this->change_module_availability($module_id, $enable);
149
	}
150
151
	/**
152
	 * Disables a module by setting the {@link Descriptor::DISABLED} key of its descriptor to `true`.
153
	 *
154
	 * @param string $module_id Module identifier.
155
	 */
156
	public function offsetUnset($module_id)
157
	{
158
		$this->change_module_availability($module_id, false);
159
	}
160
161
	/**
162
	 * Checks the availability of a module.
163
	 *
164
	 * A module is considered available when its descriptor is defined, and the
165
	 * {@link Descriptor::DISABLED} key of its descriptor is empty.
166
	 *
167
	 * Note: `empty()` will call {@link offsetGet()} to check if the value is not empty. So, unless
168
	 * you want to use the module you check, better check using `!isset()`, otherwise the module
169
	 * you check is loaded too.
170
	 *
171
	 * @param string $module_id Module identifier.
172
	 *
173
	 * @return boolean Whether or not the module is available.
174
	 */
175
	public function offsetExists($module_id)
176
	{
177
		$this->ensure_modules_are_indexed();
0 ignored issues
show
The call to the method ICanBoogie\Module\Module...e_modules_are_indexed() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
178
179
		$descriptors = $this->descriptors;
180
181
		return isset($descriptors[$module_id])
182
		&& empty($descriptors[$module_id][Descriptor::DISABLED]);
183
	}
184
185
	/**
186
	 * Returns a module object.
187
	 *
188
	 * If the {@link autorun} property is `true`, the {@link Module::run()} method of the module
189
	 * is invoked upon its first loading.
190
	 *
191
	 * @param string $module_id Module identifier.
192
	 *
193
	 * @throws ModuleNotDefined when the requested module is not defined.
194
	 *
195
	 * @throws ModuleIsDisabled when the module is disabled.
196
	 *
197
	 * @throws ModuleConstructorMissing when the class that should be used to create its instance
198
	 * is not defined.
199
	 *
200
	 * @return Module
201
	 */
202
	public function offsetGet($module_id)
203
	{
204
		$this->ensure_modules_are_indexed();
0 ignored issues
show
The call to the method ICanBoogie\Module\Module...e_modules_are_indexed() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
205
206
		if (isset($this->modules[$module_id]))
207
		{
208
			return $this->modules[$module_id];
209
		}
210
211
		return $this->modules[$module_id] = $this->instantiate_module($module_id);
212
	}
213
214
	/**
215
	 * Returns an iterator for instantiated modules.
216
	 *
217
	 * @return \ArrayIterator
218
	 */
219
	public function getIterator()
220
	{
221
		$this->ensure_modules_are_indexed();
0 ignored issues
show
The call to the method ICanBoogie\Module\Module...e_modules_are_indexed() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
222
223
		return new \ArrayIterator($this->modules);
224
	}
225
226
	/**
227
	 * Filter descriptors.
228
	 *
229
	 * @param callable $filter
230
	 *
231
	 * @return array
232
	 */
233
	public function filter_descriptors(callable $filter)
234
	{
235
		return array_filter($this->descriptors, $filter);
236
	}
237
238
	/**
239
	 * Returns the modules using a module.
240
	 *
241
	 * @param string $module_id Used module identifier.
242
	 * @param bool $all One of {@link ONLY_ENABLED_MODULES} or {@link ALL_MODULES}.
243
	 * Default: {@link ONLY_ENABLED_MODULES}.
244
	 *
245
	 * @return array A array of filtered descriptors.
246
	 */
247
	public function filter_descriptors_by_users($module_id, $all = self::ONLY_ENABLED_MODULES)
248
	{
249
		$users = [];
250
		$descriptors = $all ? $this->descriptors : $this->enabled_modules_descriptors;
251
252
		foreach ($descriptors as $user_id => $descriptor)
253
		{
254
			if ($descriptor[Descriptor::INHERITS] == $module_id
255
			|| in_array($module_id, $descriptor[Descriptor::REQUIRES]))
256
			{
257
				$users[$user_id] = $descriptor;
258
			}
259
		}
260
261
		return $users;
262
	}
263
264
	/**
265
	 * Indexes the modules found in the paths specified during construct.
266
	 *
267
	 * The index is made of an array of descriptors, an array of catalogs paths, an array of
268
	 * configs path, and finally an array of configs constructors.
269
	 *
270
	 * The method also creates a `DIR` constant for each module. The constant is defined in the
271
	 * namespace of the module e.g. `Icybee\ModuleCollection\Nodes\DIR`.
272
	 *
273
	 * @return array
274
	 */
275
	protected function lazy_get_index()
276
	{
277
		$index = $this->obtain_index();
278
		$this->descriptors = $descriptors = $index['descriptors'];
279
		$this->define_constants($descriptors);
280
281
		return $index;
282
	}
283
284
	/**
285
	 * Obtain index either from cache or by building it.
286
	 *
287
	 * @return array|mixed|null
288
	 */
289
	private function obtain_index()
290
	{
291
		$cache = $this->cache;
292
293
		if ($cache)
294
		{
295
			$key = 'cached_modules_' . substr(sha1(implode('#', $this->paths)), 0, 8);
296
			$index = $cache->retrieve($key);
297
298
			if ($index)
299
			{
300
				return $index;
301
			}
302
303
			$index = $this->index_modules();
304
			$cache->store($key, $index);
305
306
			return $index;
307
		}
308
309
		return $this->index_modules();
310
	}
311
312
	/**
313
	 * Construct the index for the modules.
314
	 *
315
	 * The index contains the following values:
316
	 *
317
	 * - (array) descriptors: The descriptors of the modules, ordered by weight.
318
	 * - (array) catalogs: Absolute paths to locale catalog directories.
319
	 * - (array) configs: Absolute paths to config directories.
320
	 * - (array) classes aliases: An array of _key/value_ pairs where _key_ is the alias of a class
321
	 * and _value_ if the real class.
322
	 *
323
	 * @return array
324
	 */
325
	protected function index_modules()
326
	{
327
		$descriptors = $this->paths ? $this->index_descriptors($this->paths) : [];
328
		$catalogs = [];
329
		$configs = [];
330
331
		foreach ($descriptors as $id => $descriptor)
332
		{
333
			$path = $descriptor[Descriptor::PATH];
334
335
			if ($descriptor['__has_locale'])
336
			{
337
				$catalogs[] = $path . 'locale';
338
			}
339
340
			if ($descriptor['__has_config'])
341
			{
342
				$configs[] = $path . 'config';
343
			}
344
		}
345
346
		return [
347
348
			'descriptors' => $descriptors,
349
			'catalogs' => $catalogs,
350
			'configs' => $configs
351
352
		];
353
	}
354
355
	/**
356
	 * Indexes descriptors.
357
	 *
358
	 * The descriptors are extended with the following default values:
359
	 *
360
	 * - (string) category: null.
361
	 * - (string) class: ModuleCollection\<normalized_module_part>
362
	 * - (string) description: null.
363
	 * - (bool) disabled: false if required, true otherwise.
364
	 * - (string) extends: null.
365
	 * - (string) id: The module's identifier.
366
	 * - (array) models: Empty array.
367
	 * - (string) path: The absolute path to the module directory.
368
	 * - (string) permission: null.
369
	 * - (array) permissions: Empty array.
370
	 * - (bool) startup: false.
371
	 * - (bool) required: false.
372
	 * - (array) requires: Empty array.
373
	 * - (string) weight: 0.
374
	 *
375
	 * The descriptors are ordered according to their inheritance and weight.
376
	 *
377
	 * @param array $paths
378
	 *
379
	 * @return array
380
	 */
381
	protected function index_descriptors(array $paths)
382
	{
383
		$descriptors = $this->collect_descriptors($paths);
384
385
		if (!$descriptors)
386
		{
387
			return [];
388
		}
389
390
		#
391
		# Compute inheritance.
392
		#
393
394
		$find_parents = function($id, &$parents = []) use (&$find_parents, &$descriptors)
395
		{
396
			$parent = $descriptors[$id][Descriptor::INHERITS];
397
398
			if ($parent)
399
			{
400
				$parents[] = $parent;
401
402
				$find_parents($parent, $parents);
403
			}
404
405
			return $parents;
406
		};
407
408
		foreach ($descriptors as $id => &$descriptor)
409
		{
410
			$descriptor['__parents'] = $find_parents($id);
411
		}
412
413
		#
414
		# Orders descriptors according to their weight.
415
		#
416
417
		$ordered_ids = $this->order_ids(array_keys($descriptors), $descriptors);
418
		$descriptors = array_merge(array_combine($ordered_ids, $ordered_ids), $descriptors);
419
420
		foreach ($descriptors as $id => &$descriptor)
421
		{
422
			foreach ($descriptor[Descriptor::MODELS] as $model_id => &$model_descriptor)
423
			{
424
				if ($model_descriptor != 'inherit')
425
				{
426
					continue;
427
				}
428
429
				$parent_descriptor = $descriptors[$descriptor[Descriptor::INHERITS]];
430
				$model_descriptor = [
431
432
					Model::EXTENDING => $parent_descriptor[Descriptor::ID] . '/' . $model_id
433
434
				];
435
			}
436
437
			$descriptor = $this->alter_descriptor($descriptor);
438
		}
439
440
		return $descriptors;
441
	}
442
443
	/**
444
	 * Collects descriptors from paths.
445
	 *
446
	 * @param array $paths
447
	 *
448
	 * @return array
449
	 */
450
	protected function collect_descriptors(array $paths)
451
	{
452
		$descriptors = [];
453
454
		foreach ($paths as $root)
455
		{
456
			$root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
457
			$descriptor_path = $root . 'descriptor.php';
458
459
			if (file_exists($descriptor_path))
460
			{
461
				$id = basename(realpath($root));
462
				$descriptor = $this->read_descriptor($id, $root);
463
464
				$descriptors[$descriptor[Descriptor::ID]] = $descriptor;
465
			}
466
			else
467
			{
468
				try
469
				{
470
					$dir = new \DirectoryIterator($root);
471
				}
472
				catch (\Exception $e)
473
				{
474
					throw new \RuntimeException(\ICanBoogie\format('Unable to open directory %root', [
475
476
						'root' => $root
477
478
					]));
479
				}
480
481
				foreach ($dir as $file)
482
				{
483
					if ($file->isDot() || !$file->isDir())
484
					{
485
						continue;
486
					}
487
488
					$id = $file->getFilename();
489
					$path = $root . $id . DIRECTORY_SEPARATOR;
490
					$descriptor = $this->read_descriptor($id, $path);
491
492
					$descriptors[$descriptor[Descriptor::ID]] = $descriptor;
493
				}
494
			}
495
		}
496
497
		return $descriptors;
498
	}
499
500
	/**
501
	 * Reads the descriptor file.
502
	 *
503
	 * The descriptor file is extended with private values and default values.
504
	 *
505
	 * @param string $module_id The identifier of the module.
506
	 * @param string $path The path to the directory where the descriptor is located.
507
	 *
508
	 * @throws \InvalidArgumentException in the following situations:
509
	 * - The descriptor is not an array
510
	 * - The {@link Descriptor::TITLE} key is empty.
511
	 * - The {@link Descriptor::NS} key is empty.
512
	 *
513
	 * @return array
514
	 */
515
	protected function read_descriptor($module_id, $path)
516
	{
517
		$descriptor_path = $path . 'descriptor.php';
518
		$descriptor = require $descriptor_path;
519
520
		if (!is_array($descriptor))
521
		{
522
			throw new \InvalidArgumentException(\ICanBoogie\format
523
			(
524
				'%var should be an array: %type given instead in %path', [
525
526
					'var' => 'descriptor',
527
					'type' => gettype($descriptor),
528
					'path' => $descriptor_path
529
530
				]
531
			));
532
		}
533
534 View Code Duplication
		if (empty($descriptor[Descriptor::TITLE]))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
535
		{
536
			throw new \InvalidArgumentException(\ICanBoogie\format
537
			(
538
				'The %name value of the %id module descriptor is empty in %path.', [
539
540
					'name' => Descriptor::TITLE,
541
					'id' => $module_id,
542
					'path' => $descriptor_path
543
544
				]
545
			));
546
		}
547
548 View Code Duplication
		if (empty($descriptor[Descriptor::NS]))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
549
		{
550
			throw new \InvalidArgumentException(\ICanBoogie\format
551
			(
552
				'%name is required. Invalid descriptor for module %id in %path.', [
553
554
					'name' => Descriptor::NS,
555
					'id' => $module_id,
556
					'path' => $descriptor_path
557
558
				]
559
			));
560
		}
561
562
		return Descriptor::normalize($descriptor + [
563
564
			Descriptor::ID => $module_id,
565
			Descriptor::PATH => $path,
566
567
			'__has_config' => is_dir($path . 'config'),
568
			'__has_locale' => is_dir($path . 'locale'),
569
			'__parents' => []
570
571
		]);
572
	}
573
574
	/**
575
	 * Alters the module descriptor.
576
	 *
577
	 * @param array $descriptor Descriptor of the module to index.
578
	 *
579
	 * @return array The altered descriptor.
580
	 */
581
	protected function alter_descriptor(array $descriptor)
582
	{
583
		$id = $descriptor[Descriptor::ID];
584
		$namespace = $descriptor[Descriptor::NS];
585
586
		# models and active records
587
588
		foreach ($descriptor[Descriptor::MODELS] as $model_id => &$definition)
589
		{
590
			if (!is_array($definition))
591
			{
592
				throw new \InvalidArgumentException(\ICanBoogie\format('Model definition must be array, given: %value.', [
593
594
					'value' => $definition
595
596
				]));
597
			}
598
599
			$basename = $id;
600
			$separator_position = strrpos($basename, '.');
601
602
			if ($separator_position)
603
			{
604
				$basename = substr($basename, $separator_position + 1);
605
			}
606
607
			if (empty($definition[Model::NAME]))
608
			{
609
				$definition[Model::NAME] = self::format_model_name($id, $model_id);
610
			}
611
612
			if (empty($definition[Model::ACTIVERECORD_CLASS]))
613
			{
614
				$definition[Model::ACTIVERECORD_CLASS] = $namespace . '\\' . \ICanBoogie\camelize(\ICanBoogie\singularize($model_id == 'primary' ? $basename : $model_id));
615
			}
616
617
			if (empty($definition[Model::CLASSNAME]))
618
			{
619
				$definition[Model::CLASSNAME] = $definition[Model::ACTIVERECORD_CLASS] . 'Model';
620
			}
621
		}
622
623
		return $descriptor;
624
	}
625
626
	/**
627
	 * Traverses the descriptors and create two array of descriptors: one for the disabled modules
628
	 * and the other for enabled modules. The {@link $disabled_modules_descriptors} magic property
629
	 * receives the descriptors of the disabled modules, while the {@link $enabled_modules_descriptors}
630
	 * magic property receives the descriptors of the enabled modules.
631
	 */
632
	private function sort_modules_descriptors()
633
	{
634
		$enabled = [];
635
		$disabled = [];
636
637
		$this->ensure_modules_are_indexed();
0 ignored issues
show
The call to the method ICanBoogie\Module\Module...e_modules_are_indexed() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
638
639
		foreach ($this->descriptors as $module_id => &$descriptor)
640
		{
641
			if (isset($this[$module_id]))
642
			{
643
				$enabled[$module_id] = $descriptor;
644
			}
645
			else
646
			{
647
				$disabled[$module_id] = $descriptor;
648
			}
649
		}
650
651
		$this->enabled_modules_descriptors = $enabled;
652
		$this->disabled_modules_descriptors = $disabled;
653
	}
654
655
	/**
656
	 * Returns the descriptors of the disabled modules.
657
	 *
658
	 * This method is the getter for the {@link $disabled_modules_descriptors} magic property.
659
	 *
660
	 * @return array
661
	 */
662
	protected function lazy_get_disabled_modules_descriptors()
663
	{
664
		$this->sort_modules_descriptors();
665
666
		return $this->disabled_modules_descriptors;
667
	}
668
669
	/**
670
	 * Returns the descriptors of the enabled modules.
671
	 *
672
	 * This method is the getter for the {@link $enabled_modules_descriptors} magic property.
673
	 *
674
	 * @return array
675
	 */
676
	protected function lazy_get_enabled_modules_descriptors()
677
	{
678
		$this->sort_modules_descriptors();
679
680
		return $this->enabled_modules_descriptors;
681
	}
682
683
	/**
684
	 * Returns the paths of the enabled modules which have a `locale` folder.
685
	 *
686
	 * @return array
687
	 */
688 View Code Duplication
	protected function lazy_get_locale_paths()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
689
	{
690
		$paths = [];
691
692
		foreach ($this->enabled_modules_descriptors as $module_id => $descriptor)
693
		{
694
			if (!$descriptor['__has_locale'])
695
			{
696
				continue;
697
			}
698
699
			$paths[] = $descriptor[Descriptor::PATH] . 'locale';
700
		}
701
702
		return $paths;
703
	}
704
705
	/**
706
	 * Returns the paths of the enabled modules which have a `config` folder.
707
	 *
708
	 * @return array
709
	 */
710 View Code Duplication
	protected function lazy_get_config_paths()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
711
	{
712
		$paths = [];
713
714
		foreach ($this->enabled_modules_descriptors as $module_id => $descriptor)
715
		{
716
			if (!$descriptor['__has_config'])
717
			{
718
				continue;
719
			}
720
721
			$paths[$descriptor[Descriptor::PATH] . 'config'] = 0;
722
		}
723
724
		return $paths;
725
	}
726
727
	/**
728
	 * Orders the module ids provided according to module inheritance and weight.
729
	 *
730
	 * @param array $ids The module ids to order.
731
	 * @param array $descriptors Module descriptors.
732
	 *
733
	 * @return array
734
	 */
735
	public function order_ids(array $ids, array $descriptors = null)
736
	{
737
		$ordered = [];
738
		$extends_weight = [];
739
740
		if ($descriptors === null)
741
		{
742
			$descriptors = $this->descriptors;
743
		}
744
745 View Code Duplication
		$count_extends = function($super_id) use (&$count_extends, &$descriptors)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
746
		{
747
			$i = 0;
748
749
			foreach ($descriptors as $module_id => $descriptor)
750
			{
751
				if ($descriptor[Descriptor::INHERITS] !== $super_id)
752
				{
753
					continue;
754
				}
755
756
				$i += 1 + $count_extends($module_id);
757
			}
758
759
			return $i;
760
		};
761
762 View Code Duplication
		$count_required = function($required_id) use (&$descriptors, &$extends_weight)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
763
		{
764
			$i = 0;
765
766
			foreach ($descriptors as $module_id => $descriptor)
767
			{
768
				if (!in_array($required_id, $descriptor[Descriptor::REQUIRES]))
769
				{
770
					continue;
771
				}
772
773
				$i += 1 + $extends_weight[$module_id];
774
			}
775
776
			return $i;
777
		};
778
779
		foreach ($ids as $module_id)
780
		{
781
			$extends_weight[$module_id] = $count_extends($module_id);
782
		}
783
784
		foreach ($ids as $module_id)
785
		{
786
 			$ordered[$module_id] = -$extends_weight[$module_id] -$count_required($module_id) + $descriptors[$module_id][Descriptor::WEIGHT];
787
		}
788
789
		\ICanBoogie\stable_sort($ordered);
790
791
		return array_keys($ordered);
792
	}
793
794
	/**
795
	 * Returns the usage of a module by other modules.
796
	 *
797
	 * @param string $module_id The identifier of the module.
798
	 * @param bool $all One of {@link ONLY_ENABLED_MODULES} or {@link ALL_MODULES}.
799
	 * Default: {@link ONLY_ENABLED_MODULES}.
800
	 *
801
	 * @return int
802
	 */
803
	public function usage($module_id, $all = self::ONLY_ENABLED_MODULES)
804
	{
805
		return count($this->filter_descriptors_by_users($module_id, $all));
806
	}
807
808
	/**
809
	 * Checks if a module inherits from another.
810
	 *
811
	 * @param string $module_id Module identifier.
812
	 * @param string $parent_id Identifier of the parent module.
813
	 *
814
	 * @return boolean `true` if the module inherits from the other.
815
	 */
816
	public function is_inheriting($module_id, $parent_id)
817
	{
818
		while ($module_id)
819
		{
820
			if ($module_id == $parent_id)
821
			{
822
				return true;
823
			}
824
825
			$descriptor = $this->descriptors[$module_id];
826
827
			$module_id = empty($descriptor[Descriptor::INHERITS]) ? null : $descriptor[Descriptor::INHERITS];
828
		}
829
830
		return false;
831
	}
832
833
	/**
834
	 * Install all the enabled modules.
835
	 *
836
	 * @param ErrorCollection|null $errors
837
	 *
838
	 * @return ErrorCollection
839
	 *
840
	 * @throws ModuleCollectionInstallFailed if an error occurs.
841
	 */
842
	public function install(ErrorCollection $errors = null)
843
	{
844
		if (!$errors)
845
		{
846
			$errors = new ErrorCollection;
847
		}
848
849
		foreach (array_keys($this->filter_descriptors(new InstallableModulesFilter($this))) as $module_id)
850
		{
851
			try
852
			{
853
				$this[$module_id]->install($errors);
854
			}
855
			catch (\Exception $e)
856
			{
857
				$errors[$module_id] = $e;
858
			}
859
		}
860
861
		if ($errors->count())
862
		{
863
			throw new ModuleCollectionInstallFailed($errors);
864
		}
865
866
		return $errors;
867
	}
868
869
	/**
870
	 * Resolves a class name using module inheritance.
871
	 *
872
	 * To resolve a given class name, the method checks in each module namespace—starting from the
873
	 * specified module—if the class exists. If it does, it returns its fully qualified name.
874
	 *
875
	 * @param string $unqualified_classname
876
	 * @param string|Module $module_id
877
	 * @param array $tried
878
	 *
879
	 * @return string|false The resolved file name, or `false` if it could not be resolved.
880
	 *
881
	 * @throws ModuleNotDefined if the specified module, or the module specified by
882
	 * {@link Descriptor::INHERITS} is not defined.
883
	 */
884
	public function resolve_classname($unqualified_classname, $module_id, array &$tried = [])
885
	{
886
		if ($module_id instanceof Module)
887
		{
888
			$module_id = $module_id->id;
889
		}
890
891
		while ($module_id)
892
		{
893
			$this->assert_module_is_defined($module_id);
894
895
			$descriptor = $this->descriptors[$module_id];
896
			$fully_qualified_classname = $descriptor[Descriptor::NS] . '\\' . $unqualified_classname;
897
			$tried[] = $fully_qualified_classname;
898
899
			if (class_exists($fully_qualified_classname, true))
900
			{
901
				return $fully_qualified_classname;
902
			}
903
904
			$module_id = $descriptor[Descriptor::INHERITS];
905
		}
906
907
		return false;
908
	}
909
910
	/**
911
	 * Changes a module availability.
912
	 *
913
	 * @param string $module_id
914
	 * @param bool $available
915
	 */
916
	protected function change_module_availability($module_id, $available)
917
	{
918
		$this->ensure_modules_are_indexed();
0 ignored issues
show
The call to the method ICanBoogie\Module\Module...e_modules_are_indexed() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
919
920
		if (empty($this->descriptors[$module_id]))
921
		{
922
			return;
923
		}
924
925
		$this->descriptors[$module_id][Descriptor::DISABLED] = $available;
926
		$this->revoke_constructions();
927
	}
928
929
	/**
930
	 * Ensures that modules are indexed, index them if not.
931
	 */
932
	protected function ensure_modules_are_indexed()
933
	{
934
		$this->index;
935
	}
936
937
	/**
938
	 * Asserts that a module is defined.
939
	 *
940
	 * @param string $module_id Module identifier.
941
	 *
942
	 * @throws ModuleNotDefined if the module is not defined.
943
	 */
944
	protected function assert_module_is_defined($module_id)
945
	{
946
		if (empty($this->descriptors[$module_id]))
947
		{
948
			throw new ModuleNotDefined($module_id);
949
		}
950
	}
951
952
	/**
953
	 * Asserts that a module is enabled.
954
	 *
955
	 * @param string $module_id
956
	 *
957
	 * @throws ModuleIsDisabled if the module is disabled.
958
	 */
959
	protected function assert_module_is_enabled($module_id)
960
	{
961
		if (!empty($this->descriptors[$module_id][Descriptor::DISABLED]))
962
		{
963
			throw new ModuleIsDisabled($module_id);
964
		}
965
	}
966
967
	/**
968
	 * Asserts that a module constructor exists.
969
	 *
970
	 * @param string $module_id Module identifier.
971
	 * @param string $class Constructor class.
972
	 */
973
	protected function assert_constructor_exists($module_id, $class)
974
	{
975
		if (!class_exists($class, true))
976
		{
977
			throw new ModuleConstructorMissing($module_id, $class);
978
		}
979
	}
980
981
	/**
982
	 * Instantiate a module.
983
	 *
984
	 * @param string $module_id Module identifier.
985
	 *
986
	 * @return Module
987
	 */
988
	protected function instantiate_module($module_id)
989
	{
990
		$this->assert_module_is_defined($module_id);
991
		$this->assert_module_is_enabled($module_id);
992
993
		$descriptor = $this->descriptors[$module_id];
994
		$class = $descriptor[Descriptor::CLASSNAME];
995
996
		$this->assert_constructor_exists($module_id, $class);
997
998
		$parent = &$descriptor[Descriptor::INHERITS];
999
1000
		if ($parent)
1001
		{
1002
			$parent = $this[$parent];
1003
		}
1004
1005
		return new $class($this, $descriptor);
1006
	}
1007
1008
	/**
1009
	 * Defines module constants.
1010
	 *
1011
	 * @param array $descriptors
1012
	 */
1013
	protected function define_constants(array $descriptors)
1014
	{
1015
		foreach ($descriptors as $descriptor)
1016
		{
1017
			$namespace = $descriptor[Descriptor::NS];
1018
			$constant = $namespace . '\DIR';
1019
1020
			if (!defined($constant))
1021
			{
1022
				define($constant, $descriptor[Descriptor::PATH]);
1023
			}
1024
		}
1025
	}
1026
}
1027