Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make use of composer/class-map-generator and deprecate ClassMapGenerator class #10885

Merged
merged 2 commits into from Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -24,6 +24,7 @@
"require": {
"php": "^7.2.5 || ^8.0",
"composer/ca-bundle": "^1.0",
"composer/class-map-generator": "^1.0",
"composer/metadata-minifier": "^1.0",
"composer/semver": "^3.0",
"composer/spdx-licenses": "^1.5.7",
Expand Down
75 changes: 74 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

149 changes: 67 additions & 82 deletions src/Composer/Autoload/AutoloadGenerator.php
Expand Up @@ -12,13 +12,15 @@

namespace Composer\Autoload;

use Composer\ClassMapGenerator\ClassMapGenerator;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
Expand All @@ -43,7 +45,7 @@ class AutoloadGenerator
private $eventDispatcher;

/**
* @var ?IOInterface
* @var IOInterface
*/
private $io;

Expand Down Expand Up @@ -80,7 +82,7 @@ class AutoloadGenerator
public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null)
{
$this->eventDispatcher = $eventDispatcher;
$this->io = $io;
$this->io = $io ?? new NullIO();

$this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
}
Expand Down Expand Up @@ -195,6 +197,9 @@ public function dump(Config $config, InstalledRepositoryInterface $localRepo, Ro
));
}

$classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']);
$classMapGenerator->avoidDuplicateScans();

$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
// Do not remove double realpath() calls.
Expand Down Expand Up @@ -273,18 +278,6 @@ public function dump(Config $config, InstalledRepositoryInterface $localRepo, Ro
}
$psr4File .= ");\n";

$classmapFile = <<<EOF
<?php

// autoload_classmap.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;

// add custom psr-0 autoloading if the root package has a target dir
$targetDirLoader = null;
$mainAutoload = $rootPackage->getAutoload();
Expand Down Expand Up @@ -323,11 +316,8 @@ public static function autoload(\$class)
$excluded = $autoloads['exclude-from-classmap'];
}

$classMap = array();
$ambiguousClasses = array();
$scannedFiles = array();
foreach ($autoloads['classmap'] as $dir) {
$classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $excluded, null, null, $classMap, $ambiguousClasses, $scannedFiles);
$classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded));
}

if ($scanPsrPackages) {
Expand All @@ -350,25 +340,47 @@ public static function autoload(\$class)
continue;
}

$classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $excluded, $namespace, $group['type'], $classMap, $ambiguousClasses, $scannedFiles);
$classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded), $group['type'], $namespace);
}
}
}
}

foreach ($ambiguousClasses as $className => $ambiguousPaths) {
$cleanPath = str_replace(array('$vendorDir . \'', '$baseDir . \'', "',\n"), array($vendorPath, $basePath, ''), $classMap[$className]);

$this->io->writeError(
'<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$cleanPath.'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
);
$classMap = $classMapGenerator->getClassMap();
foreach ($classMap->getAmbiguousClasses() as $className => $ambiguousPaths) {
if (count($ambiguousPaths) > 1) {
$this->io->writeError(
'<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
);
} else {
$this->io->writeError(
'<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
' was found in both "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
);
}
}
foreach ($classMap->getPsrViolations() as $msg) {
$this->io->writeError("<warning>$msg</warning>");
}

$classMap['Composer\\InstalledVersions'] = "\$vendorDir . '/composer/InstalledVersions.php',\n";
ksort($classMap);
foreach ($classMap as $class => $code) {
$classmapFile .= ' '.var_export($class, true).' => '.$code;
$classMap->addClass('Composer\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php');
$classMap->sort();

$classmapFile = <<<EOF
<?php

// autoload_classmap.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;
foreach ($classMap->getMap() as $className => $path) {
$pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
$classmapFile .= ' '.var_export($className, true).' => '.$pathCode;
}
$classmapFile .= ");\n";

Expand Down Expand Up @@ -433,67 +445,36 @@ public static function autoload(\$class)
));
}

return count($classMap);
return \count($classMap);
}

/**
* @param string $basePath
* @param string $vendorPath
* @param string $dir
* @param null|array<int, string> $excluded
* @param null|string $namespaceFilter
* @param null|string $autoloadType
* @param array<class-string, string> $classMap
* @param array<class-string, array<int, string>> $ambiguousClasses
* @param array<string, true> $scannedFiles
* @return array<class-string, string>
* @param array<string>|null $excluded
* @return non-empty-string|null
*/
private function addClassMapCode(Filesystem $filesystem, string $basePath, string $vendorPath, string $dir, ?array $excluded, ?string $namespaceFilter, ?string $autoloadType, array $classMap, array &$ambiguousClasses, array &$scannedFiles): array
private function buildExclusionRegex(string $dir, ?array $excluded): ?string
{
foreach ($this->generateClassMap($dir, $excluded, $namespaceFilter, $autoloadType, true, $scannedFiles) as $class => $path) {
$pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
if (!isset($classMap[$class])) {
$classMap[$class] = $pathCode;
} elseif ($this->io && $classMap[$class] !== $pathCode && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) {
$ambiguousClasses[$class][] = $path;
}
if (null === $excluded) {
return null;
}

return $classMap;
}

/**
* @param string $dir
* @param null|array<int, string> $excluded
* @param null|string $namespaceFilter
* @param null|string $autoloadType
* @param bool $showAmbiguousWarning
* @param array<string, true> $scannedFiles
* @return array<class-string, string>
*/
private function generateClassMap(string $dir, ?array $excluded, ?string $namespaceFilter, ?string $autoloadType, bool $showAmbiguousWarning, array &$scannedFiles): array
{
if ($excluded) {
// filter excluded patterns here to only use those matching $dir
// exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work
// if $dir does not exist, it should anyway not find anything there so no trouble
if (file_exists($dir)) {
// transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other
$dirMatch = preg_quote(strtr(realpath($dir), '\\', '/'));
foreach ($excluded as $index => $pattern) {
// extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character
$pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern);
// if the pattern is not a subset or superset of $dir, it is unrelated and we skip it
if (0 !== strpos($pattern, $dirMatch) && 0 !== strpos($dirMatch, $pattern)) {
unset($excluded[$index]);
}
// filter excluded patterns here to only use those matching $dir
// exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work
// if $dir does not exist, it should anyway not find anything there so no trouble
if (file_exists($dir)) {
// transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other
$dirMatch = preg_quote(strtr(realpath($dir), '\\', '/'));
foreach ($excluded as $index => $pattern) {
// extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character
$pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern);
// if the pattern is not a subset or superset of $dir, it is unrelated and we skip it
if (0 !== strpos($pattern, $dirMatch) && 0 !== strpos($dirMatch, $pattern)) {
unset($excluded[$index]);
}
}

$excluded = $excluded ? '{(' . implode('|', $excluded) . ')}' : null;
}

return ClassMapGenerator::createMap($dir, $excluded, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType, $scannedFiles);
return \count($excluded) > 0 ? '{(' . implode('|', $excluded) . ')}' : null;
}

/**
Expand Down Expand Up @@ -618,14 +599,18 @@ public function createLoader(array $autoloads, ?string $vendorDir = null)
$excluded = $autoloads['exclude-from-classmap'];
}

$scannedFiles = array();
$classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']);
$classMapGenerator->avoidDuplicateScans();

foreach ($autoloads['classmap'] as $dir) {
try {
$loader->addClassMap($this->generateClassMap($dir, $excluded, null, null, false, $scannedFiles));
$classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded));
} catch (\RuntimeException $e) {
$this->io->writeError('<warning>'.$e->getMessage().'</warning>');
}
}

$loader->addClassMap($classMapGenerator->getClassMap()->getMap());
}

return $loader;
Expand Down