Skip to content

Commit

Permalink
Merge pull request #10885 from Seldaek/classmapgen
Browse files Browse the repository at this point in the history
Make use of composer/class-map-generator and deprecate ClassMapGenerator class
  • Loading branch information
Seldaek committed Jun 20, 2022
2 parents 3e844cc + 3a945ac commit c204656
Show file tree
Hide file tree
Showing 51 changed files with 180 additions and 6,369 deletions.
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

0 comments on commit c204656

Please sign in to comment.