diff --git a/res/composer-schema.json b/res/composer-schema.json index 877336f7e..41a4ab5ff 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -209,7 +209,7 @@ }, "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.", + "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": true }, "classmap": { diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 2b6d46eb3..2359265bd 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -99,9 +99,6 @@ EOF; // 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); @@ -160,47 +157,25 @@ EOF; // flatten array $classMap = array(); if ($scanPsr0Packages) { - // Scan the PSR-0 directories for class files, and add them to the - // class map. - foreach ($autoloads['psr-0'] 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"; - } + // Scan the PSR-0/4 directories for class files, and add them to the class map + foreach (array('psr-0', 'psr-4') as $psrType) { + foreach ($autoloads[$psrType] as $namespace => $paths) { + foreach ($paths as $dir) { + $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); + if (!is_dir($dir)) { + continue; } - } - } - } - // 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"; + $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"; + } } } } @@ -273,15 +248,22 @@ EOF; /** * @param PackageInterface $package * - * @throws \Exception - * Throws an exception, if the package has illegal settings. + * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings. */ - protected function validatePackage(PackageInterface $package) { + 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'."); + throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'."); + } + if (!empty($autoload['psr-4'])) { + foreach ($autoload['psr-4'] as $namespace => $dirs) { + if ($namespace !== '' && '\\' !== substr($namespace, -1)) { + throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'."); + } + } } } diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 6218afb06..f438e319c 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -163,7 +163,7 @@ class ClassLoader // 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."); + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; @@ -211,7 +211,7 @@ class ClassLoader } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { - throw new \Exception("A non-empty PSR-4 prefix must end with a namespace separator."); + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; @@ -317,10 +317,8 @@ class ClassLoader // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name - $logicalPathPsr0 - = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR) - ; + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index f93e10bf1..a877739e1 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -186,11 +186,18 @@ class ValidatingArrayLoader implements LoaderInterface $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types); unset($this->config['autoload'][$type]); } + if ($type === 'psr-4') { + foreach ($typeConfig as $namespace => $dirs) { + if ($namespace !== '' && '\\' !== substr($namespace, -1)) { + $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\'; + } + } + } } } 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."; + $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4'; // Unset the psr-4 setting, since unsetting target-dir might // interfere with other settings. unset($this->config['autoload']['psr-4']); diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 331d0225a..499f4de0d 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -252,11 +252,14 @@ class AutoloadGeneratorTest extends TestCase $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty."); } - public function testPSR0ToClassMapIgnoresNonExistingDir() + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); - $package->setAutoload(array('psr-0' => array('foo/bar/non/existing/'))); + $package->setAutoload(array( + 'psr-0' => array('Prefix' => 'foo/bar/non/existing/'), + 'psr-4' => array('Prefix\\' => 'foo/bar/non/existing2/') + )); $this->repository->expects($this->once()) ->method('getCanonicalPackages') diff --git a/tests/Composer/Test/Autoload/ClassLoaderTest.php b/tests/Composer/Test/Autoload/ClassLoaderTest.php index 7a8ae253b..0b9c1934e 100644 --- a/tests/Composer/Test/Autoload/ClassLoaderTest.php +++ b/tests/Composer/Test/Autoload/ClassLoaderTest.php @@ -14,13 +14,9 @@ class ClassLoaderTest extends \PHPUnit_Framework_TestCase * * @dataProvider getLoadClassTests * - * @param string $class - * The fully-qualified class name to test, - * without preceding namespace separator. - * @param bool $prependSeparator - * Whether to call ->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 + * @param string $class The fully-qualified class name to test, without preceding namespace separator. + * @param bool $prependSeparator Whether to call ->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) { @@ -30,12 +26,11 @@ class ClassLoaderTest extends \PHPUnit_Framework_TestCase $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."; + $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); @@ -45,8 +40,7 @@ class ClassLoaderTest extends \PHPUnit_Framework_TestCase /** * Provides arguments for ->testLoadClass(). * - * @return array - * Array of parameter sets to test with. + * @return array Array of parameter sets to test with. */ public function getLoadClassTests() { @@ -56,10 +50,9 @@ class ClassLoaderTest extends \PHPUnit_Framework_TestCase 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', '\\'), + array('Namespaced\\Baz', true), + array('Pearlike_Bar', true), + array('ShinyVendor\\ShinyPackage\\SubNamespace\\Bar', true), ); } - }