Completed
Push — update/show-recurring-payments... ( 106a5e...b030b6 )
by
unknown
415:44 queued 407:29
created

AutoloadGenerator   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 354
Duplicated Lines 6.78 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 3
dl 24
loc 354
rs 6
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A dump() 0 32 2
A parseAutoloads() 0 22 1
A sortPackageMap() 0 22 3
A getFileIdentifier() 0 3 1
B getPathCode() 0 28 7
F parseAutoloadsType() 24 56 29
A processAutoloads() 0 24 3
A removeLegacyFiles() 0 13 2
A writeAutoloaderFiles() 0 9 1
A writeManifests() 0 28 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AutoloadGenerator 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 AutoloadGenerator, and based on these observations, apply Extract Interface, too.

1
<?php // phpcs:ignore WordPress.Files.FileName
2
/**
3
 * Autoloader Generator.
4
 *
5
 * @package automattic/jetpack-autoloader
6
 */
7
8
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_useFound
9
// phpcs:disable PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound
10
// phpcs:disable PHPCompatibility.FunctionDeclarations.NewClosure.Found
11
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_namespaceFound
12
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_dirFound
13
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
14
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export
15
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
16
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen
17
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fwrite
18
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
19
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
20
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
21
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
22
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
23
24
namespace Automattic\Jetpack\Autoloader;
25
26
use Composer\Autoload\ClassMapGenerator;
27
use Composer\Composer;
28
use Composer\Config;
29
use Composer\Installer\InstallationManager;
30
use Composer\IO\IOInterface;
31
use Composer\Package\PackageInterface;
32
use Composer\Repository\InstalledRepositoryInterface;
33
use Composer\Util\Filesystem;
34
use Composer\Util\PackageSorter;
35
36
/**
37
 * Class AutoloadGenerator.
38
 */
39
class AutoloadGenerator {
40
41
	/**
42
	 * The filesystem utility.
43
	 *
44
	 * @var Filesystem
45
	 */
46
	private $filesystem;
47
48
	/**
49
	 * Instantiate an AutoloadGenerator object.
50
	 *
51
	 * @param IOInterface $io IO object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $io not be null|IOInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
52
	 */
53
	public function __construct( IOInterface $io = null ) {
54
		$this->io         = $io;
0 ignored issues
show
Bug introduced by
The property io does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
55
		$this->filesystem = new Filesystem();
56
	}
57
58
	/**
59
	 * Dump the Jetpack autoloader files.
60
	 *
61
	 * @param Composer                     $composer The Composer object.
62
	 * @param Config                       $config Config object.
63
	 * @param InstalledRepositoryInterface $localRepo Installed Repository object.
64
	 * @param PackageInterface             $mainPackage Main Package object.
65
	 * @param InstallationManager          $installationManager Manager for installing packages.
66
	 * @param string                       $targetDir Path to the current target directory.
67
	 * @param bool                         $scanPsrPackages Whether or not PSR packages should be converted to a classmap.
68
	 * @param string                       $suffix The autoloader suffix.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $suffix not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
69
	 */
70
	public function dump(
71
		Composer $composer,
72
		Config $config,
73
		InstalledRepositoryInterface $localRepo,
74
		PackageInterface $mainPackage,
75
		InstallationManager $installationManager,
76
		$targetDir,
77
		$scanPsrPackages = false,
78
		$suffix = null
79
	) {
80
		$this->filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) );
81
82
		$packageMap = $composer->getAutoloadGenerator()->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() );
83
		$autoloads  = $this->parseAutoloads( $packageMap, $mainPackage );
84
85
		// Convert the autoloads into a format that the manifest generator can consume more easily.
86
		$basePath           = $this->filesystem->normalizePath( realpath( getcwd() ) );
87
		$vendorPath         = $this->filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) );
88
		$processedAutoloads = $this->processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath );
89
		unset( $packageMap, $autoloads );
90
91
		// Make sure none of the legacy files remain that can lead to problems with the autoloader.
92
		$this->removeLegacyFiles( $vendorPath );
93
94
		// Write all of the files now that we're done.
95
		$this->writeAutoloaderFiles( $vendorPath . '/jetpack-autoloader/', $suffix );
96
		$this->writeManifests( $vendorPath . '/' . $targetDir, $processedAutoloads );
97
98
		if ( ! $scanPsrPackages ) {
99
			$this->io->writeError( '<warning>You are generating an unoptimized autoloader. If this is a production build, consider using the -o option.</warning>' );
100
		}
101
	}
102
103
	/**
104
	 * Compiles an ordered list of namespace => path mappings
105
	 *
106
	 * @param  array            $packageMap  Array of array(package, installDir-relative-to-composer.json).
107
	 * @param  PackageInterface $mainPackage Main package instance.
108
	 *
109
	 * @return array The list of path mappings.
110
	 */
111
	public function parseAutoloads( array $packageMap, PackageInterface $mainPackage ) {
112
		$rootPackageMap = array_shift( $packageMap );
113
114
		$sortedPackageMap   = $this->sortPackageMap( $packageMap );
115
		$sortedPackageMap[] = $rootPackageMap;
116
		array_unshift( $packageMap, $rootPackageMap );
117
118
		$psr0     = $this->parseAutoloadsType( $packageMap, 'psr-0', $mainPackage );
119
		$psr4     = $this->parseAutoloadsType( $packageMap, 'psr-4', $mainPackage );
120
		$classmap = $this->parseAutoloadsType( array_reverse( $sortedPackageMap ), 'classmap', $mainPackage );
121
		$files    = $this->parseAutoloadsType( $sortedPackageMap, 'files', $mainPackage );
122
123
		krsort( $psr0 );
124
		krsort( $psr4 );
125
126
		return array(
127
			'psr-0'    => $psr0,
128
			'psr-4'    => $psr4,
129
			'classmap' => $classmap,
130
			'files'    => $files,
131
		);
132
	}
133
134
	/**
135
	 * Sorts packages by dependency weight
136
	 *
137
	 * Packages of equal weight retain the original order
138
	 *
139
	 * @param  array $packageMap The package map.
140
	 * @return array
141
	 */
142
	protected function sortPackageMap( array $packageMap ) {
143
		$packages = array();
144
		$paths    = array();
145
146
		foreach ( $packageMap as $item ) {
147
			list( $package, $path ) = $item;
148
			$name                   = $package->getName();
149
			$packages[ $name ]      = $package;
150
			$paths[ $name ]         = $path;
151
		}
152
153
		$sortedPackages = PackageSorter::sortPackages( $packages );
154
155
		$sortedPackageMap = array();
156
157
		foreach ( $sortedPackages as $package ) {
158
			$name               = $package->getName();
159
			$sortedPackageMap[] = array( $packages[ $name ], $paths[ $name ] );
160
		}
161
162
		return $sortedPackageMap;
163
	}
164
165
	/**
166
	 * Returns the file identifier.
167
	 *
168
	 * @param PackageInterface $package The package instance.
169
	 * @param string           $path The path.
170
	 */
171
	protected function getFileIdentifier( PackageInterface $package, $path ) {
172
		return md5( $package->getName() . ':' . $path );
173
	}
174
175
	/**
176
	 * Returns the path code for the given path.
177
	 *
178
	 * @param Filesystem $filesystem The filesystem instance.
179
	 * @param string     $basePath The base path.
180
	 * @param string     $vendorPath The vendor path.
181
	 * @param string     $path The path.
182
	 *
183
	 * @return string The path code.
184
	 */
185
	protected function getPathCode( Filesystem $filesystem, $basePath, $vendorPath, $path ) {
186
		if ( ! $filesystem->isAbsolutePath( $path ) ) {
187
			$path = $basePath . '/' . $path;
188
		}
189
		$path = $filesystem->normalizePath( $path );
190
191
		$baseDir = '';
192
		if ( 0 === strpos( $path . '/', $vendorPath . '/' ) ) {
193
			$path    = substr( $path, strlen( $vendorPath ) );
194
			$baseDir = '$vendorDir';
195
196
			if ( false !== $path ) {
197
				$baseDir .= ' . ';
198
			}
199
		} else {
200
			$path = $filesystem->normalizePath( $filesystem->findShortestPath( $basePath, $path, true ) );
201
			if ( ! $filesystem->isAbsolutePath( $path ) ) {
202
				$baseDir = '$baseDir . ';
203
				$path    = '/' . $path;
204
			}
205
		}
206
207
		if ( strpos( $path, '.phar' ) !== false ) {
208
			$baseDir = "'phar://' . " . $baseDir;
209
		}
210
211
		return $baseDir . ( ( false !== $path ) ? var_export( $path, true ) : '' );
212
	}
213
214
	/**
215
	 * This function differs from the composer parseAutoloadsType in that beside returning the path.
216
	 * It also return the path and the version of a package.
217
	 *
218
	 * Supports PSR-4, PSR-0, and classmap parsing.
219
	 *
220
	 * @param array            $packageMap Map of all the packages.
221
	 * @param string           $type Type of autoloader to use.
222
	 * @param PackageInterface $mainPackage Instance of the Package Object.
223
	 *
224
	 * @return array
225
	 */
226
	protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) {
227
		$autoloads = array();
228
229
		foreach ( $packageMap as $item ) {
230
			list($package, $installPath) = $item;
231
			$autoload                    = $package->getAutoload();
232
233
			if ( $package === $mainPackage ) {
234
				$autoload = array_merge_recursive( $autoload, $package->getDevAutoload() );
235
			}
236
237
			if ( null !== $package->getTargetDir() && $package !== $mainPackage ) {
238
				$installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) );
239
			}
240
241
			if ( in_array( $type, array( 'psr-4', 'psr-0' ), true ) && isset( $autoload[ $type ] ) && is_array( $autoload[ $type ] ) ) {
242
				foreach ( $autoload[ $type ] as $namespace => $paths ) {
243
					$paths = is_array( $paths ) ? $paths : array( $paths );
244
					foreach ( $paths as $path ) {
245
						$relativePath              = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
246
						$autoloads[ $namespace ][] = array(
247
							'path'    => $relativePath,
248
							'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
249
						);
250
					}
251
				}
252
			}
253
254 View Code Duplication
			if ( 'classmap' === $type && isset( $autoload['classmap'] ) && is_array( $autoload['classmap'] ) ) {
255
				foreach ( $autoload['classmap'] as $paths ) {
256
					$paths = is_array( $paths ) ? $paths : array( $paths );
257
					foreach ( $paths as $path ) {
258
						$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
259
						$autoloads[]  = array(
260
							'path'    => $relativePath,
261
							'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
262
						);
263
					}
264
				}
265
			}
266 View Code Duplication
			if ( 'files' === $type && isset( $autoload['files'] ) && is_array( $autoload['files'] ) ) {
267
				foreach ( $autoload['files'] as $paths ) {
268
					$paths = is_array( $paths ) ? $paths : array( $paths );
269
					foreach ( $paths as $path ) {
270
						$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
271
						$autoloads[ $this->getFileIdentifier( $package, $path ) ] = array(
272
							'path'    => $relativePath,
273
							'version' => $package->getVersion(), // Version of the file comes from the package - should we try to parse it?
274
						);
275
					}
276
				}
277
			}
278
		}
279
280
		return $autoloads;
281
	}
282
283
	/**
284
	 * Given Composer's autoloads this will convert them to a version that we can use to generate the manifests.
285
	 *
286
	 * When the $scanPsrPackages argument is true, PSR-4 namespaces are converted to classmaps. When $scanPsrPackages
287
	 * is false, PSR-4 namespaces are not converted to classmaps.
288
	 *
289
	 * PSR-0 namespaces are always converted to classmaps.
290
	 *
291
	 * @param array  $autoloads The autoloads we want to process.
292
	 * @param bool   $scanPsrPackages Whether or not PSR-4 packages should be converted to a classmap.
293
	 * @param string $vendorPath The path to the vendor directory.
294
	 * @param string $basePath The path to the current directory.
295
	 *
296
	 * @return array $processedAutoloads
297
	 */
298
	private function processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ) {
299
		$processor = new AutoloadProcessor(
300
			function ( $path, $excludedClasses, $namespace ) use ( $basePath ) {
301
				$dir = $this->filesystem->normalizePath(
302
					$this->filesystem->isAbsolutePath( $path ) ? $path : $basePath . '/' . $path
303
				);
304
				return ClassMapGenerator::createMap(
305
					$dir,
306
					$excludedClasses,
307
					null, // Don't pass the IOInterface since the normal autoload generation will have reported already.
308
					empty( $namespace ) ? null : $namespace
309
				);
310
			},
311
			function ( $path ) use ( $basePath, $vendorPath ) {
312
				return $this->getPathCode( $this->filesystem, $basePath, $vendorPath, $path );
313
			}
314
		);
315
316
		return array(
317
			'psr-4'    => $processor->processPsr4Packages( $autoloads, $scanPsrPackages ),
318
			'classmap' => $processor->processClassmap( $autoloads, $scanPsrPackages ),
319
			'files'    => $processor->processFiles( $autoloads ),
320
		);
321
	}
322
323
	/**
324
	 * Removes all of the legacy autoloader files so they don't cause any problems.
325
	 *
326
	 * @param string $outDir The directory legacy files are written to.
327
	 */
328
	private function removeLegacyFiles( $outDir ) {
329
		$files = array(
330
			'autoload_functions.php',
331
			'class-autoloader-handler.php',
332
			'class-classes-handler.php',
333
			'class-files-handler.php',
334
			'class-plugins-handler.php',
335
			'class-version-selector.php',
336
		);
337
		foreach ( $files as $file ) {
338
			$this->filesystem->remove( $outDir . '/' . $file );
339
		}
340
	}
341
342
	/**
343
	 * Writes all of the autoloader files to disk.
344
	 *
345
	 * @param string $outDir The directory to write to.
346
	 * @param string $suffix The unique autoloader suffix.
347
	 */
348
	private function writeAutoloaderFiles( $outDir, $suffix ) {
349
		$this->io->writeError( "<info>Generating jetpack autoloader ($outDir)</info>" );
350
351
		// We will remove all autoloader files to generate this again.
352
		$this->filesystem->emptyDirectory( $outDir );
353
354
		// Write the autoloader files.
355
		AutoloadFileWriter::copyAutoloaderFiles( $this->io, $outDir, $suffix );
356
	}
357
358
	/**
359
	 * Writes all of the manifest files to disk.
360
	 *
361
	 * @param string $outDir The directory to write to.
362
	 * @param array  $processedAutoloads The processed autoloads.
363
	 */
364
	private function writeManifests( $outDir, $processedAutoloads ) {
365
		$this->io->writeError( "<info>Generating jetpack autoloader manifests ($outDir)</info>" );
366
367
		$manifestFiles = array(
368
			'classmap' => 'jetpack_autoload_classmap.php',
369
			'psr-4'    => 'jetpack_autoload_psr4.php',
370
			'files'    => 'jetpack_autoload_filemap.php',
371
		);
372
373
		foreach ( $manifestFiles as $key => $file ) {
374
			// Make sure the file doesn't exist so it isn't there if we don't write it.
375
			$this->filesystem->remove( $outDir . '/' . $file );
376
			if ( empty( $processedAutoloads[ $key ] ) ) {
377
				continue;
378
			}
379
380
			$content = ManifestGenerator::buildManifest( $key, $file, $processedAutoloads[ $key ] );
381
			if ( empty( $content ) ) {
382
				continue;
383
			}
384
385
			if ( file_put_contents( $outDir . '/' . $file, $content ) ) {
386
				$this->io->writeError( "  <info>Generated: $file</info>" );
387
			} else {
388
				$this->io->writeError( "  <error>Error: $file</error>" );
389
			}
390
		}
391
	}
392
}
393