1
0
Fork 0

make optimized autoloader respect PSR standards

pull/8397/head
Andriy Maletsky 2019-10-29 20:18:48 +02:00
parent eea4098f98
commit ec293adabc
3 changed files with 118 additions and 14 deletions

View File

@ -256,15 +256,14 @@ EOF;
continue; continue;
} }
$namespaceFilter = $namespace === '' ? null : $namespace; $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap);
$classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
} }
} }
} }
} }
foreach ($autoloads['classmap'] as $dir) { foreach ($autoloads['classmap'] as $dir) {
$classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap); $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
} }
ksort($classMap); ksort($classMap);
@ -317,9 +316,9 @@ EOF;
return count($classMap); return count($classMap);
} }
private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array()) private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap = array())
{ {
foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) { foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) {
$pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
if (!isset($classMap[$class])) { if (!isset($classMap[$class])) {
$classMap[$class] = $pathCode; $classMap[$class] = $pathCode;
@ -334,9 +333,9 @@ EOF;
return $classMap; return $classMap;
} }
private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true) private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, $showAmbiguousWarning = true)
{ {
return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter); return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType);
} }
public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages) public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@ -447,7 +446,7 @@ EOF;
foreach ($autoloads['classmap'] as $dir) { foreach ($autoloads['classmap'] as $dir) {
try { try {
$loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false)); $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false));
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$this->io->writeError('<warning>'.$e->getMessage().'</warning>'); $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
} }

View File

@ -50,17 +50,19 @@ class ClassMapGenerator
/** /**
* Iterate over all files in the given directory searching for classes * Iterate over all files in the given directory searching for classes
* *
* @param \Iterator|string $path The path to search in or an iterator * @param \Iterator|string $path The path to search in or an iterator
* @param string $blacklist Regex that matches against the file path that exclude from the classmap. * @param string $blacklist Regex that matches against the file path that exclude from the classmap.
* @param IOInterface $io IO object * @param IOInterface $io IO object
* @param string $namespace Optional namespace prefix to filter by * @param string $namespace Optional namespace prefix to filter by
* @param string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
* *
* @throws \RuntimeException When the path is neither an existing file nor directory * @throws \RuntimeException When the path is neither an existing file nor directory
* @return array A class map array * @return array A class map array
*/ */
public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null) public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null)
{ {
if (is_string($path)) { if (is_string($path)) {
$basePath = $path;
if (is_file($path)) { if (is_file($path)) {
$path = array(new \SplFileInfo($path)); $path = array(new \SplFileInfo($path));
} elseif (is_dir($path)) { } elseif (is_dir($path)) {
@ -71,6 +73,8 @@ class ClassMapGenerator
'" which does not appear to be a file nor a folder' '" which does not appear to be a file nor a folder'
); );
} }
} elseif (null !== $autoloadType) {
throw new \RuntimeException('Path must be a string when specifying an autoload type');
} }
$map = array(); $map = array();
@ -100,10 +104,13 @@ class ClassMapGenerator
} }
$classes = self::findClasses($filePath); $classes = self::findClasses($filePath);
if (null !== $autoloadType) {
$classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
}
foreach ($classes as $class) { foreach ($classes as $class) {
// skip classes not within the given namespace prefix // skip classes not within the given namespace prefix
if (null !== $namespace && 0 !== strpos($class, $namespace)) { if (null === $autoloadType && null !== $namespace && 0 !== strpos($class, $namespace)) {
continue; continue;
} }
@ -121,6 +128,65 @@ class ClassMapGenerator
return $map; return $map;
} }
/**
* Remove classes which could not have been loaded by namespace autoloaders
*
* @param array $classes found classes in given file
* @param string $filePath current file
* @param string $baseNamespace prefix of given autoload mapping
* @param string $namespaceType psr-0|psr-4
* @param string $basePath root directory of given autoload mapping
* @param IOInterface $io IO object
* @return array valid classes
*/
private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io)
{
$validClasses = array();
$rejectedClasses = array();
foreach ($classes as $class) {
// silently skip if ns doesn't have common root
if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
continue;
}
// transform class name to file path and validate
if ('psr-0' === $namespaceType) {
$namespaceLength = strrpos($class, '\\');
if (false !== $namespaceLength) {
$namespace = substr($class, 0, $namespaceLength + 1);
$className = substr($class, $namespaceLength + 1);
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
. str_replace('_', DIRECTORY_SEPARATOR, $className);
}
else {
$subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
}
} elseif ('psr-4' === $namespaceType) {
$subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
} else {
throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
}
$realSubPath = substr($filePath, strlen($basePath) + 1);
$realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.'));
if ($subPath === $realSubPath) {
$validClasses[] = $class;
} else {
$rejectedClasses[] = $class;
}
}
// warn only if no valid classes, else silently skip invalid
if (!empty($validClasses)) {
return $validClasses;
}
if ($io) {
foreach ($rejectedClasses as $class) {
$io->writeError("<warning>Warning: class $class located in $filePath "
. "doesn't comply with $namespaceType autoloading standard. Skipping.</warning>");
}
}
return array();
}
/** /**
* Extract the classes in the given file * Extract the classes in the given file
* *

View File

@ -548,6 +548,45 @@ class AutoloadGeneratorTest extends TestCase
); );
} }
public function testPSRToClassMapIgnoresNonPSRClasses()
{
$package = new Package('a', '1.0', '1.0');
$package->setAutoload(array(
'psr-0' => array('psr0_' => 'psr0/'),
'psr-4' => array('psr4\\' => 'psr4/'),
));
$this->repository->expects($this->once())
->method('getCanonicalPackages')
->will($this->returnValue(array()));
$this->fs->ensureDirectoryExists($this->workingDir.'/psr0/psr0');
$this->fs->ensureDirectoryExists($this->workingDir.'/psr4');
file_put_contents($this->workingDir.'/psr0/psr0/match.php', '<?php class psr0_match {}');
file_put_contents($this->workingDir.'/psr0/psr0/badfile.php', '<?php class psr0_badclass {}');
file_put_contents($this->workingDir.'/psr4/match.php', '<?php namespace psr4; class match {}');
file_put_contents($this->workingDir.'/psr4/badfile.php', '<?php namespace psr4; class badclass {}');
$this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1');
$this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated.");
$expectedClassmap = <<<EOF
<?php
// autoload_classmap.php @generated by Composer
\$vendorDir = dirname(dirname(__FILE__));
\$baseDir = dirname(\$vendorDir);
return array(
'psr0_match' => \$baseDir . '/psr0/psr0/match.php',
'psr4\\\\match' => \$baseDir . '/psr4/match.php',
);
EOF;
$this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap);
}
public function testVendorsClassMapAutoloading() public function testVendorsClassMapAutoloading()
{ {
$package = new Package('a', '1.0', '1.0'); $package = new Package('a', '1.0', '1.0');