Completed
Push — try/custom-autoloader ( 8f11c5...90b010 )
by
unknown
13:30 queued 06:34
created

AutoloadGenerator::addClassMapCode()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 8
dl 0
loc 35
rs 8.7377
c 0
b 0
f 0

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
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.Files.FileName.NotHyphenatedLowercase
15
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
16
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export
17
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
18
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen
19
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fwrite
20
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
21
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
22
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
23
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
24
25
namespace Automattic\Jetpack\Autoloader;
26
27
use Composer\Autoload\AutoloadGenerator as BaseGenerator;
28
use Composer\Autoload\ClassMapGenerator;
29
use Composer\Config;
30
use Composer\Installer\InstallationManager;
31
use Composer\IO\IOInterface;
32
use Composer\Package\PackageInterface;
33
use Composer\Repository\InstalledRepositoryInterface;
34
use Composer\Util\Filesystem;
35
36
/**
37
 * Class AutoloadGenerator.
38
 */
39
class AutoloadGenerator extends BaseGenerator {
40
41
	/**
42
	 * Whether or not generated autoloader considers the class map authoritative.
43
	 *
44
	 * @var bool Whether or not generated autoloader considers the class map authoritative.
45
	 */
46
	private $classMapAuthoritative = false;
47
48
	/**
49
	 * Instantiate an AutoloadGenerator object.
50
	 *
51
	 * @param IOInterface $io IO object.
52
	 */
53
	public function __construct( $io ) {
54
		$this->io = $io;
55
	}
56
57
	/**
58
	 * Dump the autoloader.
59
	 *
60
	 * @param Config                       $config Config object.
61
	 * @param InstalledRepositoryInterface $localRepo Installed Reposetories object.
62
	 * @param PackageInterface             $mainPackage Main Package object.
63
	 * @param InstallationManager          $installationManager Manager for installing packages.
64
	 * @param string                       $targetDir Path to the current target directory.
65
	 * @param bool                         $scanPsr0Packages Whether to search for packages. Currently hard coded to always be false.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $scanPsr0Packages not be boolean|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...
66
	 * @param string                       $suffix The autoloader suffix, ignored since we want our autoloader to only be included once.
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...
67
	 */
68
	public function dump(
69
		Config $config,
70
		InstalledRepositoryInterface $localRepo,
71
		PackageInterface $mainPackage,
72
		InstallationManager $installationManager,
73
		$targetDir,
74
		$scanPsr0Packages = null, // Not used we always optimize.
75
		$suffix = null // Not used since we create our own autoloader.
76
	) {
77
78
		$filesystem = new Filesystem();
79
		$filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) );
80
81
		$basePath   = $filesystem->normalizePath( realpath( getcwd() ) );
82
		$vendorPath = $filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) );
83
		$targetDir  = $vendorPath . '/' . $targetDir;
84
		$filesystem->ensureDirectoryExists( $targetDir );
85
86
		$vendorPathCode = $filesystem->findShortestPathCode( realpath( $targetDir ), $vendorPath, true );
87
88
		$appBaseDirCode = $filesystem->findShortestPathCode( $vendorPath, $basePath, true );
89
		$appBaseDirCode = str_replace( '__DIR__', '$vendorDir', $appBaseDirCode );
90
91
		$packageMap = $this->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() );
92
		$autoloads  = $this->parseAutoloads( $packageMap, $mainPackage );
93
94
		$classMap = $this->getClassMapFromAutoloads( $autoloads, $filesystem, $vendorPath, $basePath );
95
96
		// Write out the autoload_classmap_package.php file.
97
		$classmapFile  = <<<EOF
98
<?php
99
100
// This file `autoload_packages.php`was generated by automattic/jetpack-autoloader.
101
102
\$vendorDir = $vendorPathCode;
103
\$baseDir = $appBaseDirCode;
104
105
106
EOF;
107
		$classmapFile .= 'return ' . $this->classMapToPHPArrayString( $classMap );
108
		file_put_contents( $targetDir . '/autoload_classmap_package.php', $classmapFile );
109
		$this->io->writeError( '<info>Generated autoload_classmap_package.php</info>', true );
110
111
		if ( ! $suffix ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $suffix of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
112
			$suffix = $config->get( 'autoloader-suffix' )
113
				? $config->get( 'autoloader-suffix' )
114
				: md5( uniqid( '', true ) );
115
		}
116
		// Copy over the autoload.php file into autoload_packages.php.
117
		file_put_contents( $vendorPath . '/autoload_packages.php', $this->getAutoloadPackageFile( $suffix ) );
118
		$this->io->writeError( '<info>Generated autoload_packages.php</info>', true );
119
120
	}
121
122
	/**
123
	 * Takes a classMap and returns the array string representation.
124
	 *
125
	 * @param array $classMap Map of all the package classes and paths and versions.
126
	 *
127
	 * @return string
128
	 */
129
	private function classMapToPHPArrayString( $classMap ) {
130
		$classmapString = ' array( ';
131
		ksort( $classMap );
132
		foreach ( $classMap as $class => $code ) {
133
			$classmapString .= '    ' . var_export( $class, true ) . ' => ' . $code;
134
		}
135
		$classmapString .= ");\n";
136
		return $classmapString;
137
	}
138
139
	/**
140
	 * This function differs from the composer parseAutoloadsType in that beside returning the path.
141
	 * It also return the path and the version of a package.
142
	 *
143
	 * @param array            $packageMap Map of all the packages.
144
	 * @param string           $type Type of autoloader to use, currently not used, since we only support psr-4.
145
	 * @param PackageInterface $mainPackage Instance of the Package Object.
146
	 *
147
	 * @return array
148
	 */
149
	protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) {
150
		$autoloads = array();
151
		foreach ( $packageMap as $item ) {
152
			list($package, $installPath) = $item;
153
			$autoload                    = $package->getAutoload();
154
155
			if ( $package === $mainPackage ) {
156
				$autoload = array_merge_recursive( $autoload, $package->getDevAutoload() );
157
			}
158
159
			// Skip packages that are not 'psr-4' since we only support them for now.
160
			if ( ! isset( $autoload['psr-4'] ) || ! is_array( $autoload['psr-4'] ) ) {
161
				continue;
162
			}
163
164
			if ( null !== $package->getTargetDir() && $package !== $mainPackage ) {
165
				$installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) );
166
			}
167
			foreach ( $autoload['psr-4'] as $namespace => $paths ) {
168
				foreach ( (array) $paths as $path ) {
169
					$relativePath              = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
170
					$autoloads[ $namespace ][] = array(
171
						'path'    => $relativePath,
172
						'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
173
					);
174
				}
175
			}
176
		}
177
		return $autoloads;
178
	}
179
180
	/**
181
	 * Take the autoloads array and return the classMap that contains the path and the version for each namespace.
182
	 *
183
	 * @param array      $autoloads Array of autoload settings defined defined by the packages.
184
	 * @param Filesystem $filesystem Filesystem class instance.
185
	 * @param string     $vendorPath Path to the vendor directory.
186
	 * @param string     $basePath Base Path.
187
	 *
188
	 * @return array $classMap
189
	 */
190
	private function getClassMapFromAutoloads( $autoloads, $filesystem, $vendorPath, $basePath ) {
191
192
		$classMap = array();
193
194
		$namespacesToScan = array();
195
		$blacklist        = null; // not supported for now.
196
197
		// Scan the PSR-4 directories for class files, and add them to the class map.
198
		foreach ( $autoloads['psr-4'] as $namespace => $info ) {
199
200
			$version = array_reduce(
201
				array_map(
202
					function( $item ) {
203
						return $item['version']; },
204
					$info
205
				),
206
				function( $carry, $version ) {
207
					// Are we using a dev version since we assumer that is the latest version.
208
					if ( 'dev-' === substr( $version, 0, 4 ) ) {
209
						return $version;
210
					}
211
					return version_compare( $version, $carry, '>' ) ? $version : $carry;
212
				},
213
				0
214
			);
215
216
			$namespacesToScan[ $namespace ][] = array(
217
				'paths'   => array_map(
218
					function( $item ) {
219
						return $item['path']; },
220
					$info
221
				),
222
				'version' => $version,
223
			);
224
		}
225
226
		krsort( $namespacesToScan );
227
228
		foreach ( $namespacesToScan as $namespace => $groups ) {
229
230
			foreach ( $groups as $group ) {
231
232
				foreach ( $group['paths'] as $dir ) {
233
					$dir = $filesystem->normalizePath( $filesystem->isAbsolutePath( $dir ) ? $dir : $basePath . '/' . $dir );
234
235
					if ( ! is_dir( $dir ) ) {
236
						continue;
237
					}
238
239
					$namespaceFilter = '' === $namespace ? null : $namespace;
240
					$classMap        = $this->addClassMapCode(
241
						$filesystem,
242
						$basePath,
243
						$vendorPath,
244
						$dir,
245
						$blacklist,
246
						$namespaceFilter,
247
						$group['version'],
248
						$classMap
249
					);
250
				}
251
			}
252
		}
253
254
		return $classMap;
255
	}
256
257
	/**
258
	 * Add a single class map resolution.
259
	 *
260
	 * @param Filesystem $filesystem Filesystem class instance.
261
	 * @param string     $basePath Base path.
262
	 * @param string     $vendorPath Path to the vendor diretory.
263
	 * @param string     $dir Direcotry path.
264
	 * @param null       $blacklist Blacklist of namespaces set to be ignored currently not used.
265
	 * @param null       $namespaceFilter Namespace being used.
266
	 * @param string     $version The version of the package.
267
	 * @param array      $classMap The current classMap.
268
	 *
269
	 * @return array
270
	 */
271
	private function addClassMapCode(
272
		$filesystem,
273
		$basePath,
274
		$vendorPath,
275
		$dir,
276
		$blacklist = null,
277
		$namespaceFilter = null,
278
		$version,
279
280
		array $classMap = array()
281
	) {
282
283
		foreach ( $this->generateClassMap( $dir, $blacklist, $namespaceFilter ) as $class => $path ) {
284
			$pathCode = "array( 'path' => " . $this->getPathCode( $filesystem, $basePath, $vendorPath, $path ) . ", 'version'=>'" . $version . "' ),\n";
285
286
			if ( ! isset( $classMap[ $class ] ) ) {
287
				$classMap[ $class ] = $pathCode;
288
			} elseif ( $this->io && $classMap[ $class ] !== $pathCode && ! preg_match(
289
				'{/(test|fixture|example|stub)s?/}i',
290
				strtr( $classMap[ $class ] . ' ' . $path, '\\', '/' )
291
			)
292
			) {
293
				$this->io->writeError(
294
					'<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
295
					' was found in both "' . str_replace(
296
						array( '$vendorDir . \'', "',\n" ),
297
						array( $vendorPath, '' ),
298
						$classMap[ $class ]
299
					) . '" and "' . $path . '", the first will be used.</warning>'
300
				);
301
			}
302
		}
303
304
		return $classMap;
305
	}
306
307
	/**
308
	 * Trigger the class map generation.
309
	 *
310
	 * @param string $dir  Directory path.
311
	 * @param null   $blacklist Blacklist of namespaces set to be ignored currently not used.
312
	 * @param null   $namespaceFilter Namespace being used.
313
	 * @param bool   $showAmbiguousWarning Whether to show a warning in the console.
314
	 *
315
	 * @return array
316
	 */
317
	private function generateClassMap( $dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true ) {
318
		return ClassMapGenerator::createMap(
319
			$dir,
320
			$blacklist,
321
			$showAmbiguousWarning ? $this->io : null,
322
			$namespaceFilter
323
		);
324
	}
325
326
	/**
327
	 * Generate the PHP that will be used in the autoload_packages.php files.
328
	 *
329
	 * @param string $suffix  Unique suffix added to the jetpack_enqueue_packages function.
330
	 *
331
	 * @return string
332
	 */
333
	private function getAutoloadPackageFile( $suffix ) {
334
		$sourceLoader   = fopen( __DIR__ . '/autoload.php', 'r' );
335
		$file_contents  = stream_get_contents( $sourceLoader );
336
		$file_contents .= <<<INCLUDE_FILES
337
/**
338
 * Prepare all the classes for autoloading.
339
 */
340
function jetpack_enqueue_packages_$suffix() {
341
	\$class_map = require_once dirname( __FILE__ ) . '/composer/autoload_classmap_package.php';
342
	foreach ( \$class_map as \$class_name => \$class_info ) {
343
		jetpack_enqueue_package( \$class_name, \$class_info['version'], \$class_info['path'] );
344
	}
345
}
346
347
jetpack_enqueue_packages_$suffix();
348
		
349
INCLUDE_FILES;
350
351
		return $file_contents;
352
	}
353
}
354