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

AutoloadGenerator::generateClassMap()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
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