diff --git a/.travis.yml b/.travis.yml index 5355d5be5..800a4f2f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ before_script: - sudo apt-get install parallel - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini - composer install --dev --prefer-source + - bin/composer install --dev --prefer-source - git config --global user.name travis-ci - git config --global user.email travis@example.com diff --git a/doc/04-schema.md b/doc/04-schema.md index 56e787110..e52f19902 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -438,6 +438,10 @@ use an empty prefix like: } } +#### PSR-4 + +Stub: Similar to PSR-0. + #### Classmap The `classmap` references are all combined, during install/update, into a single diff --git a/res/composer-schema.json b/res/composer-schema.json index 827e41f5c..877336f7e 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -207,6 +207,11 @@ "description": "This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", "additionalProperties": true }, + "psr-4": { + "type": "object", + "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can be found into (values, can be arrays of paths) by the autoloader.", + "additionalProperties": true + }, "classmap": { "type": "array", "description": "This is an array of directories that contain classes to be included in the class-map generation process." diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index b26fb5831..2b6d46eb3 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -69,9 +69,23 @@ return array( EOF; + $psr4File = <<buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages()); $autoloads = $this->parseAutoloads($packageMap, $mainPackage); + // Process the 'psr-0' base directories. foreach ($autoloads['psr-0'] as $namespace => $paths) { $exportedPaths = array(); foreach ($paths as $path) { @@ -83,6 +97,21 @@ EOF; } $namespacesFile .= ");\n"; + // Process the 'psr-4' base directories. + foreach ($autoloads['psr-4'] as $namespace => $paths) { + if ('\\' !== $namespace[strlen($namespace) - 1]) { + throw new \Exception("PSR-4 namespaces must end with a namespace separator. '$namespace' does not."); + } + $exportedPaths = array(); + foreach ($paths as $path) { + $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); + } + $exportedPrefix = var_export($namespace, true); + $psr4File .= " $exportedPrefix => "; + $psr4File .= "array(".implode(', ', $exportedPaths)."),\n"; + } + $psr4File .= ");\n"; + $classmapFile = << $paths) { foreach ($paths as $dir) { $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); @@ -152,6 +183,29 @@ EOF; } } } + // Scan the PSR-4 directories for class files, and add them to the + // class map. + foreach ($autoloads['psr-4'] as $namespace => $paths) { + foreach ($paths as $dir) { + $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); + if (!is_dir($dir)) { + continue; + } + $whitelist = sprintf( + '{%s/%s.+(? $path) { + if ('' === $namespace || 0 === strpos($class, $namespace)) { + if (!isset($classMap[$class])) { + $path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); + $classMap[$class] = $path.",\n"; + } + } + } + } + } } $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap'])); @@ -173,6 +227,7 @@ EOF; } file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile); + file_put_contents($targetDir.'/autoload_psr4.php', $psr4File); file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile); if ($includePathFile = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { file_put_contents($targetDir.'/include_paths.php', $includePathFile); @@ -181,7 +236,7 @@ EOF; file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile); } file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); - file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)); + file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)); // use stream_copy_to_stream instead of copy // to work around https://bugs.php.net/bug.php?id=64634 @@ -204,6 +259,7 @@ EOF; if ($package instanceof AliasPackage) { continue; } + $this->validatePackage($package); $packageMap[] = array( $package, @@ -214,6 +270,21 @@ EOF; return $packageMap; } + /** + * @param PackageInterface $package + * + * @throws \Exception + * Throws an exception, if the package has illegal settings. + */ + protected function validatePackage(PackageInterface $package) { + $autoload = $package->getAutoload(); + if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) { + $name = $package->getName(); + $package->getTargetDir(); + throw new \Exception("The ['autoload']['psr-4'] setting is incompatible with the ['target-dir'] setting, in package '$name'."); + } + } + /** * Compiles an ordered list of namespace => path mappings * @@ -229,12 +300,14 @@ EOF; array_unshift($packageMap, $mainPackageMap); $psr0 = $this->parseAutoloadsType($packageMap, 'psr-0', $mainPackage); + $psr4 = $this->parseAutoloadsType($packageMap, 'psr-4', $mainPackage); $classmap = $this->parseAutoloadsType($sortedPackageMap, 'classmap', $mainPackage); $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $mainPackage); krsort($psr0); + krsort($psr4); - return array('psr-0' => $psr0, 'classmap' => $classmap, 'files' => $files); + return array('psr-0' => $psr0, 'psr-4' => $psr4, 'classmap' => $classmap, 'files' => $files); } /** @@ -253,6 +326,12 @@ EOF; } } + if (isset($autoloads['psr-4'])) { + foreach ($autoloads['psr-4'] as $namespace => $path) { + $loader->addPsr4($namespace, $path); + } + } + return $loader; } @@ -366,7 +445,7 @@ return ComposerAutoloaderInit$suffix::getLoader(); AUTOLOAD; } - protected function getAutoloadRealFile($usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader) + protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader) { // TODO the class ComposerAutoloaderInit should be revert to a closure // when APC has been fixed: @@ -417,8 +496,7 @@ HEADER; INCLUDE_PATH; } - if ($usePSR0) { - $file .= <<<'PSR0' + $file .= <<<'PSR0' $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); @@ -426,8 +504,16 @@ INCLUDE_PATH; PSR0; + + $file .= <<<'PSR4' + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); } + +PSR4; + if ($useClassMap) { $file .= <<<'CLASSMAP' $classMap = require __DIR__ . '/autoload_classmap.php'; diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 1db8d9a0b..6218afb06 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -42,19 +42,36 @@ namespace Composer\Autoload; */ class ClassLoader { - private $prefixes = array(); - private $fallbackDirs = array(); + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + private $useIncludePath = false; private $classMap = array(); public function getPrefixes() { - return call_user_func_array('array_merge', $this->prefixes); + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; } public function getFallbackDirs() { - return $this->fallbackDirs; + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; } public function getClassMap() @@ -75,23 +92,24 @@ class ClassLoader } /** - * Registers a set of classes, merging with any others previously set. + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The classes prefix - * @param array|string $paths The location(s) of the classes - * @param bool $prepend Prepend the location(s) + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories */ public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { - $this->fallbackDirs = array_merge( + $this->fallbackDirsPsr0 = array_merge( (array) $paths, - $this->fallbackDirs + $this->fallbackDirsPsr0 ); } else { - $this->fallbackDirs = array_merge( - $this->fallbackDirs, + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, (array) $paths ); } @@ -100,38 +118,104 @@ class ClassLoader } $first = $prefix[0]; - if (!isset($this->prefixes[$first][$prefix])) { - $this->prefixes[$first][$prefix] = (array) $paths; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { - $this->prefixes[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix] = array_merge( (array) $paths, - $this->prefixes[$first][$prefix] + $this->prefixesPsr0[$first][$prefix] ); } else { - $this->prefixes[$first][$prefix] = array_merge( - $this->prefixes[$first][$prefix], + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], (array) $paths ); } } /** - * Registers a set of classes, replacing any others previously set. + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The classes prefix - * @param array|string $paths The location(s) of the classes + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \Exception("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories */ public function set($prefix, $paths) { if (!$prefix) { - $this->fallbackDirs = (array) $paths; + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } - return; + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + */ + public function setPsr4($prefix, $paths) { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \Exception("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; } - $this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths; } /** @@ -202,45 +286,71 @@ class ClassLoader $class = substr($class, 1); } + // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name - $classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - $className = substr($class, $pos + 1); + $logicalPathPsr0 + = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR) + ; } else { // PEAR-like class name - $classPath = null; - $className = $class; + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; } - $classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php'; - - $first = $class[0]; - if (isset($this->prefixes[$first])) { - foreach ($this->prefixes[$first] as $prefix => $dirs) { + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { - if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { - return $dir . DIRECTORY_SEPARATOR . $classPath; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; } } } } } - foreach ($this->fallbackDirs as $dir) { - if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { - return $dir . DIRECTORY_SEPARATOR . $classPath; + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; } } - if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } + // Remember that this class does not exist. return $this->classMap[$class] = false; } } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 97e315a5b..4b5b6ee9c 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -303,6 +303,10 @@ EOT foreach ($autoloads as $name => $path) { $output->writeln(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } + } elseif ($type === 'psr-4') { + foreach ($autoloads as $name => $path) { + $output->writeln(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + } } elseif ($type === 'classmap') { $output->writeln(implode(', ', $autoloads)); } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index c81e300b6..41c30d6d1 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -103,6 +103,7 @@ class Compiler $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/autoload.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_namespaces.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_psr4.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_classmap.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_real.php')); if (file_exists(__DIR__.'/../../vendor/composer/include_paths.php')) { diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index a5b6281a3..f93e10bf1 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -180,7 +180,7 @@ class ValidatingArrayLoader implements LoaderInterface } if ($this->validateArray('autoload') && !empty($this->config['autoload'])) { - $types = array('psr-0', 'classmap', 'files'); + $types = array('psr-0', 'psr-4', 'classmap', 'files'); foreach ($this->config['autoload'] as $type => $typeConfig) { if (!in_array($type, $types)) { $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types); @@ -189,6 +189,13 @@ class ValidatingArrayLoader implements LoaderInterface } } + if (!empty($this->config['autoload']['psr-4']) && !empty($this->config['target-dir'])) { + $this->errors[] = "The ['autoload']['psr-4'] setting is incompatible with the ['target-dir'] setting."; + // Unset the psr-4 setting, since unsetting target-dir might + // interfere with other settings. + unset($this->config['autoload']['psr-4']); + } + // TODO validate dist // TODO validate source diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 7a1609926..331d0225a 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -95,7 +95,14 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( - 'psr-0' => array('Main' => 'src/', 'Lala' => array('src/', 'lib/')), + 'psr-0' => array( + 'Main' => 'src/', + 'Lala' => array('src/', 'lib/'), + ), + 'psr-4' => array( + 'Acme\Fruit\\' => 'src-fruit/', + 'Acme\Cake\\' => array('src-cake/', 'lib-cake/'), + ), 'classmap' => array('composersrc/'), )); @@ -107,11 +114,22 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->workingDir.'/src'); $this->fs->ensureDirectoryExists($this->workingDir.'/lib'); + $this->fs->ensureDirectoryExists($this->workingDir.'/src-fruit'); + $this->fs->ensureDirectoryExists($this->workingDir.'/src-cake'); + $this->fs->ensureDirectoryExists($this->workingDir.'/lib-cake'); + $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc'); file_put_contents($this->workingDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_1'); + + // Assert that autoload_namespaces.php was correctly generated. $this->assertAutoloadFiles('main', $this->vendorDir.'/composer'); + + // Assert that autoload_psr4.php was correctly generated. + $this->assertAutoloadFiles('psr4', $this->vendorDir.'/composer', 'psr4'); + + // Assert that autoload_classmap.php was correctly generated. $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); } @@ -122,6 +140,10 @@ class AutoloadGeneratorTest extends TestCase $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'), + 'psr-4' => array( + 'Acme\Fruit\\' => 'src-fruit/', + 'Acme\Cake\\' => array('src-cake/', 'lib-cake/'), + ), 'classmap' => array('composersrc/'), )); @@ -138,6 +160,7 @@ class AutoloadGeneratorTest extends TestCase $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_2'); $this->assertAutoloadFiles('main3', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('psr4_3', $this->vendorDir.'/composer', 'psr4'); $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap'); } @@ -146,6 +169,10 @@ class AutoloadGeneratorTest extends TestCase $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'), + 'psr-4' => array( + 'Acme\Fruit\\' => 'src-fruit/', + 'Acme\Cake\\' => array('src-cake/', 'lib-cake/'), + ), 'classmap' => array('composersrc/'), )); @@ -162,6 +189,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->workingDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_3'); $this->assertAutoloadFiles('main2', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('psr4_2', $this->vendorDir.'/composer', 'psr4'); $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap'); } @@ -170,6 +198,10 @@ class AutoloadGeneratorTest extends TestCase $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Main\\Foo' => '', 'Main\\Bar' => ''), + 'psr-4' => array( + 'Acme\Fruit\\' => 'src-fruit/', + 'Acme\Cake\\' => array('src-cake/', 'lib-cake/'), + ), 'classmap' => array('Main/Foo/src', 'lib'), 'files' => array('foo.php', 'Main/Foo/bar.php'), )); @@ -486,6 +518,20 @@ return array( 'A' => array(\$vendorDir . '/a/a/src'), ); +EOF; + + // autoload_psr4.php is expected to be empty in this example. + $expectedPsr4 = <<generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_9'); $this->assertEquals($expectedNamespace, file_get_contents($this->vendorDir.'/composer/autoload_namespaces.php')); + $this->assertEquals($expectedPsr4, file_get_contents($this->vendorDir.'/composer/autoload_psr4.php')); $this->assertEquals($expectedClassmap, file_get_contents($this->vendorDir.'/composer/autoload_classmap.php')); } @@ -678,6 +725,7 @@ EOF; $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Foo' => 'src'), + 'psr-4' => array('Acme\Foo\\' => 'src-psr4'), 'classmap' => array('classmap'), 'files' => array('test.php'), )); @@ -685,6 +733,7 @@ EOF; $vendorPackage = new Package('b/b', '1.0', '1.0'); $vendorPackage->setAutoload(array( 'psr-0' => array('Bar' => 'lib'), + 'psr-4' => array('Acme\Bar\\' => 'lib-psr4'), 'classmap' => array('classmaps'), 'files' => array('bootstrap.php'), )); @@ -734,6 +783,21 @@ return array( 'Bar' => array($vendorDir . '/b/b/lib'), ); +EOF; + + $expectedPsr4 = <<<'EOF' + array($baseDir . '/src-psr4'), + 'Acme\\Bar\\' => array($vendorDir . '/b/b/lib-psr4'), +); + EOF; $expectedClassmap = <<<'EOF' @@ -754,6 +818,7 @@ return array( EOF; $this->assertEquals($expectedNamespace, file_get_contents($vendorDir.'/composer/autoload_namespaces.php')); + $this->assertEquals($expectedPsr4, file_get_contents($vendorDir.'/composer/autoload_psr4.php')); $this->assertEquals($expectedClassmap, file_get_contents($vendorDir.'/composer/autoload_classmap.php')); $this->assertContains("\n \$vendorDir . '/b/b/bootstrap.php',\n", file_get_contents($vendorDir.'/composer/autoload_files.php')); $this->assertContains("\n \$baseDir . '/test.php',\n", file_get_contents($vendorDir.'/composer/autoload_files.php')); @@ -768,6 +833,7 @@ EOF; $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Foo' => '../path/../src'), + 'psr-4' => array('Acme\Foo\\' => '../path/../src-psr4'), 'classmap' => array('../classmap'), 'files' => array('../test.php'), )); @@ -798,7 +864,21 @@ return array( EOF; - $expectedClassmap = <<<'EOF' + $expectedPsr4 = <<<'EOF' + array($baseDir . '/../src-psr4'), +); + +EOF; + + $expectedClassmap = <<<'EOF' assertEquals($expectedNamespace, file_get_contents($this->vendorDir.'/composer/autoload_namespaces.php')); + $this->assertEquals($expectedPsr4, file_get_contents($this->vendorDir.'/composer/autoload_psr4.php')); $this->assertEquals($expectedClassmap, file_get_contents($this->vendorDir.'/composer/autoload_classmap.php')); $this->assertContains("\n \$baseDir . '/../test.php',\n", file_get_contents($this->vendorDir.'/composer/autoload_files.php')); } @@ -823,6 +904,7 @@ EOF; $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Foo' => ''), + 'psr-4' => array('Acme\Foo\\' => ''), 'classmap' => array(''), )); @@ -850,7 +932,21 @@ return array( EOF; - $expectedClassmap = <<<'EOF' + $expectedPsr4 = <<<'EOF' + array($baseDir . '/'), +); + +EOF; + + $expectedClassmap = <<<'EOF' assertEquals($expectedNamespace, file_get_contents($this->vendorDir.'/composer/autoload_namespaces.php')); + $this->assertEquals($expectedPsr4, file_get_contents($this->vendorDir.'/composer/autoload_psr4.php')); $this->assertEquals($expectedClassmap, file_get_contents($this->vendorDir.'/composer/autoload_classmap.php')); } @@ -874,6 +971,7 @@ EOF; $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Foo' => 'composer-test-autoload-src/src'), + 'psr-4' => array('Acme\Foo\\' => 'composer-test-autoload-src/src-psr4'), )); $this->repository->expects($this->once()) @@ -894,10 +992,25 @@ return array( 'Foo' => array($baseDir . '/composer-test-autoload-src/src'), ); +EOF; + + $expectedPsr4 = <<<'EOF' + array($baseDir . '/composer-test-autoload-src/src-psr4'), +); + EOF; $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'VendorSubstring'); $this->assertEquals($expectedNamespace, file_get_contents($this->vendorDir.'/composer/autoload_namespaces.php')); + $this->assertEquals($expectedPsr4, file_get_contents($this->vendorDir.'/composer/autoload_psr4.php')); } private function assertAutoloadFiles($name, $dir, $type = 'namespaces') diff --git a/tests/Composer/Test/Autoload/ClassLoaderTest.php b/tests/Composer/Test/Autoload/ClassLoaderTest.php new file mode 100644 index 000000000..7a8ae253b --- /dev/null +++ b/tests/Composer/Test/Autoload/ClassLoaderTest.php @@ -0,0 +1,65 @@ +loadClass() with a class name with preceding + * namespace separator, as it happens in PHP 5.3.0 - 5.3.2. + * See https://bugs.php.net/50731 + */ + public function testLoadClass($class, $prependSeparator = FALSE) + { + $loader = new ClassLoader(); + $loader->add('Namespaced\\', __DIR__ . '/Fixtures'); + $loader->add('Pearlike_', __DIR__ . '/Fixtures'); + $loader->addPsr4('ShinyVendor\\ShinyPackage\\', __DIR__ . '/Fixtures'); + + if ($prependSeparator) { + $prepend = '\\'; + $message = "->loadClass() loads '$class'."; + } + else { + $prepend = ''; + $message = "->loadClass() loads '\\$class', as required in PHP 5.3.0 - 5.3.2."; + } + + $loader->loadClass($prepend . $class); + $this->assertTrue(class_exists($class, false), $message); + } + + /** + * Provides arguments for ->testLoadClass(). + * + * @return array + * Array of parameter sets to test with. + */ + public function getLoadClassTests() + { + return array( + array('Namespaced\\Foo'), + array('Pearlike_Foo'), + array('ShinyVendor\\ShinyPackage\\SubNamespace\\Foo'), + // "Bar" would not work here, since it is defined in a ".inc" file, + // instead of a ".php" file. So, use "Baz" instead. + array('Namespaced\\Baz', '\\'), + array('Pearlike_Bar', '\\'), + array('ShinyVendor\\ShinyPackage\\SubNamespace\\Bar', '\\'), + ); + } + +} diff --git a/tests/Composer/Test/Autoload/Fixtures/SubNamespace/Bar.php b/tests/Composer/Test/Autoload/Fixtures/SubNamespace/Bar.php new file mode 100644 index 000000000..74cd9d7f3 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/SubNamespace/Bar.php @@ -0,0 +1,5 @@ + array($baseDir . '/src-fruit'), + 'Acme\\Cake\\' => array($baseDir . '/src-cake', $baseDir . '/lib-cake'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_psr4_2.php b/tests/Composer/Test/Autoload/Fixtures/autoload_psr4_2.php new file mode 100644 index 000000000..ab9ca2f54 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_psr4_2.php @@ -0,0 +1,11 @@ + array($baseDir . '/src-fruit'), + 'Acme\\Cake\\' => array($baseDir . '/src-cake', $baseDir . '/lib-cake'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_psr4_3.php b/tests/Composer/Test/Autoload/Fixtures/autoload_psr4_3.php new file mode 100644 index 000000000..a903b17b8 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_psr4_3.php @@ -0,0 +1,11 @@ + array($vendorDir . '/src-fruit'), + 'Acme\\Cake\\' => array($vendorDir . '/src-cake', $vendorDir . '/lib-cake'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php index d1f9afd30..e58e8d2fa 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php @@ -31,6 +31,11 @@ class ComposerAutoloaderInitFilesAutoloadOrder $loader->set($namespace, $path); } + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php index dc09bc0ee..a92e664cd 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php @@ -31,6 +31,11 @@ class ComposerAutoloaderInitFilesAutoload $loader->set($namespace, $path); } + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php index 3c92a5a2c..e72ea108a 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php @@ -31,6 +31,11 @@ class ComposerAutoloaderInitIncludePath $loader->set($namespace, $path); } + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php index cae8d5a31..4a6259da2 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php @@ -31,6 +31,11 @@ class ComposerAutoloaderInitTargetDir $loader->set($namespace, $path); } + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 262c24bf6..9b1982b9d 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -253,7 +253,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase ), ), array( - 'autoload : invalid value (psr0), must be one of psr-0, classmap, files' + 'autoload : invalid value (psr0), must be one of psr-0, psr-4, classmap, files' ) ), );