From ec293adabc8e58f614e1092486a4b28fe61b7d09 Mon Sep 17 00:00:00 2001 From: Andriy Maletsky Date: Tue, 29 Oct 2019 20:18:48 +0200 Subject: [PATCH 1/8] make optimized autoloader respect PSR standards --- src/Composer/Autoload/AutoloadGenerator.php | 15 ++-- src/Composer/Autoload/ClassMapGenerator.php | 78 +++++++++++++++++-- .../Test/Autoload/AutoloadGeneratorTest.php | 39 ++++++++++ 3 files changed, 118 insertions(+), 14 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index d970ca5b1..f0872fb7e 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -256,15 +256,14 @@ EOF; continue; } - $namespaceFilter = $namespace === '' ? null : $namespace; - $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap); + $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap); } } } } 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); @@ -317,9 +316,9 @@ EOF; 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"; if (!isset($classMap[$class])) { $classMap[$class] = $pathCode; @@ -334,9 +333,9 @@ EOF; 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) @@ -447,7 +446,7 @@ EOF; foreach ($autoloads['classmap'] as $dir) { try { - $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false)); + $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false)); } catch (\RuntimeException $e) { $this->io->writeError(''.$e->getMessage().''); } diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 8467c742e..be8bcdb11 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -50,17 +50,19 @@ class ClassMapGenerator /** * Iterate over all files in the given directory searching for classes * - * @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 IOInterface $io IO object - * @param string $namespace Optional namespace prefix to filter by + * @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 IOInterface $io IO object + * @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 * @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)) { + $basePath = $path; if (is_file($path)) { $path = array(new \SplFileInfo($path)); } elseif (is_dir($path)) { @@ -71,6 +73,8 @@ class ClassMapGenerator '" 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(); @@ -100,10 +104,13 @@ class ClassMapGenerator } $classes = self::findClasses($filePath); + if (null !== $autoloadType) { + $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io); + } foreach ($classes as $class) { // 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; } @@ -121,6 +128,65 @@ class ClassMapGenerator 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: class $class located in $filePath " + . "doesn't comply with $namespaceType autoloading standard. Skipping."); + } + } + return array(); + } + /** * Extract the classes in the given file * diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 84ac16df7..86d8bb247 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -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', 'workingDir.'/psr0/psr0/badfile.php', 'workingDir.'/psr4/match.php', 'workingDir.'/psr4/badfile.php', '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 = << \$baseDir . '/psr0/psr0/match.php', + 'psr4\\\\match' => \$baseDir . '/psr4/match.php', +); + +EOF; + $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); + } + public function testVendorsClassMapAutoloading() { $package = new Package('a', '1.0', '1.0'); From 050707ed0cdb2d3f9a2c446a9689101a7ad6146f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 30 Oct 2019 16:35:13 +0100 Subject: [PATCH 2/8] Fix output of dump-autoload command to avoid interfering with warnings, refs #8397 --- src/Composer/Command/DumpAutoloadCommand.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index dbda29d63..f1e91f15c 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -63,11 +63,11 @@ EOT $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader'); if ($authoritative) { - $this->getIO()->writeError('Generating optimized autoload files (authoritative)', false); + $this->getIO()->write('Generating optimized autoload files (authoritative)'); } elseif ($optimize) { - $this->getIO()->writeError('Generating optimized autoload files', false); + $this->getIO()->write('Generating optimized autoload files'); } else { - $this->getIO()->writeError('Generating autoload files', false); + $this->getIO()->write('Generating autoload files'); } $generator = $composer->getAutoloadGenerator(); @@ -78,11 +78,11 @@ EOT $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); if ($authoritative) { - $this->getIO()->overwriteError('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); + $this->getIO()->write('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); } elseif ($optimize) { - $this->getIO()->overwriteError('Generated optimized autoload files containing '. $numberOfClasses .' classes'); + $this->getIO()->write('Generated optimized autoload files containing '. $numberOfClasses .' classes'); } else { - $this->getIO()->overwriteError('Generated autoload files containing '. $numberOfClasses .' classes'); + $this->getIO()->write('Generated autoload files containing '. $numberOfClasses .' classes'); } return 0; From f6b8643dcdbad43d0373ec03089634daed32b324 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Nov 2019 14:50:15 +0100 Subject: [PATCH 3/8] Change PSR-fix for optimized autoloader to only warn for now, refs #8397 --- src/Composer/Autoload/ClassMapGenerator.php | 25 +++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index be8bcdb11..49414883c 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -143,6 +143,10 @@ class ClassMapGenerator { $validClasses = array(); $rejectedClasses = array(); + + $realSubPath = substr($filePath, strlen($basePath) + 1); + $realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.')); + foreach ($classes as $class) { // silently skip if ns doesn't have common root if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) { @@ -166,8 +170,6 @@ class ClassMapGenerator } 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 { @@ -175,16 +177,21 @@ class ClassMapGenerator } } // warn only if no valid classes, else silently skip invalid - if (!empty($validClasses)) { - return $validClasses; - } - if ($io) { + if (empty($validClasses)) { foreach ($rejectedClasses as $class) { - $io->writeError("Warning: class $class located in $filePath " - . "doesn't comply with $namespaceType autoloading standard. Skipping."); + trigger_error( + "Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. It will not autoload anymore in Composer v1.11+.", + E_USER_DEPRECATED + ); } + + // TODO enable in Composer v1.11 or 2.0 whichever comes first + //return array(); } - return array(); + + // TODO enable in Composer v1.11 or 2.0 whichever comes first + //return $validClasses; + return $classes; } /** From c3f034e33bea599186db955554d384b9ff4699bd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Nov 2019 15:13:28 +0100 Subject: [PATCH 4/8] Fix tests for PSR-fix in optimized autoloader, refs #8397 --- .../Test/Autoload/AutoloadGeneratorTest.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 86d8bb247..42aff0868 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -568,8 +568,22 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->workingDir.'/psr4/match.php', 'workingDir.'/psr4/badfile.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + set_error_handler(function ($errno, $errstr) { + if ($errno !== E_USER_DEPRECATED || !preg_match('{^Class (psr4\\\\badclass|psr0_badclass) located in .+? does not comply with psr-[04] autoloading standard}', $errstr)) { + throw new \UnexpectedValueException('Unexpected error: '.$errstr); + } + }); + + try { + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + } catch (\Exception $e) { + restore_error_handler(); + throw $e; + } + restore_error_handler(); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + $expectedClassmap = << \$baseDir . '/psr0/psr0/badfile.php', 'psr0_match' => \$baseDir . '/psr0/psr0/match.php', + 'psr4\\\\badclass' => \$baseDir . '/psr4/badfile.php', 'psr4\\\\match' => \$baseDir . '/psr4/match.php', ); From 502b68967ac7998f2479a7d0b51fc25548cb6a8e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Nov 2019 15:31:55 +0100 Subject: [PATCH 5/8] Fix tests for PSR-fix in optimized autoloader, refs #8397 --- src/Composer/Autoload/ClassMapGenerator.php | 2 +- .../Test/Autoload/AutoloadGeneratorTest.php | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 49414883c..80d56b4d0 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -189,7 +189,7 @@ class ClassMapGenerator //return array(); } - // TODO enable in Composer v1.11 or 2.0 whichever comes first + // TODO enable in Composer v1.11 or 2.0 whichever comes first & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses //return $validClasses; return $classes; } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 42aff0868..ba89ccea9 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -552,6 +552,8 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); + $this->markTestSkipped('Skipped until ClassMapGenerator ignoring of invalid PSR-x classes is enabled'); + $package->setAutoload(array( 'psr-0' => array('psr0_' => 'psr0/'), 'psr-4' => array('psr4\\' => 'psr4/'), @@ -568,20 +570,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->workingDir.'/psr4/match.php', 'workingDir.'/psr4/badfile.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); - } catch (\Exception $e) { - restore_error_handler(); - throw $e; - } - restore_error_handler(); - + $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 = << \$baseDir . '/psr0/psr0/badfile.php', 'psr0_match' => \$baseDir . '/psr0/psr0/match.php', - 'psr4\\\\badclass' => \$baseDir . '/psr4/badfile.php', 'psr4\\\\match' => \$baseDir . '/psr4/match.php', ); From d059d90ecf326132b8da2dc0ad79e3d6e00ebb47 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Nov 2019 16:18:42 +0100 Subject: [PATCH 6/8] Fix PSR warnings for optimized autoloader, refs #8397, refs #8403 --- src/Composer/Autoload/ClassMapGenerator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 80d56b4d0..0a2123d20 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -110,7 +110,8 @@ class ClassMapGenerator foreach ($classes as $class) { // skip classes not within the given namespace prefix - if (null === $autoloadType && null !== $namespace && 0 !== strpos($class, $namespace)) { + // TODO enable in Composer v1.11 or 2.0 whichever comes first + if (/* null === $autoloadType && */ null !== $namespace && 0 !== strpos($class, $namespace)) { continue; } From af86ca1fb3bde63ec351d78f431da5502318bc3f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Nov 2019 16:32:34 +0100 Subject: [PATCH 7/8] Output a hint that maybe you are not in the right directory, fixes #8404 --- src/Composer/Command/RequireCommand.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 1fa280d3f..bb99fc310 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -139,7 +139,15 @@ EOT } $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion(); - $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update')); + try { + $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update')); + } catch (\Exception $e) { + if ($this->newlyCreated) { + throw new \RuntimeException('No composer.json present in the current directory, this may be the cause of the following exception.', 0, $e); + } + + throw $e; + } $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; From 1a797c16a0e641c91cfa201da72a59e0925da0c4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Nov 2019 16:40:30 +0100 Subject: [PATCH 8/8] Prepare 1.9.1 changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15526d860..ade8d99cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +### [1.9.1] 2019-11-01 + + * Fixed various credential handling issues with gitlab and github + * Fixed credentials being present in git remotes in Composer cache and vendor directory when not using SSH keys + * Fixed `composer why` not listing replacers as a reason something is present + * Fixed various PHP 7.4 compatibility issues + * Fixed root warnings always present in Docker containers, setting COMPOSER_ALLOW_SUPERUSER is not necessary anymore + * Fixed GitHub access tokens leaking into debug-verbosity output + * Fixed several edge case issues detecting GitHub, Bitbucket and GitLab repository types + * Fixed Composer asking if you want to use a composer.json in a parent directory when ran in non-interactive mode + * Fixed classmap autoloading issue finding classes located within a few non-PHP context blocks (?>...