Completed
Push — add/jetpack_connection_new_met... ( 975e37...16d930 )
by
unknown
25:10 queued 15:42
created

AutoloadGenerator::dump()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 32
rs 9.408
c 0
b 0
f 0
nc 2
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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