Bootstrap   F
last analyzed

Complexity

Total Complexity 77

Size/Duplication

Total Lines 435
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 77
eloc 146
c 1
b 0
f 0
dl 0
loc 435
rs 2.24

15 Methods

Rating   Name   Duplication   Size   Complexity  
B autoload() 0 27 8
A addManifests() 0 23 6
A registerAutoloader() 0 12 3
A getExtensions() 0 14 4
A getI18nPaths() 0 16 4
A addDependencies() 0 24 6
A getIncludePaths() 0 16 4
A getI18nList() 0 22 6
A getSetupPaths() 0 23 5
A getManifestFile() 0 18 3
A getTemplatePaths() 0 16 5
A getCustomPaths() 0 12 3
B getManifests() 0 33 9
A getConfigPaths() 0 16 4
B __construct() 0 25 7

How to fix   Complexity   

Complex Class

Complex classes like Bootstrap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Bootstrap, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2021
7
 */
8
9
10
namespace Aimeos;
11
12
13
/**
14
 * Global starting point for applicatons.
15
 */
16
class Bootstrap
17
{
18
	private $manifests = [];
19
	private $extensions = [];
20
	private $extensionsDone = [];
21
	private $dependencies = [];
22
	private static $includePaths = [];
23
	private static $autoloader = false;
24
25
26
	/**
27
	 * Initialises the object.
28
	 *
29
	 * @param array $extdirs List of directories to look for manifest files (or sub-directories thereof)
30
	 * @param boolean $defaultdir If default extension directory should be included automatically
31
	 * @param string|null $basedir Aimeos core path (optional, __DIR__ if null)
32
	 */
33
	public function __construct( array $extdirs = [], bool $defaultdir = true, string $basedir = null )
34
	{
35
		$basedir = $basedir ?: __DIR__;
36
		$class = '\Composer\InstalledVersions';
37
38
		if( $defaultdir && is_dir( $basedir . DIRECTORY_SEPARATOR . 'ext' ) ) {
39
			$extdirs[] = realpath( $basedir . DIRECTORY_SEPARATOR . 'ext' );
40
		}
41
42
		if( class_exists( $class ) && method_exists( $class, 'getInstalledPackagesByType' ) )
43
		{
44
			$packages = \Composer\InstalledVersions::getInstalledPackagesByType( 'aimeos-extension' );
45
46
			foreach( $packages as $package ) {
47
				$extdirs[] = realpath( \Composer\InstalledVersions::getInstallPath( $package ) );
0 ignored issues
show
Bug introduced by
It seems like Composer\InstalledVersio...etInstallPath($package) can also be of type null; however, parameter $path of realpath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

47
				$extdirs[] = realpath( /** @scrutinizer ignore-type */ \Composer\InstalledVersions::getInstallPath( $package ) );
Loading history...
48
			}
49
		}
50
51
		$this->manifests[$basedir] = $this->getManifestFile( $basedir );
52
53
		self::$includePaths = $this->getIncludePaths();
54
		$this->registerAutoloader();
55
		$this->addDependencies( $extdirs );
56
		$this->addManifests( $this->dependencies );
57
		self::$includePaths = $this->getIncludePaths();
58
	}
59
60
61
	/**
62
	 * Loads the class files for a given class name.
63
	 *
64
	 * @param string $className Name of the class
65
	 * @return bool True if file was found, false if not
66
	 */
67
	public static function autoload( string $className ) : bool
68
	{
69
		$fileName = strtr( ltrim( $className, '\\' ), '\\_', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR ) . '.php';
70
71
		if( strncmp( $fileName, 'Aimeos' . DIRECTORY_SEPARATOR, 7 ) === 0 ) {
72
			$fileName = substr( $fileName, 7 );
73
		}
74
75
		foreach( self::$includePaths as $path )
76
		{
77
			$file = $path . DIRECTORY_SEPARATOR . $fileName;
78
79
			if( file_exists( $file ) === true && ( include_once $file ) !== false ) {
80
				return true;
81
			}
82
		}
83
84
		foreach( explode( PATH_SEPARATOR, get_include_path() ) as $path )
85
		{
86
			$file = $path . DIRECTORY_SEPARATOR . $fileName;
87
88
			if( file_exists( $file ) === true && ( include_once $file ) !== false ) {
89
				return true;
90
			}
91
		}
92
93
		return false;
94
	}
95
96
97
	/**
98
	 * Returns the paths containing the required configuration files.
99
	 *
100
	 * @return string[] List of configuration paths
101
	 */
102
	public function getConfigPaths() : array
103
	{
104
		$confpaths = [];
105
106
		foreach( $this->manifests as $path => $manifest )
107
		{
108
			if( !isset( $manifest['config'] ) ) {
109
				continue;
110
			}
111
112
			foreach( (array) $manifest['config'] as $relpath ) {
113
				$confpaths[] = $path . DIRECTORY_SEPARATOR . $relpath;
114
			}
115
		}
116
117
		return $confpaths;
118
	}
119
120
121
	/**
122
	 * Returns the paths stored in the manifest file for the given custom section.
123
	 *
124
	 * @param string $section Name of the section like in the manifest file
125
	 * @return array List of paths
126
	 */
127
	public function getCustomPaths( string $section ) : array
128
	{
129
		$paths = [];
130
131
		foreach( $this->manifests as $path => $manifest )
132
		{
133
			if( isset( $manifest['custom'][$section] ) ) {
134
				$paths[$path] = $manifest['custom'][$section];
135
			}
136
		}
137
138
		return $paths;
139
	}
140
141
142
	/**
143
	 * Returns the available extensions
144
	 *
145
	 * @return array List of available extension names
146
	 */
147
	public function getExtensions() : array
148
	{
149
		$list = [];
150
151
		foreach( $this->manifests as $path => $manifest )
152
		{
153
			if( isset( $manifest['name'] ) && $manifest['name'] != '' ) {
154
				$list[] = $manifest['name'];
155
			} else {
156
				$list[] = basename( $path );
157
			}
158
		}
159
160
		return $list;
161
	}
162
163
164
	/**
165
	 * Returns the language IDs for the available translations
166
	 *
167
	 * @param string $section Section name in the i18n paths
168
	 * @return array List of ISO language codes
169
	 */
170
	public function getI18nList( string $section ) : array
171
	{
172
		$list = [];
173
		$paths = $this->getI18nPaths();
174
		$paths = ( isset( $paths[$section] ) ? (array) $paths[$section] : [] );
175
176
		foreach( $paths as $path )
177
		{
178
			$iter = new \DirectoryIterator( $path );
179
180
			foreach( $iter as $file )
181
			{
182
				$name = $file->getFilename();
183
184
				if( $file->isFile() && preg_match( '/^[a-z]{2,3}(_[A-Z]{2})?$/', $name ) ) {
185
					$list[$name] = null;
186
				}
187
			}
188
		}
189
190
		ksort( $list );
191
		return array_keys( $list );
192
	}
193
194
195
	/**
196
	 * Returns the list of paths for each domain where the translation files are located.
197
	 *
198
	 * @return array Associative list of i18n domains and lists of absolute paths to the translation directories
199
	 */
200
	public function getI18nPaths() : array
201
	{
202
		$paths = [];
203
204
		foreach( $this->manifests as $basePath => $manifest )
205
		{
206
			if( !isset( $manifest['i18n'] ) ) {
207
				continue;
208
			}
209
210
			foreach( $manifest['i18n'] as $domain => $location ) {
211
				$paths[$domain][] = $basePath . DIRECTORY_SEPARATOR . $location;
212
			}
213
		}
214
215
		return $paths;
216
	}
217
218
219
	/**
220
	 * Returns the include paths containing the required class files.
221
	 *
222
	 * @return array List of include paths
223
	 */
224
	public function getIncludePaths() : array
225
	{
226
		$includes = [];
227
228
		foreach( $this->manifests as $path => $manifest )
229
		{
230
			if( !isset( $manifest['include'] ) ) {
231
				continue;
232
			}
233
234
			foreach( $manifest['include'] as $paths ) {
235
				$includes[] = $path . DIRECTORY_SEPARATOR . $paths;
236
			}
237
		}
238
239
		return $includes;
240
	}
241
242
243
	/**
244
	 * Returns the list of paths where setup tasks are stored.
245
	 *
246
	 * @param string $site Name of the site like "default", "unitperf" and "unittest"
247
	 * @return array List of setup paths
248
	 */
249
	public function getSetupPaths( string $site ) : array
250
	{
251
		$setupPaths = [];
252
253
		foreach( $this->manifests as $path => $manifest )
254
		{
255
			if( !isset( $manifest['setup'] ) ) {
256
				continue;
257
			}
258
259
			foreach( $manifest['setup'] as $relpath )
260
			{
261
				$setupPaths[] = $path . DIRECTORY_SEPARATOR . $relpath;
262
263
				$sitePath = $path . DIRECTORY_SEPARATOR . $relpath . DIRECTORY_SEPARATOR . $site;
264
265
				if( is_dir( realpath( $sitePath ) ) ) {
266
					$setupPaths[] = $sitePath;
267
				}
268
			}
269
		}
270
271
		return $setupPaths;
272
	}
273
274
275
	/**
276
	 * Returns the template paths stored in the manifest file for the given section and theme.
277
	 *
278
	 * @param string $section Name of the section like in the manifest file
279
	 * @param string|null $theme Name of the theme to get specific template paths for
280
	 * @return array List of paths
281
	 */
282
	public function getTemplatePaths( string $section, string $theme = null ) : array
283
	{
284
		$paths = [];
285
286
		foreach( $this->manifests as $path => $manifest )
287
		{
288
			if( isset( $manifest['template'][$section] ) ) {
289
				$paths[$path] = $manifest['template'][$section];
290
			}
291
292
			if( $theme && isset( $manifest['template'][$theme][$section] ) ) {
293
				$paths[$path] = $manifest['template'][$theme][$section];
294
			}
295
		}
296
297
		return $paths;
298
	}
299
300
301
	/**
302
	 * Returns the configurations of the manifest files in the given directories.
303
	 *
304
	 * @param array $directories List of directories where the manifest files are stored
305
	 * @return array Associative list of directory / configuration array pairs
306
	 */
307
	protected function getManifests( array $directories ) : array
308
	{
309
		$manifests = [];
310
311
		foreach( $directories as $directory )
312
		{
313
			$manifest = $this->getManifestFile( $directory );
314
315
			if( $manifest !== false )
316
			{
317
				$manifests[$directory] = $manifest;
318
				continue;
319
			}
320
321
			if( file_exists( $directory ) )
322
			{
323
				$dir = new \DirectoryIterator( $directory );
324
325
				foreach( $dir as $dirinfo )
326
				{
327
					if( $dirinfo->isDir() === false || $dirinfo->isDot() !== false
328
						|| substr( $dirinfo->getFilename(), 0, 1 ) === '.'
329
						|| ( $manifest = $this->getManifestFile( $dirinfo->getPathName() ) ) === false
330
					) {
331
						continue;
332
					}
333
334
					$manifests[$dirinfo->getPathName()] = $manifest;
335
				}
336
			}
337
		}
338
339
		return $manifests;
340
	}
341
342
343
	/**
344
	 * Loads the manifest file from the given directory.
345
	 *
346
	 * @param string $dir Directory that includes the manifest file
347
	 * @return array|false Associative list of configurations or false if the file doesn't exist
348
	 */
349
	protected function getManifestFile( string $dir )
350
	{
351
		$manifestFile = $dir . DIRECTORY_SEPARATOR . 'manifest.php';
352
353
		if( file_exists( $manifestFile ) )
354
		{
355
			try
356
			{
357
				return include $manifestFile;
358
			}
359
			catch( \Throwable $t )
360
			{
361
				echo $manifestFile . ':' . PHP_EOL . $t->getMessage() . PHP_EOL . PHP_EOL . $t->getTraceAsString() . PHP_EOL;
362
				exit( 1 );
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
363
			}
364
		}
365
366
		return false;
367
	}
368
369
370
	/**
371
	 * Registers the Aimeos autoloader.
372
	 */
373
	protected function registerAutoloader()
374
	{
375
		if( self::$autoloader === false )
376
		{
377
			spl_autoload_register( array( $this, 'autoload' ), true, false );
378
			self::$autoloader = true;
379
		}
380
381
		$ds = DIRECTORY_SEPARATOR;
382
383
		if( is_file( __DIR__ . $ds . 'vendor' . $ds . 'autoload.php' ) ) {
384
			require __DIR__ . $ds . 'vendor' . $ds . 'autoload.php';
385
		}
386
	}
387
388
389
	/**
390
	 * Adds the dependencies from the extensions
391
	 *
392
	 * @param array $extdirs List of extension directories
393
	 * @throws \Exception If dependencies are incorrectly configured
394
	 */
395
	private function addDependencies( array $extdirs )
396
	{
397
		foreach( $this->getManifests( $extdirs ) as $location => $manifest )
398
		{
399
			if( isset( $this->extensions[$manifest['name']] ) )
400
			{
401
				$location2 = $this->extensions[$manifest['name']]['location'];
402
				$msg = 'Extension "%1$s" exists twice in "%2$s" and in "%3$s"';
403
				throw new \Exception( sprintf( $msg, $manifest['name'], $location, $location2 ) );
404
			}
405
406
			if( !isset( $manifest['depends'] ) || !is_array( $manifest['depends'] ) ) {
407
				throw new \Exception( sprintf( 'Incorrect dependency configuration in manifest "%1$s"', $location ) );
408
			}
409
410
			$manifest['location'] = $location;
411
			$this->extensions[$manifest['name']] = $manifest;
412
413
			foreach( $manifest['depends'] as $name ) {
414
				$this->dependencies[$manifest['name']][$name] = $name;
415
			}
416
		}
417
418
		ksort( $this->dependencies );
419
	}
420
421
422
	/**
423
	 * Re-order the given dependencies of each manifest configuration.
424
	 *
425
	 * @param array $deps List of dependencies
426
	 * @param array $stack List of task names that are scheduled after this task
427
	 */
428
	private function addManifests( array $deps, array $stack = [] )
429
	{
430
		foreach( $deps as $extName => $name )
431
		{
432
			if( in_array( $extName, $this->extensionsDone ) ) {
433
				continue;
434
			}
435
436
			if( in_array( $extName, $stack ) ) {
437
				throw new \Exception( sprintf( 'Circular dependency for "%1$s" detected', $extName ) );
438
			}
439
440
			$stack[] = $extName;
441
442
			if( isset( $this->dependencies[$extName] ) ) {
443
				$this->addManifests( (array) $this->dependencies[$extName], $stack );
444
			}
445
446
			if( isset( $this->extensions[$extName] ) ) {
447
				$this->manifests[$this->extensions[$extName]['location']] = $this->extensions[$extName];
448
			}
449
450
			$this->extensionsDone[] = $extName;
451
		}
452
	}
453
}
454