Permalink
Browse files

Merge remote-tracking branch 'beberlei/ClassMaps'

  • Loading branch information...
2 parents 5f2e42e + 590ee41 commit 27eb249aaba70678ac6f86f6c7e172d76f415142 @Seldaek committed Mar 10, 2012
Showing with 426 additions and 5 deletions.
  1. +17 −4 doc/04-schema.md
  2. +4 −0 res/composer-schema.json
  3. +14 −1 src/Composer/Autoload/AutoloadGenerator.php
  4. +22 −0 src/Composer/Autoload/ClassLoader.php
  5. +137 −0 src/Composer/Autoload/ClassMapGenerator.php
  6. +34 −0 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  7. +83 −0 tests/Composer/Test/Autoload/ClassMapGeneratorTest.php
  8. +8 −0 tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php
  9. +8 −0 tests/Composer/Test/Autoload/Fixtures/Namespaced/Baz.php
  10. +8 −0 tests/Composer/Test/Autoload/Fixtures/Namespaced/Foo.php
  11. +6 −0 tests/Composer/Test/Autoload/Fixtures/Pearlike/Bar.php
  12. +6 −0 tests/Composer/Test/Autoload/Fixtures/Pearlike/Baz.php
  13. +6 −0 tests/Composer/Test/Autoload/Fixtures/Pearlike/Foo.php
  14. +8 −0 tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php
  15. +8 −0 tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Foo.php
  16. +6 −0 tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Bar.php
  17. +6 −0 tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Foo.php
  18. +8 −0 tests/Composer/Test/Autoload/Fixtures/classmap/SomeClass.php
  19. +8 −0 tests/Composer/Test/Autoload/Fixtures/classmap/SomeInterface.php
  20. +8 −0 tests/Composer/Test/Autoload/Fixtures/classmap/SomeParent.php
  21. +11 −0 tests/Composer/Test/Autoload/Fixtures/classmap/multipleNs.php
  22. +3 −0 tests/Composer/Test/Autoload/Fixtures/classmap/notAClass.php
  23. +1 −0 tests/Composer/Test/Autoload/Fixtures/classmap/notPhpFile.md
  24. +6 −0 tests/Composer/Test/Autoload/Fixtures/classmap/sameNsMultipleClasses.php
View
@@ -183,9 +183,10 @@ Optional.
Autoload mapping for a PHP autoloader.
-Currently only [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
-autoloading is supported. Under the
-`psr-0` key you define a mapping from namespaces to paths, relative to the
+Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
+autoloading and ClassMap generation are supported.
+
+Under the `psr-0` key you define a mapping from namespaces to paths, relative to the
package root.
Example:
@@ -198,6 +199,18 @@ Example:
Optional, but it is highly recommended that you follow PSR-0 and use this.
+You can use the classmap generation support to define autoloading for all libraries
+that do not follow "PSR-0". To configure this you specify all directories
+to search for classes.
+
+Example:
+
+ {
+ "autoload: {
+ "classmap": ["src/", "lib/"]
+ }
+ }
+
## target-dir
Defines the installation target.
@@ -389,4 +402,4 @@ See (Vendor Bins)[articles/vendor-bins.md] for more details.
Optional.
-← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) →
+← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) →
View
@@ -127,6 +127,10 @@
"type": "object",
"description": "This is a hash of namespaces (keys) and the directories they can be found into (values) 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."
}
}
},
@@ -44,6 +44,11 @@ public function dump(RepositoryInterface $localRepo, PackageInterface $mainPacka
$loader->add($namespace, $path);
}
+ $classMap = require __DIR__.'/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+
$loader->register();
return $loader;
@@ -107,9 +112,17 @@ public function dump(RepositoryInterface $localRepo, PackageInterface $mainPacka
}
}
}
-
$namespacesFile .= ");\n";
+ if (isset($autoloads['classmap'])) {
+ // flatten array
+ $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
+ } else {
+ $autoloads['classmap'] = array();
+ }
+
+ ClassMapGenerator::dump($autoloads['classmap'], $targetDir.'/autoload_classmap.php');
+
file_put_contents($targetDir.'/autoload.php', $autoloadFile);
file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
@@ -45,6 +45,7 @@ class ClassLoader
private $prefixes = array();
private $fallbackDirs = array();
private $useIncludePath = false;
+ private $classMap = array();
public function getPrefixes()
{
@@ -56,6 +57,23 @@ public function getFallbackDirs()
return $this->fallbackDirs;
}
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
/**
* Registers a set of classes
*
@@ -142,6 +160,10 @@ public function loadClass($class)
*/
public function findFile($class)
{
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * This file is copied from the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassMapGenerator
+ *
+ * @author Gyula Sallai <salla016@gmail.com>
+ */
+class ClassMapGenerator
+{
+ /**
+ * Generate a class map file
+ *
+ * @param Traversable $dirs Directories or a single path to search in
+ * @param string $file The name of the class map file
+ */
+ static public function dump($dirs, $file)
+ {
+ $maps = array();
+
+ foreach ($dirs as $dir) {
+ $maps = array_merge($maps, static::createMap($dir));
+ }
+
+ file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
+ }
+
+ /**
+ * Iterate over all files in the given directory searching for classes
+ *
+ * @param Iterator|string $dir The directory to search in or an iterator
+ *
+ * @return array A class map array
+ */
+ static public function createMap($dir)
+ {
+ if (is_string($dir)) {
+ $dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
+ }
+
+ $map = array();
+
+ foreach ($dir as $file) {
+ if (!$file->isFile()) {
+ continue;
+ }
+
+ $path = $file->getRealPath();
+
+ if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') {
+ continue;
+ }
+
+ $classes = self::findClasses($path);
+
+ foreach ($classes as $class) {
+ $map[$class] = $path;
+ }
+
+ }
+
+ return $map;
+ }
+
+ /**
+ * Extract the classes in the given file
+ *
+ * @param string $path The file to check
+ *
+ * @return array The found classes
+ */
+ static private function findClasses($path)
+ {
+ $contents = file_get_contents($path);
+ $tokens = token_get_all($contents);
+
+ $classes = array();
+
+ $namespace = '';
+ for ($i = 0, $max = count($tokens); $i < $max; $i++) {
+ $token = $tokens[$i];
+
+ if (is_string($token)) {
+ continue;
+ }
+
+ $class = '';
+
+ switch ($token[0]) {
+ case T_NAMESPACE:
+ $namespace = '';
+ // If there is a namespace, extract it
+ while (($t = $tokens[++$i]) && is_array($t)) {
+ if (in_array($t[0], array(T_STRING, T_NS_SEPARATOR))) {
+ $namespace .= $t[1];
+ }
+ }
+ $namespace .= '\\';
+ break;
+ case T_CLASS:
+ case T_INTERFACE:
+ // Find the classname
+ while (($t = $tokens[++$i]) && is_array($t)) {
+ if (T_STRING === $t[0]) {
+ $class .= $t[1];
+ } elseif ($class !== '' && T_WHITESPACE == $t[0]) {
+ break;
+ }
+ }
+
+ if (empty($namespace)) {
+ $classes[] = $class;
+ } else {
+ $classes[] = $namespace . $class;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $classes;
+ }
+}
+
@@ -134,6 +134,40 @@ public function testVendorsAutoloading()
mkdir($this->vendorDir.'/.composer', 0777, true);
$this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
$this->assertAutoloadFiles('vendors', $this->vendorDir.'/.composer');
+ $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
+ }
+
+ public function testVendorsClassMapAutoloading()
+ {
+ $package = new MemoryPackage('a', '1.0', '1.0');
+
+ $packages = array();
+ $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
+ $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
+ $a->setAutoload(array('classmap' => array('src/')));
+ $b->setAutoload(array('classmap' => array('src/', 'lib/')));
+
+ $this->repository->expects($this->once())
+ ->method('getPackages')
+ ->will($this->returnValue($packages));
+
+ @mkdir($this->vendorDir.'/.composer', 0777, true);
+ mkdir($this->vendorDir.'/a/a/src', 0777, true);
+ mkdir($this->vendorDir.'/b/b/src', 0777, true);
+ mkdir($this->vendorDir.'/b/b/lib', 0777, true);
+ file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
+ file_put_contents($this->vendorDir.'/b/b/src/b.php', '<?php class ClassMapBar {}');
+ file_put_contents($this->vendorDir.'/b/b/lib/c.php', '<?php class ClassMapBaz {}');
+
+ $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
+ $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
+ $this->assertEquals(array(
+ 'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php',
+ 'ClassMapBar' => $this->vendorDir.'/b/b/src/b.php',
+ 'ClassMapBaz' => $this->vendorDir.'/b/b/lib/c.php',
+ ),
+ include ($this->vendorDir.'/.composer/autoload_classmap.php')
+ );
}
public function testOverrideVendorsAutoloading()
@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file was copied from the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Autoload;
+
+use Composer\Autoload\ClassMapGenerator;
+
+class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getTestCreateMapTests
+ */
+ public function testCreateMap($directory, $expected)
+ {
+ $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory));
+ }
+
+ public function getTestCreateMapTests()
+ {
+ return array(
+ array(__DIR__.'/Fixtures/Namespaced', array(
+ 'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php',
+ 'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php',
+ 'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php',
+ )
+ ),
+ array(__DIR__.'/Fixtures/beta/NamespaceCollision', array(
+ 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
+ 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
+ )),
+ array(__DIR__.'/Fixtures/Pearlike', array(
+ 'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php',
+ 'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php',
+ 'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php',
+ )),
+ array(__DIR__.'/Fixtures/classmap', array(
+ 'Foo\\Bar\\A' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
+ 'Foo\\Bar\\B' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
+ 'Alpha\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Alpha\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Beta\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Beta\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php',
+ 'ClassMap\\SomeParent' => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php',
+ 'ClassMap\\SomeClass' => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php',
+ )),
+ );
+ }
+
+ public function testCreateMapFinderSupport()
+ {
+ if (!class_exists('Symfony\\Component\\Finder\\Finder')) {
+ $this->markTestSkipped('Finder component is not available');
+ }
+
+ $finder = new \Symfony\Component\Finder\Finder();
+ $finder->files()->in(__DIR__ . '/Fixtures/beta/NamespaceCollision');
+
+ $this->assertEqualsNormalized(array(
+ 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
+ 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
+ ), ClassMapGenerator::createMap($finder));
+ }
+
+ protected function assertEqualsNormalized($expected, $actual, $message = null)
+ {
+ foreach ($expected as $ns => $path) {
+ $expected[$ns] = strtr($path, '\\', '/');
+ }
+ foreach ($actual as $ns => $path) {
+ $actual[$ns] = strtr($path, '\\', '/');
+ }
+ $this->assertEquals($expected, $actual, $message);
+ }
+}
@@ -0,0 +1,8 @@
+<?php
+
+namespace Namespaced;
+
+class Bar
+{
+ public static $loaded = true;
+}
Oops, something went wrong.

0 comments on commit 27eb249

Please sign in to comment.