Completed
Push — try/custom-autoloader ( baedcd...b37bdc )
by
unknown
12:55 queued 06:13
created

AutoloadGenerator::addClassMapCode()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 8
dl 0
loc 34
rs 8.7537
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
26
namespace Automattic\Jetpack\Autoloader;
27
28
use Composer\Autoload\AutoloadGenerator as BaseGenerator;
29
use Composer\Autoload\ClassMapGenerator;
30
use Composer\Config;
31
use Composer\Installer\InstallationManager;
32
use Composer\IO\IOInterface;
33
use Composer\Package\PackageInterface;
34
use Composer\Repository\InstalledRepositoryInterface;
35
use Composer\Util\Filesystem;
36
37
/**
38
 * Class AutoloadGenerator.
39
 */
40
class AutoloadGenerator extends BaseGenerator {
41
42
	/**
43
	 * Instantiate an AutoloadGenerator object.
44
	 *
45
	 * @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...
46
	 */
47
	public function __construct( IOInterface $io = null ) {
48
		$this->io = $io;
49
	}
50
51
	/**
52
	 * Dump the autoloader.
53
	 *
54
	 * @param Config                       $config Config object.
55
	 * @param InstalledRepositoryInterface $localRepo Installed Reposetories object.
56
	 * @param PackageInterface             $mainPackage Main Package object.
57
	 * @param InstallationManager          $installationManager Manager for installing packages.
58
	 * @param string                       $targetDir Path to the current target directory.
59
	 * @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...
60
	 * @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...
61
	 */
62
	public function dump(
63
		Config $config,
64
		InstalledRepositoryInterface $localRepo,
65
		PackageInterface $mainPackage,
66
		InstallationManager $installationManager,
67
		$targetDir,
68
		$scanPsr0Packages = null, // Not used we always optimize.
69
		$suffix = null // Not used since we create our own autoloader.
70
	) {
71
72
		$filesystem = new Filesystem();
73
		$filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) );
74
75
		$basePath   = $filesystem->normalizePath( realpath( getcwd() ) );
76
		$vendorPath = $filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) );
77
		$targetDir  = $vendorPath . '/' . $targetDir;
78
		$filesystem->ensureDirectoryExists( $targetDir );
79
80
		$vendorPathCode = $filesystem->findShortestPathCode( realpath( $targetDir ), $vendorPath, true );
81
82
		$appBaseDirCode = $filesystem->findShortestPathCode( $vendorPath, $basePath, true );
83
		$appBaseDirCode = str_replace( '__DIR__', '$vendorDir', $appBaseDirCode );
84
85
		$packageMap = $this->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() );
86
		$autoloads  = $this->parseAutoloads( $packageMap, $mainPackage );
87
88
		$classMap = $this->getClassMapFromAutoloads( $autoloads, $filesystem, $vendorPath, $basePath );
89
90
		// Write out the autoload_classmap_package.php file.
91
		$classmapFile  = <<<EOF
92
<?php
93
94
// This file `autoload_packages.php`was generated by automattic/jetpack-autoloader.
95
96
\$vendorDir = $vendorPathCode;
97
\$baseDir = $appBaseDirCode;
98
99
100
EOF;
101
		$classmapFile .= 'return ' . $this->classMapToPHPArrayString( $classMap );
102
		file_put_contents( $targetDir . '/autoload_classmap_package.php', $classmapFile );
103
		$this->io->writeError( '<info>Generated autoload_classmap_package.php</info>', true );
104
105
		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...
106
			$suffix = $config->get( 'autoloader-suffix' )
107
				? $config->get( 'autoloader-suffix' )
108
				: md5( uniqid( '', true ) );
109
		}
110
		// Copy over the autoload.php file into autoload_packages.php.
111
		file_put_contents( $vendorPath . '/autoload_packages.php', $this->getAutoloadPackageFile( $suffix ) );
112
		$this->io->writeError( '<info>Generated autoload_packages.php</info>', true );
113
114
	}
115
116
	/**
117
	 * Takes a classMap and returns the array string representation.
118
	 *
119
	 * @param array $classMap Map of all the package classes and paths and versions.
120
	 *
121
	 * @return string
122
	 */
123
	private function classMapToPHPArrayString( array $classMap ) {
124
		$classmapString = ' array( ';
125
		ksort( $classMap );
126
		foreach ( $classMap as $class => $code ) {
127
			$classmapString .= '    ' . var_export( $class, true ) . ' => ' . $code;
128
		}
129
		$classmapString .= ");\n";
130
		return $classmapString;
131
	}
132
133
	/**
134
	 * This function differs from the composer parseAutoloadsType in that beside returning the path.
135
	 * It also return the path and the version of a package.
136
	 *
137
	 * @param array            $packageMap Map of all the packages.
138
	 * @param string           $type Type of autoloader to use, currently not used, since we only support psr-4.
139
	 * @param PackageInterface $mainPackage Instance of the Package Object.
140
	 *
141
	 * @return array
142
	 */
143
	protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) {
144
		$autoloads = array();
145
		foreach ( $packageMap as $item ) {
146
			list($package, $installPath) = $item;
147
			$autoload                    = $package->getAutoload();
148
149
			if ( $package === $mainPackage ) {
150
				$autoload = array_merge_recursive( $autoload, $package->getDevAutoload() );
151
			}
152
153
			// Skip packages that are not 'psr-4' since we only support them for now.
154
			if ( ! isset( $autoload['psr-4'] ) || ! is_array( $autoload['psr-4'] ) ) {
155
				continue;
156
			}
157
158
			if ( null !== $package->getTargetDir() && $package !== $mainPackage ) {
159
				$installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) );
160
			}
161
			foreach ( $autoload['psr-4'] as $namespace => $paths ) {
162
				foreach ( (array) $paths as $path ) {
163
					$relativePath              = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
164
					$autoloads[ $namespace ][] = array(
165
						'path'    => $relativePath,
166
						'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
167
					);
168
				}
169
			}
170
		}
171
		return $autoloads;
172
	}
173
174
	/**
175
	 * Take the autoloads array and return the classMap that contains the path and the version for each namespace.
176
	 *
177
	 * @param array      $autoloads Array of autoload settings defined defined by the packages.
178
	 * @param Filesystem $filesystem Filesystem class instance.
179
	 * @param string     $vendorPath Path to the vendor directory.
180
	 * @param string     $basePath Base Path.
181
	 *
182
	 * @return array $classMap
183
	 */
184
	private function getClassMapFromAutoloads( array $autoloads, Filesystem $filesystem, $vendorPath, $basePath ) {
185
186
		$classMap = array();
187
188
		$namespacesToScan = array();
189
		$blacklist        = null; // not supported for now.
190
191
		// Scan the PSR-4 directories for class files, and add them to the class map.
192
		foreach ( $autoloads['psr-4'] as $namespace => $info ) {
193
194
			$version = array_reduce(
195
				array_map(
196
					function( $item ) {
197
						return $item['version']; },
198
					$info
199
				),
200
				function( $carry, $version ) {
201
					// Are we using a dev version since we assumer that is the latest version.
202
					if ( 'dev-' === substr( $version, 0, 4 ) ) {
203
						return $version;
204
					}
205
					return version_compare( $version, $carry, '>' ) ? $version : $carry;
206
				},
207
				0
208
			);
209
210
			$namespacesToScan[ $namespace ][] = array(
211
				'paths'   => array_map(
212
					function( $item ) {
213
						return $item['path']; },
214
					$info
215
				),
216
				'version' => $version,
217
			);
218
		}
219
220
		krsort( $namespacesToScan );
221
222
		foreach ( $namespacesToScan as $namespace => $groups ) {
223
224
			foreach ( $groups as $group ) {
225
226
				foreach ( $group['paths'] as $dir ) {
227
					$dir = $filesystem->normalizePath( $filesystem->isAbsolutePath( $dir ) ? $dir : $basePath . '/' . $dir );
228
229
					if ( ! is_dir( $dir ) ) {
230
						continue;
231
					}
232
233
					$namespaceFilter = '' === $namespace ? null : $namespace;
234
					$classMap        = $this->addClassMapCode(
235
						$filesystem,
236
						$basePath,
237
						$vendorPath,
238
						$dir,
239
						$blacklist,
240
						$namespaceFilter,
241
						$group['version'],
242
						$classMap
243
					);
244
				}
245
			}
246
		}
247
248
		return $classMap;
249
	}
250
251
	/**
252
	 * Add a single class map resolution.
253
	 *
254
	 * @param Filesystem $filesystem Filesystem class instance.
255
	 * @param string     $basePath Base path.
256
	 * @param string     $vendorPath Path to the vendor diretory.
257
	 * @param string     $dir Direcotry path.
258
	 * @param null       $blacklist Blacklist of namespaces set to be ignored currently not used.
259
	 * @param null       $namespaceFilter Namespace being used.
260
	 * @param string     $version The version of the package.
261
	 * @param array      $classMap The current classMap.
262
	 *
263
	 * @return array
264
	 */
265
	private function addClassMapCode(
266
		Filesystem $filesystem,
267
		$basePath,
268
		$vendorPath,
269
		$dir,
270
		$blacklist = null,
271
		$namespaceFilter = null,
272
		$version,
273
		array $classMap = array()
274
	) {
275
		$map = ClassMapGenerator::createMap( $dir, null, $this->io, $namespaceFilter );
276
		foreach ( $map as $class => $path ) {
277
			$pathCode = "array( 'path' => " . $this->getPathCode( $filesystem, $basePath, $vendorPath, $path ) . ", 'version'=>'" . $version . "' ),\n";
278
279
			if ( ! isset( $classMap[ $class ] ) ) {
280
				$classMap[ $class ] = $pathCode;
281
			} elseif ( $this->io && $classMap[ $class ] !== $pathCode && ! preg_match(
282
				'{/(test|fixture|example|stub)s?/}i',
283
				strtr( $classMap[ $class ] . ' ' . $path, '\\', '/' )
284
			)
285
			) {
286
				$this->io->writeError(
287
					'<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
288
					' was found in both "' . str_replace(
289
						array( '$vendorDir . \'', "',\n" ),
290
						array( $vendorPath, '' ),
291
						$classMap[ $class ]
292
					) . '" and "' . $path . '", the first will be used.</warning>'
293
				);
294
			}
295
		}
296
297
		return $classMap;
298
	}
299
	
300
	/**
301
	 * Generate the PHP that will be used in the autoload_packages.php files.
302
	 *
303
	 * @param string $suffix  Unique suffix added to the jetpack_enqueue_packages function.
304
	 *
305
	 * @return string
306
	 */
307
	private function getAutoloadPackageFile( $suffix ) {
308
		$sourceLoader   = fopen( __DIR__ . '/autoload.php', 'r' );
309
		$file_contents  = stream_get_contents( $sourceLoader );
310
		$file_contents .= <<<INCLUDE_FILES
311
/**
312
 * Prepare all the classes for autoloading.
313
 */
314
function enqueue_packages_$suffix() {
315
	\$class_map = require_once dirname( __FILE__ ) . '/composer/autoload_classmap_package.php';
316
	foreach ( \$class_map as \$class_name => \$class_info ) {
317
		enqueue_package( \$class_name, \$class_info['version'], \$class_info['path'] );
318
	}
319
}
320
321
enqueue_packages_$suffix();
322
		
323
INCLUDE_FILES;
324
325
		return $file_contents;
326
	}
327
}
328