Completed
Push — renovate/mocha-8.x ( 07030e...e8e64c )
by
unknown
28:17 queued 19:09
created

AutoloadGenerator   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 292
Duplicated Lines 15.75 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 46
loc 292
rs 8.4
c 0
b 0
f 0
wmc 50
lcom 1
cbo 2

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A dump() 0 31 2
F parseAutoloadsType() 36 60 32
A processAutoloads() 0 24 3
A removeLegacyFiles() 0 13 2
A writeAutoloaderFiles() 5 29 4
A writeManifests() 5 28 5
A getAutoloadPackageFile() 0 14 1

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.ValidVariableName.UsedPropertyNotSnakeCase
19
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
20
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
21
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
22
23
24
namespace Automattic\Jetpack\Autoloader;
25
26
use Composer\Autoload\AutoloadGenerator as BaseGenerator;
27
use Composer\Autoload\ClassMapGenerator;
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
35
/**
36
 * Class AutoloadGenerator.
37
 */
38
class AutoloadGenerator extends BaseGenerator {
39
40
	const COMMENT = <<<AUTOLOADER_COMMENT
41
/**
42
 * This file was automatically generated by automattic/jetpack-autoloader.
43
 *
44
 * @package automattic/jetpack-autoloader
45
 */
46
47
AUTOLOADER_COMMENT;
48
49
	/**
50
	 * The filesystem utility.
51
	 *
52
	 * @var Filesystem
53
	 */
54
	private $filesystem;
55
56
	/**
57
	 * Instantiate an AutoloadGenerator object.
58
	 *
59
	 * @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...
60
	 */
61
	public function __construct( IOInterface $io = null ) {
62
		$this->io         = $io;
63
		$this->filesystem = new Filesystem();
64
	}
65
66
	/**
67
	 * Dump the Jetpack autoloader files.
68
	 *
69
	 * @param Config                       $config Config object.
70
	 * @param InstalledRepositoryInterface $localRepo Installed Reposetories object.
71
	 * @param PackageInterface             $mainPackage Main Package object.
72
	 * @param InstallationManager          $installationManager Manager for installing packages.
73
	 * @param string                       $targetDir Path to the current target directory.
74
	 * @param bool                         $scanPsrPackages Whether or not PSR packages should be converted to a classmap.
75
	 * @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...
76
	 */
77
	public function dump(
78
		Config $config,
79
		InstalledRepositoryInterface $localRepo,
80
		PackageInterface $mainPackage,
81
		InstallationManager $installationManager,
82
		$targetDir,
83
		$scanPsrPackages = false,
84
		$suffix = null
85
	) {
86
		$this->filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) );
87
88
		$packageMap = $this->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() );
89
		$autoloads  = $this->parseAutoloads( $packageMap, $mainPackage );
90
91
		// Convert the autoloads into a format that the manifest generator can consume more easily.
92
		$basePath           = $this->filesystem->normalizePath( realpath( getcwd() ) );
93
		$vendorPath         = $this->filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) );
94
		$processedAutoloads = $this->processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath );
95
		unset( $packageMap, $autoloads );
96
97
		// Make sure none of the legacy files remain that can lead to problems with the autoloader.
98
		$this->removeLegacyFiles( $vendorPath );
99
100
		// Write all of the files now that we're done.
101
		$this->writeAutoloaderFiles( $vendorPath . '/jetpack-autoloader/', $suffix );
102
		$this->writeManifests( $vendorPath . '/' . $targetDir, $processedAutoloads );
103
104
		if ( ! $scanPsrPackages ) {
105
			$this->io->writeError( '<warning>You are generating an unoptimized autoloader. If this is a production build, consider using the -o option.</warning>' );
106
		}
107
	}
108
109
	/**
110
	 * This function differs from the composer parseAutoloadsType in that beside returning the path.
111
	 * It also return the path and the version of a package.
112
	 *
113
	 * Currently supports only psr-4 and clasmap parsing.
114
	 *
115
	 * @param array            $packageMap Map of all the packages.
116
	 * @param string           $type Type of autoloader to use, currently not used, since we only support psr-4.
117
	 * @param PackageInterface $mainPackage Instance of the Package Object.
118
	 *
119
	 * @return array
120
	 */
121
	protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) {
122
		$autoloads = array();
123
124
		if ( 'psr-4' !== $type && 'classmap' !== $type && 'files' !== $type ) {
125
			return parent::parseAutoloadsType( $packageMap, $type, $mainPackage );
126
		}
127
128
		foreach ( $packageMap as $item ) {
129
			list($package, $installPath) = $item;
130
			$autoload                    = $package->getAutoload();
131
132
			if ( $package === $mainPackage ) {
133
				$autoload = array_merge_recursive( $autoload, $package->getDevAutoload() );
134
			}
135
136
			if ( null !== $package->getTargetDir() && $package !== $mainPackage ) {
137
				$installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) );
138
			}
139
140 View Code Duplication
			if ( 'psr-4' === $type && isset( $autoload['psr-4'] ) && is_array( $autoload['psr-4'] ) ) {
141
				foreach ( $autoload['psr-4'] as $namespace => $paths ) {
142
					$paths = is_array( $paths ) ? $paths : array( $paths );
143
					foreach ( $paths as $path ) {
144
						$relativePath              = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
145
						$autoloads[ $namespace ][] = array(
146
							'path'    => $relativePath,
147
							'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
148
						);
149
					}
150
				}
151
			}
152
153 View Code Duplication
			if ( 'classmap' === $type && isset( $autoload['classmap'] ) && is_array( $autoload['classmap'] ) ) {
154
				foreach ( $autoload['classmap'] as $paths ) {
155
					$paths = is_array( $paths ) ? $paths : array( $paths );
156
					foreach ( $paths as $path ) {
157
						$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
158
						$autoloads[]  = array(
159
							'path'    => $relativePath,
160
							'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
161
						);
162
					}
163
				}
164
			}
165 View Code Duplication
			if ( 'files' === $type && isset( $autoload['files'] ) && is_array( $autoload['files'] ) ) {
166
				foreach ( $autoload['files'] as $paths ) {
167
					$paths = is_array( $paths ) ? $paths : array( $paths );
168
					foreach ( $paths as $path ) {
169
						$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
170
						$autoloads[ $this->getFileIdentifier( $package, $path ) ] = array(
171
							'path'    => $relativePath,
172
							'version' => $package->getVersion(), // Version of the file comes from the package - should we try to parse it?
173
						);
174
					}
175
				}
176
			}
177
		}
178
179
		return $autoloads;
180
	}
181
182
	/**
183
	 * Given Composer's autoloads this will convert them to a version that we can use to generate the manifests.
184
	 *
185
	 * @param array  $autoloads The autoloads we want to process.
186
	 * @param bool   $scanPsrPackages Whether or not PSR packages should be converted to a classmap.
187
	 * @param string $vendorPath The path to the vendor directory.
188
	 * @param string $basePath The path to the current directory.
189
	 *
190
	 * @return array $processedAutoloads
191
	 */
192
	private function processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ) {
193
		$processor = new AutoloadProcessor(
194
			function ( $path, $excludedClasses, $namespace ) use ( $basePath ) {
195
				$dir = $this->filesystem->normalizePath(
196
					$this->filesystem->isAbsolutePath( $path ) ? $path : $basePath . '/' . $path
197
				);
198
				return ClassMapGenerator::createMap(
199
					$dir,
200
					$excludedClasses,
201
					null, // Don't pass the IOInterface since the normal autoload generation will have reported already.
202
					empty( $namespace ) ? null : $namespace
203
				);
204
			},
205
			function ( $path ) use ( $basePath, $vendorPath ) {
206
				return $this->getPathCode( $this->filesystem, $basePath, $vendorPath, $path );
207
			}
208
		);
209
210
		return array(
211
			'psr-4'    => $processor->processPsr4Packages( $autoloads, $scanPsrPackages ),
212
			'classmap' => $processor->processClassmap( $autoloads, $scanPsrPackages ),
213
			'files'    => $processor->processFiles( $autoloads ),
214
		);
215
	}
216
217
	/**
218
	 * Removes all of the legacy autoloader files so they don't cause any problems.
219
	 *
220
	 * @param string $outDir The directory legacy files are written to.
221
	 */
222
	private function removeLegacyFiles( $outDir ) {
223
		$files = array(
224
			'autoload_functions.php',
225
			'class-autoloader-handler.php',
226
			'class-classes-handler.php',
227
			'class-files-handler.php',
228
			'class-plugins-handler.php',
229
			'class-version-selector.php',
230
		);
231
		foreach ( $files as $file ) {
232
			$this->filesystem->remove( $outDir . '/' . $file );
233
		}
234
	}
235
236
	/**
237
	 * Writes all of the autoloader files to disk.
238
	 *
239
	 * @param string $outDir The directory to write to.
240
	 * @param string $suffix The unique autoloader suffix.
241
	 */
242
	private function writeAutoloaderFiles( $outDir, $suffix ) {
243
		$this->io->writeError( "<info>Generating jetpack autoloader ($outDir)</info>" );
244
245
		// We will remove all autoloader files to generate this again.
246
		$this->filesystem->emptyDirectory( $outDir );
247
248
		$packageFiles = array(
249
			'autoload.php'                 => '../autoload_packages.php',
250
			'functions.php'                => 'autoload_functions.php',
251
			'class-autoloader-locator.php' => null,
252
			'class-autoloader-handler.php' => null,
253
			'class-manifest-handler.php'   => null,
254
			'class-plugins-handler.php'    => null,
255
			'class-version-selector.php'   => null,
256
			'class-version-loader.php'     => null,
257
		);
258
259
		foreach ( $packageFiles as $file => $newFile ) {
260
			$newFile = isset( $newFile ) ? $newFile : $file;
261
262
			$content = $this->getAutoloadPackageFile( $file, $suffix );
263
264 View Code Duplication
			if ( file_put_contents( $outDir . '/' . $newFile, $content ) ) {
265
				$this->io->writeError( "  <info>Generated: $newFile</info>" );
266
			} else {
267
				$this->io->writeError( "  <error>Error: $newFile</error>" );
268
			}
269
		}
270
	}
271
272
	/**
273
	 * Writes all of the manifest files to disk.
274
	 *
275
	 * @param string $outDir The directory to write to.
276
	 * @param array  $processedAutoloads The processed autoloads.
277
	 */
278
	private function writeManifests( $outDir, $processedAutoloads ) {
279
		$this->io->writeError( "<info>Generating jetpack autoloader manifests ($outDir)</info>" );
280
281
		$manifestFiles = array(
282
			'classmap' => 'jetpack_autoload_classmap.php',
283
			'psr-4'    => 'jetpack_autoload_psr4.php',
284
			'files'    => 'jetpack_autoload_filemap.php',
285
		);
286
287
		foreach ( $manifestFiles as $key => $file ) {
288
			// Make sure the file doesn't exist so it isn't there if we don't write it.
289
			$this->filesystem->remove( $outDir . '/' . $file );
290
			if ( empty( $processedAutoloads[ $key ] ) ) {
291
				continue;
292
			}
293
294
			$content = ManifestGenerator::buildManifest( $key, $file, $processedAutoloads[ $key ] );
295
			if ( empty( $content ) ) {
296
				continue;
297
			}
298
299 View Code Duplication
			if ( file_put_contents( $outDir . '/' . $file, $content ) ) {
300
				$this->io->writeError( "  <info>Generated: $file</info>" );
301
			} else {
302
				$this->io->writeError( "  <error>Error: $file</error>" );
303
			}
304
		}
305
	}
306
307
	/**
308
	 * Generate the PHP that will be used in the autoload_packages.php files.
309
	 *
310
	 * @param String $filename a file to prepare.
311
	 * @param String $suffix   Unique suffix used in the namespace.
312
	 *
313
	 * @return string
314
	 */
315
	private function getAutoloadPackageFile( $filename, $suffix ) {
316
		$header  = self::COMMENT;
317
		$header .= PHP_EOL;
318
		$header .= 'namespace Automattic\Jetpack\Autoloader\jp' . $suffix . ';';
319
		$header .= PHP_EOL . PHP_EOL;
320
321
		$sourceLoader  = fopen( __DIR__ . '/' . $filename, 'r' );
322
		$file_contents = stream_get_contents( $sourceLoader );
323
		return str_replace(
324
			'/* HEADER */',
325
			$header,
326
			$file_contents
327
		);
328
	}
329
}
330