Completed
Push — try/custom-autoloader ( baedcd...b37bdc )
by
unknown
12:55 queued 06:13
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
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