Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge remote-tracking branch 'beberlei/ClassMaps'

  • Loading branch information...
commit 27eb249aaba70678ac6f86f6c7e172d76f415142 2 parents 5f2e42e + 590ee41
Jordi Boggiano authored March 10, 2012

Showing 24 changed files with 426 additions and 5 deletions. Show diff stats Hide diff stats

  1. 21  doc/04-schema.md
  2. 4  res/composer-schema.json
  3. 15  src/Composer/Autoload/AutoloadGenerator.php
  4. 22  src/Composer/Autoload/ClassLoader.php
  5. 137  src/Composer/Autoload/ClassMapGenerator.php
  6. 34  tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  7. 83  tests/Composer/Test/Autoload/ClassMapGeneratorTest.php
  8. 8  tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php
  9. 8  tests/Composer/Test/Autoload/Fixtures/Namespaced/Baz.php
  10. 8  tests/Composer/Test/Autoload/Fixtures/Namespaced/Foo.php
  11. 6  tests/Composer/Test/Autoload/Fixtures/Pearlike/Bar.php
  12. 6  tests/Composer/Test/Autoload/Fixtures/Pearlike/Baz.php
  13. 6  tests/Composer/Test/Autoload/Fixtures/Pearlike/Foo.php
  14. 8  tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php
  15. 8  tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Foo.php
  16. 6  tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Bar.php
  17. 6  tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Foo.php
  18. 8  tests/Composer/Test/Autoload/Fixtures/classmap/SomeClass.php
  19. 8  tests/Composer/Test/Autoload/Fixtures/classmap/SomeInterface.php
  20. 8  tests/Composer/Test/Autoload/Fixtures/classmap/SomeParent.php
  21. 11  tests/Composer/Test/Autoload/Fixtures/classmap/multipleNs.php
  22. 3  tests/Composer/Test/Autoload/Fixtures/classmap/notAClass.php
  23. 1  tests/Composer/Test/Autoload/Fixtures/classmap/notPhpFile.md
  24. 6  tests/Composer/Test/Autoload/Fixtures/classmap/sameNsMultipleClasses.php
21  doc/04-schema.md
Source Rendered
@@ -183,9 +183,10 @@ Optional.
183 183
 
184 184
 Autoload mapping for a PHP autoloader.
185 185
 
186  
-Currently only [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
187  
-autoloading is supported. Under the
188  
-`psr-0` key you define a mapping from namespaces to paths, relative to the
  186
+Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
  187
+autoloading and ClassMap generation are supported.
  188
+
  189
+Under the `psr-0` key you define a mapping from namespaces to paths, relative to the
189 190
 package root.
190 191
 
191 192
 Example:
@@ -198,6 +199,18 @@ Example:
198 199
 
199 200
 Optional, but it is highly recommended that you follow PSR-0 and use this.
200 201
 
  202
+You can use the classmap generation support to define autoloading for all libraries
  203
+that do not follow "PSR-0". To configure this you specify all directories
  204
+to search for classes.
  205
+
  206
+Example:
  207
+
  208
+    {
  209
+        "autoload: {
  210
+            "classmap": ["src/", "lib/"]
  211
+        }
  212
+    }
  213
+
201 214
 ## target-dir
202 215
 
203 216
 Defines the installation target.
@@ -389,4 +402,4 @@ See (Vendor Bins)[articles/vendor-bins.md] for more details.
389 402
 
390 403
 Optional.
391 404
 
392  
-← [Command-line interface](03-cli.md)  |  [Repositories](05-repositories.md) →
  405
+← [Command-line interface](03-cli.md)  |  [Repositories](05-repositories.md) →
4  res/composer-schema.json
@@ -127,6 +127,10 @@
127 127
                     "type": "object",
128 128
                     "description": "This is a hash of namespaces (keys) and the directories they can be found into (values) by the autoloader.",
129 129
                     "additionalProperties": true
  130
+                },
  131
+                "classmap": {
  132
+                    "type": "array",
  133
+                    "description": "This is an array of directories that contain classes to be included in the class-map generation process."
130 134
                 }
131 135
             }
132 136
         },
15  src/Composer/Autoload/AutoloadGenerator.php
@@ -44,6 +44,11 @@ public function dump(RepositoryInterface $localRepo, PackageInterface $mainPacka
44 44
         $loader->add($namespace, $path);
45 45
     }
46 46
 
  47
+    $classMap = require __DIR__.'/autoload_classmap.php';
  48
+    if ($classMap) {
  49
+        $loader->addClassMap($classMap);
  50
+    }
  51
+
47 52
     $loader->register();
48 53
 
49 54
     return $loader;
@@ -107,9 +112,17 @@ public function dump(RepositoryInterface $localRepo, PackageInterface $mainPacka
107 112
                 }
108 113
             }
109 114
         }
110  
-
111 115
         $namespacesFile .= ");\n";
112 116
 
  117
+        if (isset($autoloads['classmap'])) {
  118
+            // flatten array
  119
+            $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
  120
+        } else {
  121
+            $autoloads['classmap'] = array();
  122
+        }
  123
+
  124
+        ClassMapGenerator::dump($autoloads['classmap'], $targetDir.'/autoload_classmap.php');
  125
+
113 126
         file_put_contents($targetDir.'/autoload.php', $autoloadFile);
114 127
         file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
115 128
         copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
22  src/Composer/Autoload/ClassLoader.php
@@ -45,6 +45,7 @@ class ClassLoader
45 45
     private $prefixes = array();
46 46
     private $fallbackDirs = array();
47 47
     private $useIncludePath = false;
  48
+    private $classMap = array();
48 49
 
49 50
     public function getPrefixes()
50 51
     {
@@ -56,6 +57,23 @@ public function getFallbackDirs()
56 57
         return $this->fallbackDirs;
57 58
     }
58 59
 
  60
+    public function getClassMap()
  61
+    {
  62
+        return $this->classMap;
  63
+    }
  64
+
  65
+    /**
  66
+     * @param array $classMap Class to filename map
  67
+     */
  68
+    public function addClassMap(array $classMap)
  69
+    {
  70
+        if ($this->classMap) {
  71
+            $this->classMap = array_merge($this->classMap, $classMap);
  72
+        } else {
  73
+            $this->classMap = $classMap;
  74
+        }
  75
+    }
  76
+
59 77
     /**
60 78
      * Registers a set of classes
61 79
      *
@@ -142,6 +160,10 @@ public function loadClass($class)
142 160
      */
143 161
     public function findFile($class)
144 162
     {
  163
+        if (isset($this->classMap[$class])) {
  164
+            return $this->classMap[$class];
  165
+        }
  166
+
145 167
         if ('\\' == $class[0]) {
146 168
             $class = substr($class, 1);
147 169
         }
137  src/Composer/Autoload/ClassMapGenerator.php
... ...
@@ -0,0 +1,137 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is copied from the Symfony package.
  5
+ *
  6
+ * (c) Fabien Potencier <fabien@symfony.com>
  7
+ *
  8
+ * For the full copyright and license information, please view the LICENSE
  9
+ * file that was distributed with this source code.
  10
+ *
  11
+ * @license MIT
  12
+ */
  13
+
  14
+namespace Composer\Autoload;
  15
+
  16
+/**
  17
+ * ClassMapGenerator
  18
+ *
  19
+ * @author Gyula Sallai <salla016@gmail.com>
  20
+ */
  21
+class ClassMapGenerator
  22
+{
  23
+    /**
  24
+     * Generate a class map file
  25
+     *
  26
+     * @param Traversable $dirs Directories or a single path to search in
  27
+     * @param string $file The name of the class map file
  28
+     */
  29
+    static public function dump($dirs, $file)
  30
+    {
  31
+        $maps = array();
  32
+
  33
+        foreach ($dirs as $dir) {
  34
+            $maps = array_merge($maps, static::createMap($dir));
  35
+        }
  36
+
  37
+        file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
  38
+    }
  39
+
  40
+    /**
  41
+     * Iterate over all files in the given directory searching for classes
  42
+     *
  43
+     * @param Iterator|string $dir The directory to search in or an iterator
  44
+     *
  45
+     * @return array A class map array
  46
+     */
  47
+    static public function createMap($dir)
  48
+    {
  49
+        if (is_string($dir)) {
  50
+            $dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
  51
+        }
  52
+
  53
+        $map = array();
  54
+
  55
+        foreach ($dir as $file) {
  56
+            if (!$file->isFile()) {
  57
+                continue;
  58
+            }
  59
+
  60
+            $path = $file->getRealPath();
  61
+
  62
+            if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') {
  63
+                continue;
  64
+            }
  65
+
  66
+            $classes = self::findClasses($path);
  67
+
  68
+            foreach ($classes as $class) {
  69
+                $map[$class] = $path;
  70
+            }
  71
+
  72
+        }
  73
+
  74
+        return $map;
  75
+    }
  76
+
  77
+    /**
  78
+     * Extract the classes in the given file
  79
+     *
  80
+     * @param string $path The file to check
  81
+     *
  82
+     * @return array The found classes
  83
+     */
  84
+    static private function findClasses($path)
  85
+    {
  86
+        $contents = file_get_contents($path);
  87
+        $tokens   = token_get_all($contents);
  88
+
  89
+        $classes = array();
  90
+
  91
+        $namespace = '';
  92
+        for ($i = 0, $max = count($tokens); $i < $max; $i++) {
  93
+            $token = $tokens[$i];
  94
+
  95
+            if (is_string($token)) {
  96
+                continue;
  97
+            }
  98
+
  99
+            $class = '';
  100
+
  101
+            switch ($token[0]) {
  102
+                case T_NAMESPACE:
  103
+                    $namespace = '';
  104
+                    // If there is a namespace, extract it
  105
+                    while (($t = $tokens[++$i]) && is_array($t)) {
  106
+                        if (in_array($t[0], array(T_STRING, T_NS_SEPARATOR))) {
  107
+                            $namespace .= $t[1];
  108
+                        }
  109
+                    }
  110
+                    $namespace .= '\\';
  111
+                    break;
  112
+                case T_CLASS:
  113
+                case T_INTERFACE:
  114
+                    // Find the classname
  115
+                    while (($t = $tokens[++$i]) && is_array($t)) {
  116
+                        if (T_STRING === $t[0]) {
  117
+                            $class .= $t[1];
  118
+                        } elseif ($class !== '' && T_WHITESPACE == $t[0]) {
  119
+                            break;
  120
+                        }
  121
+                    }
  122
+
  123
+                    if (empty($namespace)) {
  124
+                        $classes[] = $class;
  125
+                    } else {
  126
+                        $classes[] = $namespace . $class;
  127
+                    }
  128
+                    break;
  129
+                default:
  130
+                    break;
  131
+            }
  132
+        }
  133
+
  134
+        return $classes;
  135
+    }
  136
+}
  137
+
34  tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -134,6 +134,40 @@ public function testVendorsAutoloading()
134 134
         mkdir($this->vendorDir.'/.composer', 0777, true);
135 135
         $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
136 136
         $this->assertAutoloadFiles('vendors', $this->vendorDir.'/.composer');
  137
+        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
  138
+    }
  139
+
  140
+    public function testVendorsClassMapAutoloading()
  141
+    {
  142
+        $package = new MemoryPackage('a', '1.0', '1.0');
  143
+
  144
+        $packages = array();
  145
+        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
  146
+        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
  147
+        $a->setAutoload(array('classmap' => array('src/')));
  148
+        $b->setAutoload(array('classmap' => array('src/', 'lib/')));
  149
+
  150
+        $this->repository->expects($this->once())
  151
+            ->method('getPackages')
  152
+            ->will($this->returnValue($packages));
  153
+
  154
+        @mkdir($this->vendorDir.'/.composer', 0777, true);
  155
+        mkdir($this->vendorDir.'/a/a/src', 0777, true);
  156
+        mkdir($this->vendorDir.'/b/b/src', 0777, true);
  157
+        mkdir($this->vendorDir.'/b/b/lib', 0777, true);
  158
+        file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
  159
+        file_put_contents($this->vendorDir.'/b/b/src/b.php', '<?php class ClassMapBar {}');
  160
+        file_put_contents($this->vendorDir.'/b/b/lib/c.php', '<?php class ClassMapBaz {}');
  161
+
  162
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
  163
+        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
  164
+        $this->assertEquals(array(
  165
+                'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php',
  166
+                'ClassMapBar' => $this->vendorDir.'/b/b/src/b.php',
  167
+                'ClassMapBaz' => $this->vendorDir.'/b/b/lib/c.php',
  168
+            ),
  169
+            include ($this->vendorDir.'/.composer/autoload_classmap.php')
  170
+        );
137 171
     }
138 172
 
139 173
     public function testOverrideVendorsAutoloading()
83  tests/Composer/Test/Autoload/ClassMapGeneratorTest.php
... ...
@@ -0,0 +1,83 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file was copied from the Symfony package.
  5
+ *
  6
+ * (c) Fabien Potencier <fabien@symfony.com>
  7
+ *
  8
+ * For the full copyright and license information, please view the LICENSE
  9
+ * file that was distributed with this source code.
  10
+ */
  11
+
  12
+namespace Composer\Test\Autoload;
  13
+
  14
+use Composer\Autoload\ClassMapGenerator;
  15
+
  16
+class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
  17
+{
  18
+    /**
  19
+     * @dataProvider getTestCreateMapTests
  20
+     */
  21
+    public function testCreateMap($directory, $expected)
  22
+    {
  23
+        $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory));
  24
+    }
  25
+
  26
+    public function getTestCreateMapTests()
  27
+    {
  28
+        return array(
  29
+            array(__DIR__.'/Fixtures/Namespaced', array(
  30
+                'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php',
  31
+                'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php',
  32
+                'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php',
  33
+                )
  34
+            ),
  35
+            array(__DIR__.'/Fixtures/beta/NamespaceCollision', array(
  36
+                'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
  37
+                'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
  38
+            )),
  39
+            array(__DIR__.'/Fixtures/Pearlike', array(
  40
+                'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php',
  41
+                'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php',
  42
+                'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php',
  43
+            )),
  44
+            array(__DIR__.'/Fixtures/classmap', array(
  45
+                'Foo\\Bar\\A'             => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
  46
+                'Foo\\Bar\\B'             => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
  47
+                'Alpha\\A'                => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
  48
+                'Alpha\\B'                => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
  49
+                'Beta\\A'                 => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
  50
+                'Beta\\B'                 => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
  51
+                'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php',
  52
+                'ClassMap\\SomeParent'    => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php',
  53
+                'ClassMap\\SomeClass'     => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php',
  54
+            )),
  55
+        );
  56
+    }
  57
+
  58
+    public function testCreateMapFinderSupport()
  59
+    {
  60
+        if (!class_exists('Symfony\\Component\\Finder\\Finder')) {
  61
+            $this->markTestSkipped('Finder component is not available');
  62
+        }
  63
+
  64
+        $finder = new \Symfony\Component\Finder\Finder();
  65
+        $finder->files()->in(__DIR__ . '/Fixtures/beta/NamespaceCollision');
  66
+
  67
+        $this->assertEqualsNormalized(array(
  68
+            'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
  69
+            'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
  70
+        ), ClassMapGenerator::createMap($finder));
  71
+    }
  72
+
  73
+    protected function assertEqualsNormalized($expected, $actual, $message = null)
  74
+    {
  75
+        foreach ($expected as $ns => $path) {
  76
+            $expected[$ns] = strtr($path, '\\', '/');
  77
+        }
  78
+        foreach ($actual as $ns => $path) {
  79
+            $actual[$ns] = strtr($path, '\\', '/');
  80
+        }
  81
+        $this->assertEquals($expected, $actual, $message);
  82
+    }
  83
+}
8  tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace Namespaced;
  4
+
  5
+class Bar
  6
+{
  7
+    public static $loaded = true;
  8
+}
8  tests/Composer/Test/Autoload/Fixtures/Namespaced/Baz.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace Namespaced;
  4
+
  5
+class Baz
  6
+{
  7
+    public static $loaded = true;
  8
+}
8  tests/Composer/Test/Autoload/Fixtures/Namespaced/Foo.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace Namespaced;
  4
+
  5
+class Foo
  6
+{
  7
+    public static $loaded = true;
  8
+}
6  tests/Composer/Test/Autoload/Fixtures/Pearlike/Bar.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+
  3
+class Pearlike_Bar
  4
+{
  5
+    public static $loaded = true;
  6
+}
6  tests/Composer/Test/Autoload/Fixtures/Pearlike/Baz.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+
  3
+class Pearlike_Baz
  4
+{
  5
+    public static $loaded = true;
  6
+}
6  tests/Composer/Test/Autoload/Fixtures/Pearlike/Foo.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+
  3
+class Pearlike_Foo
  4
+{
  5
+    public static $loaded = true;
  6
+}
8  tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace NamespaceCollision\A\B;
  4
+
  5
+class Bar
  6
+{
  7
+    public static $loaded = true;
  8
+}
8  tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Foo.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace NamespaceCollision\A\B;
  4
+
  5
+class Foo
  6
+{
  7
+    public static $loaded = true;
  8
+}
6  tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Bar.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+
  3
+class PrefixCollision_A_B_Bar
  4
+{
  5
+    public static $loaded = true;
  6
+}
6  tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Foo.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+
  3
+class PrefixCollision_A_B_Foo
  4
+{
  5
+    public static $loaded = true;
  6
+}
8  tests/Composer/Test/Autoload/Fixtures/classmap/SomeClass.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace ClassMap;
  4
+
  5
+class SomeClass extends SomeParent implements SomeInterface
  6
+{
  7
+
  8
+}
8  tests/Composer/Test/Autoload/Fixtures/classmap/SomeInterface.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace ClassMap;
  4
+
  5
+interface SomeInterface
  6
+{
  7
+
  8
+}
8  tests/Composer/Test/Autoload/Fixtures/classmap/SomeParent.php
... ...
@@ -0,0 +1,8 @@
  1
+<?php
  2
+
  3
+namespace ClassMap;
  4
+
  5
+abstract class SomeParent
  6
+{
  7
+
  8
+}
11  tests/Composer/Test/Autoload/Fixtures/classmap/multipleNs.php
... ...
@@ -0,0 +1,11 @@
  1
+<?php
  2
+
  3
+namespace Alpha {
  4
+    class A {}
  5
+    class B {}
  6
+}
  7
+
  8
+namespace Beta {
  9
+    class A {}
  10
+    class B {}
  11
+}
3  tests/Composer/Test/Autoload/Fixtures/classmap/notAClass.php
... ...
@@ -0,0 +1,3 @@
  1
+<?php
  2
+
  3
+$a = new stdClass();
1  tests/Composer/Test/Autoload/Fixtures/classmap/notPhpFile.md
Source Rendered
... ...
@@ -0,0 +1 @@
  1
+This file should be skipped.
6  tests/Composer/Test/Autoload/Fixtures/classmap/sameNsMultipleClasses.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+
  3
+namespace Foo\Bar;
  4
+
  5
+class A {}
  6
+class B {}

0 notes on commit 27eb249

Please sign in to comment.
Something went wrong with that request. Please try again.