Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

PSR-4: ClassLoader + AutoloadGenerator + tests #2459

Merged
merged 16 commits into from

9 participants

@donquixote

Here we go!

This time without any refactoring in AutoloadGenerator or AutoloadGeneratorTests. This is still a mess!

Based on a rebase of #2295, which was a follow-up to #2121

@donquixote

How can we force Travis CI to do bin/composer install and not just composer install?

@donquixote

ah, I get it! Edit .travis.yml.

@stof

@donquixote you need to install the vendors first before being able to use composer from source

@donquixote

@stof Show me how!

@stof

@donquixote install deps with the working composer (i.e the system-wide one) and then dump the autoload again with the local one (no need to do the full installation again. the autoload generation is enough)

@donquixote

Ok, so what to write in .travis.yml?

@donquixote

I don't get those fails.
It seems that at some point, autoload_real.php is the one with PSR-4, but autoload_psr4.php is missing. But I don't understand why that would happen. If you run bin/composer install locally, the autoload_psr4.php is created, even if it returns an empty array.

@Seldaek
Owner

@donquixote the AllFunctional tests use a phar compiled from the current checkout so this should indeed be correct if you only run bin/composer dump-autoload before. Strange but in the worst case I can live with travis failing those tests until the getcomposer.org phar is updated.

@donquixote

@Seldaek Could you have a special look on the testMainPackageAutoloadingWithTargetDir() test, if this is the intended behavior with PSR-4 plus target-dir setting?

Btw, I am still a little confused by the fixtures all thrown in one folder.. This is also asking for refactoring (after PSR-4 is in). Maybe one folder per "scenario", and then name the files by their actual expected name.

@Seldaek
Owner

@donquixote IMO psr-4 should just be disallowed with target-dir. We should only allow target-dir with psr-0 ideally, but I don't know if that'd break existing packages so it's safer not to. But I would disallow it with psr-4, and deprecate it once psr-4 is merged because psr-4 is preferrable to psr-0 + target-dir really.

@donquixote
@donquixote

Looking at https://github.com/symfony/WebProfilerBundle/blob/1a9a3613a5ca4ca09a5a62d8c854e9e7ae628ef0/composer.json

Code right now:

"autoload": {
    "psr-0": { "Symfony\\Bundle\\WebProfilerBundle\\": "" }
},
"target-dir": "Symfony/Bundle/WebProfilerBundle",

This could easily be replaced by

"autoload": {
    "psr-4": { "Symfony\\Bundle\\WebProfilerBundle\\": "" }
}

without any target-dir setting, and without moving any files around.
So yes, I am all for it!

Maybe we should throw an exception or make some other kind of noise if we find target-dir with PSR-4.

@webchick

Looks like PSR-4 passed: https://groups.google.com/forum/#!msg/php-fig/L8oCDQCzDcs/Z-6_9U0hpJIJ Hooray!

Is there a timeframe on when this is planning to get merged into Composer's autoloader?

@dongilbert

"Maybe we should throw an exception or make some other kind of noise if we find target-dir with PSR-4."

I would say no. It's a human that's involved, and made decision to do both PSR-4 AND put a target-dir, so I don't think we should do anything about it, except maybe document the practice.

@stof

@dongilbert a human can do mistakes, especially when it is abotu taking a decision related to an external tool. We already saw lots of misunderstanding of target-dir so warning about mistakes is a good idea IMO (the behavior when using both together will be quite hard to understand IMO, for a case which does not make much sense).
Documenting a practice is not enough to avoid people using it, because many people don't read the doc properly. The real benefit is that maintainers can close issues with a link to the doc instead of having to write an explanation again and again.

I can guess that even if we throw an exception with a message explaining why it is there, we would still see some issues opened for it. Unfortunately, some people don't even read the error message they are getting when they copy-paste it in an issue tracker.

@dongilbert

As long as it's a warning, and not something that stops the whole process. I tend to think, however, that the users will figure out they did something wrong when their code doesn't autoload. They can then look at the docs for using PSR-4 with Composer, and see their mistake if it was indeed a mistake.

Maybe it would be beneficial to output links to documentation items in the displayed Exception, to make them one less google search away from the answer they require.

@donquixote
@donquixote

I can't figure out what's the best place to throw such an exception. Maybe even more than one place.
igorw and simensen mentioned Composer\Json\JsonFile::validateSchema().

@stof

it is not in JsonFile::validateSchema (which is about applying the json schema validation).

the good place for this validation is in the ValidatingArrayLoader

@donquixote
@stof

@donquixote It does not for packages coming from repositories. But for them, it is run by packagist when updating them. So in this case, it will notify maintainers about their mistake instead of notifying users (which cannot really fix it).
and it is also by composer validate

@philsturgeon

Where are we at with this chaps?

@donquixote

@philsturgeon: maybe some people should try this at home, in addition to the travis/phpunit stuff.

@machee

Small test but figured I'd give some feedback.

Cloned and compiled donquixote/composer@c0aad84 from feature/psr4-complete branch. Changed composer.json in the same manner as in this comment for a package in our satis repos. Then composer update and everything working as expected.

@donquixote

Thanks machee!

Quick note about the 2x validation:
The one in ValidatingArrayLoader is to tell the package maintainer directly that something is wrong.

The validation in AutoloadGenerator is to tell the package user that the package is "broken". The package user can:

  • report the bug to the package maintainer, and
  • temporarily fork the package, and/or
  • temporarily stop using this package.

All of this is better than silently pretending that everything is ok.

@donquixote

I would like to also add a PSR-4 case in AutoloadGeneratorTest::testOverrideVendorsAutoloading().
But I don't really get the idea of this test. I suppose it is meant to test the order and priority of namespace mappings. But I don't quite get the logic behind this order. My guess is:

/**
 * Test that PSR-0 and PSR-4 mappings are processed in the correct order for
 * autoloading and for classmap generation:
 * - The main package has priority over other packages.
 * - Longer namespaces have priority over shorter namespaces.
 */

But I don't see this documented anywhere.. could be I am wrong and it is just the order of packages that matters.

@ghost

What's holding up this PR?

@philsturgeon

I suppose we need some more +1's from people who have tried it. I'll admit I should have, I'll give it a go now.

@philsturgeon philsturgeon referenced this pull request from a commit in thephpleague/fractal
@philsturgeon philsturgeon Switched from PSR-0 to PSR-4.
Also testing PSR-4 pull request for composer: composer/composer#2459
9156084
@philsturgeon

I've implemented a PSR-4 branch in a package I've been working on and the unit-tests are passing nicely. Sorry I don't have anything else bigger and more tested ready to play with, but it looks good. See above.

Can we merge this?

@igorw

Patch looks good.

@Seldaek Seldaek merged commit 0a4b18c into composer:master

1 check passed

Details default The Travis CI build passed
@Seldaek
Owner

Merged with just a few tweaks (see above) and I also updated the docs to recommend psr-4 and other shenanigans. Thanks @donquixote! Please mind the blog post at http://seld.be/notes/psr-4-autoloading-support-in-composer and don't rush on this to avoid creating a support mess + pissed off users :)

@ceeram

Nitpick, opening brace should be on a new line

@Michelemassari Michelemassari referenced this pull request from a commit in Michelemassari/fractal
@philsturgeon philsturgeon Switched from PSR-0 to PSR-4.
Also testing PSR-4 pull request for composer: composer/composer#2459
2668dfb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 25, 2013
  1. @donquixote
  2. @donquixote

    PSR-4 class loader: Add getters for PSR-4 prefixes. Rename PSR-0 rela…

    donquixote authored
    …ted attributes by appending *Psr0. Add more comments.
  3. @donquixote

    PSR-4 loader: Fix comments: PSR-0 related methods should have comment…

    donquixote authored
    …s refering to PSR-0 and prefixes. PSR-4 related methods should have comments refering to PSR-4 and namespaces.
  4. @donquixote

    Further improve comments.

    donquixote authored
  5. @donquixote
  6. @donquixote
Commits on Nov 26, 2013
  1. @donquixote
  2. @donquixote
  3. @donquixote
Commits on Dec 14, 2013
  1. @donquixote
  2. @donquixote
Commits on Dec 22, 2013
  1. @donquixote
  2. @donquixote
  3. @donquixote
  4. @donquixote
  5. @donquixote
This page is out of date. Refresh to see the latest.
Showing with 508 additions and 49 deletions.
  1. +1 −0  .travis.yml
  2. +4 −0 doc/04-schema.md
  3. +5 −0 res/composer-schema.json
  4. +91 −5 src/Composer/Autoload/AutoloadGenerator.php
  5. +149 −39 src/Composer/Autoload/ClassLoader.php
  6. +4 −0 src/Composer/Command/ShowCommand.php
  7. +1 −0  src/Composer/Compiler.php
  8. +8 −1 src/Composer/Package/Loader/ValidatingArrayLoader.php
  9. +116 −3 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  10. +65 −0 tests/Composer/Test/Autoload/ClassLoaderTest.php
  11. +5 −0 tests/Composer/Test/Autoload/Fixtures/SubNamespace/Bar.php
  12. +5 −0 tests/Composer/Test/Autoload/Fixtures/SubNamespace/Foo.php
  13. +11 −0 tests/Composer/Test/Autoload/Fixtures/autoload_psr4.php
  14. +11 −0 tests/Composer/Test/Autoload/Fixtures/autoload_psr4_2.php
  15. +11 −0 tests/Composer/Test/Autoload/Fixtures/autoload_psr4_3.php
  16. +5 −0 tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
  17. +5 −0 tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
  18. +5 −0 tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
  19. +5 −0 tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
  20. +1 −1  tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php
View
1  .travis.yml
@@ -10,6 +10,7 @@ before_script:
- sudo apt-get install parallel
- echo '' > ~/.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
View
4 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
View
5 res/composer-schema.json
@@ -203,6 +203,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."
View
96 src/Composer/Autoload/AutoloadGenerator.php
@@ -69,9 +69,23 @@ public function dump(Config $config, InstalledRepositoryInterface $localRepo, Pa
EOF;
+ $psr4File = <<<EOF
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+\$vendorDir = $vendorPathCode52;
+\$baseDir = $appBaseDirCode;
+
+return array(
+
+EOF;
+
+ // Collect information from all packages.
$packageMap = $this->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 @@ public function dump(Config $config, InstalledRepositoryInterface $localRepo, Pa
}
$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 = <<<EOF
<?php
@@ -131,6 +160,8 @@ public static function autoload(\$class)
// 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);
@@ -152,6 +183,29 @@ public static function autoload(\$class)
}
}
}
+ // 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.+(?<!(?<!/)Test\.php)$}',
+ preg_quote($dir),
+ strpos($namespace, '_') === false ? preg_quote(strtr($namespace, '\\', '/')) : ''
+ );
+ foreach (ClassMapGenerator::createMap($dir, $whitelist) as $class => $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 @@ public static function autoload(\$class)
}
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 @@ public static function autoload(\$class)
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 @@ public function buildPackageMap(InstallationManager $installationManager, Packag
if ($package instanceof AliasPackage) {
continue;
}
+ $this->validatePackage($package);
$packageMap[] = array(
$package,
@@ -215,6 +271,21 @@ public function buildPackageMap(InstallationManager $installationManager, Packag
}
/**
+ * @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
*
* @param array $packageMap array of array(package, installDir-relative-to-composer.json)
@@ -229,12 +300,14 @@ public function parseAutoloads(array $packageMap, PackageInterface $mainPackage)
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 @@ public function createLoader(array $autoloads)
}
}
+ if (isset($autoloads['psr-4'])) {
+ foreach ($autoloads['psr-4'] as $namespace => $path) {
+ $loader->addPsr4($namespace, $path);
+ }
+ }
+
return $loader;
}
@@ -366,7 +445,7 @@ protected function getAutoloadFile($vendorPathToTargetDirCode, $suffix)
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 @@ public static function getLoader()
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 @@ public static function getLoader()
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';
View
188 src/Composer/Autoload/ClassLoader.php
@@ -42,19 +42,36 @@
*/
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 @@ public function addClassMap(array $classMap)
}
/**
- * 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 @@ public function add($prefix, $paths, $prepend = false)
}
$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 @@ public function findFile($class)
$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;
}
}
View
4 src/Composer/Command/ShowCommand.php
@@ -303,6 +303,10 @@ protected function printMeta(InputInterface $input, OutputInterface $output, Com
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));
}
View
1  src/Composer/Compiler.php
@@ -103,6 +103,7 @@ public function compile($pharFile = 'composer.phar')
$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')) {
View
9 src/Composer/Package/Loader/ValidatingArrayLoader.php
@@ -180,7 +180,7 @@ public function load(array $config, $class = 'Composer\Package\CompletePackage')
}
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 @@ public function load(array $config, $class = 'Composer\Package\CompletePackage')
}
}
+ 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
View
119 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -95,7 +95,14 @@ public function testMainPackageAutoloading()
{
$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 @@ public function testMainPackageAutoloading()
$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', '<?php class ClassMapFoo {}');
$this->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 @@ public function testVendorDirSameAsWorkingDir()
$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 @@ public function testVendorDirSameAsWorkingDir()
$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 @@ public function testMainPackageAutoloadingAlternativeVendorDir()
$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 @@ public function testMainPackageAutoloadingAlternativeVendorDir()
file_put_contents($this->workingDir.'/composersrc/foo.php', '<?php class ClassMapFoo {}');
$this->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 @@ public function testMainPackageAutoloadingWithTargetDir()
$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'),
));
@@ -488,6 +520,20 @@ public function testOverrideVendorsAutoloading()
EOF;
+ // autoload_psr4.php is expected to be empty in this example.
+ $expectedPsr4 = <<<EOF
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+\$vendorDir = dirname(dirname(__FILE__));
+\$baseDir = dirname(\$vendorDir);
+
+return array(
+);
+
+EOF;
+
$expectedClassmap = <<<EOF
<?php
@@ -505,6 +551,7 @@ public function testOverrideVendorsAutoloading()
$this->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 @@ public function testVendorDirExcludedFromWorkingDir()
$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 @@ public function testVendorDirExcludedFromWorkingDir()
$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'),
));
@@ -736,6 +785,21 @@ public function testVendorDirExcludedFromWorkingDir()
EOF;
+ $expectedPsr4 = <<<'EOF'
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir).'/working-dir';
+
+return array(
+ 'Acme\\Foo\\' => array($baseDir . '/src-psr4'),
+ 'Acme\\Bar\\' => array($vendorDir . '/b/b/lib-psr4'),
+);
+
+EOF;
+
$expectedClassmap = <<<'EOF'
<?php
@@ -754,6 +818,7 @@ public function testVendorDirExcludedFromWorkingDir()
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 @@ public function testUpLevelRelativePaths()
$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 @@ public function testUpLevelRelativePaths()
EOF;
- $expectedClassmap = <<<'EOF'
+ $expectedPsr4 = <<<'EOF'
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir).'/working-dir';
+
+return array(
+ 'Acme\\Foo\\' => array($baseDir . '/../src-psr4'),
+);
+
+EOF;
+
+ $expectedClassmap = <<<'EOF'
<?php
// autoload_classmap.php @generated by Composer
@@ -814,6 +894,7 @@ public function testUpLevelRelativePaths()
EOF;
$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'));
$this->assertContains("\n \$baseDir . '/../test.php',\n", file_get_contents($this->vendorDir.'/composer/autoload_files.php'));
}
@@ -823,6 +904,7 @@ public function testEmptyPaths()
$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 @@ public function testEmptyPaths()
EOF;
- $expectedClassmap = <<<'EOF'
+ $expectedPsr4 = <<<'EOF'
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Acme\\Foo\\' => array($baseDir . '/'),
+);
+
+EOF;
+
+ $expectedClassmap = <<<'EOF'
<?php
// autoload_classmap.php @generated by Composer
@@ -866,6 +962,7 @@ public function testEmptyPaths()
EOF;
$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'));
}
@@ -874,6 +971,7 @@ public function testVendorSubstringPath()
$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())
@@ -896,8 +994,23 @@ public function testVendorSubstringPath()
EOF;
+ $expectedPsr4 = <<<'EOF'
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Acme\\Foo\\' => 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')
View
65 tests/Composer/Test/Autoload/ClassLoaderTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Composer\Test\Autoload;
+
+use Composer\Autoload\ClassLoader;
+
+/**
+ * Tests the Composer\Autoload\ClassLoader class.
+ */
+class ClassLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * Tests regular PSR-0 and PSR-4 class loading.
+ *
+ * @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
+ */
+ 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', '\\'),
+ );
+ }
+
+}
View
5 tests/Composer/Test/Autoload/Fixtures/SubNamespace/Bar.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace ShinyVendor\ShinyPackage\SubNamespace;
+
+class Bar {}
View
5 tests/Composer/Test/Autoload/Fixtures/SubNamespace/Foo.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace ShinyVendor\ShinyPackage\SubNamespace;
+
+class Foo {}
View
11 tests/Composer/Test/Autoload/Fixtures/autoload_psr4.php
@@ -0,0 +1,11 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Acme\\Fruit\\' => array($baseDir . '/src-fruit'),
+ 'Acme\\Cake\\' => array($baseDir . '/src-cake', $baseDir . '/lib-cake'),
+);
View
11 tests/Composer/Test/Autoload/Fixtures/autoload_psr4_2.php
@@ -0,0 +1,11 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname(dirname($vendorDir));
+
+return array(
+ 'Acme\\Fruit\\' => array($baseDir . '/src-fruit'),
+ 'Acme\\Cake\\' => array($baseDir . '/src-cake', $baseDir . '/lib-cake'),
+);
View
11 tests/Composer/Test/Autoload/Fixtures/autoload_psr4_3.php
@@ -0,0 +1,11 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+ 'Acme\\Fruit\\' => array($vendorDir . '/src-fruit'),
+ 'Acme\\Cake\\' => array($vendorDir . '/src-cake', $vendorDir . '/lib-cake'),
+);
View
5 tests/Composer/Test/Autoload/Fixtures/autoload_real_files_by_dependency.php
@@ -31,6 +31,11 @@ public static function getLoader()
$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);
View
5 tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
@@ -31,6 +31,11 @@ public static function getLoader()
$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);
View
5 tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
@@ -31,6 +31,11 @@ public static function getLoader()
$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);
View
5 tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
@@ -31,6 +31,11 @@ public static function getLoader()
$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);
View
2  tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php
@@ -253,7 +253,7 @@ public function errorProvider()
),
),
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'
)
),
);
Something went wrong with that request. Please try again.