From 09850bdf0132dbbed6986dfeebf4c4125ee51dad Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sun, 1 Apr 2012 23:31:48 +0200 Subject: [PATCH 1/6] [ClassLoader] Added a simplified PSR-0 ClassLoader The new ClassLoader does not differentiate namespaced classes and PEAR-like classes like the UniversalClassLoader does as the PEAR format is a subset of PSR-0. The new loader registers fallbacks by adding a location for an empty prefix, as done in Composer. This allows using namespaces map generated by Composer without any special processing on them. --- .../Component/ClassLoader/ClassLoader.php | 185 ++++++++++++++++++ .../ClassLoader/Tests/ClassLoaderTest.php | 163 +++++++++++++++ .../Tests/ClassMapGeneratorTest.php | 4 + .../Tests/Fixtures/Namespaced2/Bar.php | 8 + .../Tests/Fixtures/Namespaced2/Baz.php | 8 + .../Tests/Fixtures/Namespaced2/Foo.php | 8 + .../Tests/Fixtures/Pearlike2/Bar.php | 6 + .../Tests/Fixtures/Pearlike2/Baz.php | 6 + .../Tests/Fixtures/Pearlike2/Foo.php | 6 + .../alpha/NamespaceCollision/C/Bar.php | 8 + .../alpha/NamespaceCollision/C/Foo.php | 8 + .../Fixtures/alpha/PrefixCollision/C/Bar.php | 6 + .../Fixtures/alpha/PrefixCollision/C/Foo.php | 6 + .../beta/NamespaceCollision/C/B/Bar.php | 8 + .../beta/NamespaceCollision/C/B/Foo.php | 8 + .../Fixtures/beta/PrefixCollision/C/B/Bar.php | 6 + .../Fixtures/beta/PrefixCollision/C/B/Foo.php | 6 + .../Fixtures/fallback/Namespaced2/FooBar.php | 8 + .../Fixtures/fallback/Pearlike2/FooBar.php | 6 + 19 files changed, 464 insertions(+) create mode 100644 src/Symfony/Component/ClassLoader/ClassLoader.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/ClassLoaderTest.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Bar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Baz.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Foo.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/Pearlike2/Bar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/Pearlike2/Baz.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/Pearlike2/Foo.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/alpha/NamespaceCollision/C/Foo.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/alpha/PrefixCollision/C/Bar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/alpha/PrefixCollision/C/Foo.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/beta/NamespaceCollision/C/B/Foo.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/beta/PrefixCollision/C/B/Bar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/beta/PrefixCollision/C/B/Foo.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/fallback/Namespaced2/FooBar.php create mode 100644 src/Symfony/Component/ClassLoader/Tests/Fixtures/fallback/Pearlike2/FooBar.php diff --git a/src/Symfony/Component/ClassLoader/ClassLoader.php b/src/Symfony/Component/ClassLoader/ClassLoader.php new file mode 100644 index 0000000000..bb3e4119b6 --- /dev/null +++ b/src/Symfony/Component/ClassLoader/ClassLoader.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * ClassLoader implements an PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + private $prefixes = array(); + private $fallbackDirs = array(); + private $useIncludePath = false; + + public function getPrefixes() + { + return $this->prefixes; + } + + public function getFallbackDirs() + { + return $this->fallbackDirs; + } + + public function addPrefixes(array $prefixes) + { + foreach ($prefixes as $prefix => $path) { + $this->addPrefix($prefix, $path); + } + } + + /** + * Registers a set of classes + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + */ + public function addPrefix($prefix, $paths) + { + if (!$prefix) { + foreach ((array) $paths as $path) { + $this->fallbackDirs[] = $path; + } + return; + } + if (isset($this->prefixes[$prefix])) { + $this->prefixes[$prefix] = array_merge( + $this->prefixes[$prefix], + (array) $paths + ); + } else { + $this->prefixes[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include for class files. + * + * @param Boolean $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return Boolean + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param Boolean $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return Boolean|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|null The path, if found + */ + public function findFile($class) + { + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)) . DIRECTORY_SEPARATOR; + $className = substr($class, $pos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; + + foreach ($this->prefixes as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + } + } + + foreach ($this->fallbackDirs as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + + if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + return $file; + } + } +} diff --git a/src/Symfony/Component/ClassLoader/Tests/ClassLoaderTest.php b/src/Symfony/Component/ClassLoader/Tests/ClassLoaderTest.php new file mode 100644 index 0000000000..fb7e5a935a --- /dev/null +++ b/src/Symfony/Component/ClassLoader/Tests/ClassLoaderTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use Symfony\Component\ClassLoader\ClassLoader; + +class ClassLoaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassTests() + { + return array( + array('\\Namespaced2\\Foo', 'Namespaced2\\Foo', '->loadClass() loads Namespaced2\Foo class'), + array('\\Pearlike2_Foo', 'Pearlike2_Foo', '->loadClass() loads Pearlike2_Foo class'), + array('\\Namespaced2\\Bar', '\\Namespaced2\\Bar', '->loadClass() loads Namespaced2\Bar class with a leading slash'), + array('\\Pearlike2_Bar', '\\Pearlike2_Bar', '->loadClass() loads Pearlike2_Bar class with a leading slash'), + ); + } + + public function testUseIncludePath() + { + $loader = new ClassLoader(); + $this->assertFalse($loader->getUseIncludePath()); + + $this->assertEquals(null, $loader->findFile('Foo')); + + $includePath = get_include_path(); + + $loader->setUseIncludePath(true); + $this->assertTrue($loader->getUseIncludePath()); + + set_include_path(__DIR__.'/Fixtures/includepath' . PATH_SEPARATOR . $includePath); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'includepath'.DIRECTORY_SEPARATOR.'Foo.php', $loader->findFile('Foo')); + + set_include_path($includePath); + } + + /** + * @dataProvider getLoadClassFromFallbackTests + */ + public function testLoadClassFromFallback($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/fallback')); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassFromFallbackTests() + { + return array( + array('\\Namespaced2\\Baz', 'Namespaced2\\Baz', '->loadClass() loads Namespaced2\Baz class'), + array('\\Pearlike2_Baz', 'Pearlike2_Baz', '->loadClass() loads Pearlike2_Baz class'), + array('\\Namespaced2\\FooBar', 'Namespaced2\\FooBar', '->loadClass() loads Namespaced2\Baz class from fallback dir'), + array('\\Pearlike2_FooBar', 'Pearlike2_FooBar', '->loadClass() loads Pearlike2_Baz class from fallback dir'), + ); + } + + /** + * @dataProvider getLoadClassNamespaceCollisionTests + */ + public function testLoadClassNamespaceCollision($namespaces, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($namespaces); + + $loader->loadClass($className); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassNamespaceCollisionTests() + { + return array( + array( + array( + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'NamespaceCollision\C\Foo', + '->loadClass() loads NamespaceCollision\C\Foo from alpha.', + ), + array( + array( + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'NamespaceCollision\C\Bar', + '->loadClass() loads NamespaceCollision\C\Bar from alpha.', + ), + array( + array( + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'NamespaceCollision\C\B\Foo', + '->loadClass() loads NamespaceCollision\C\B\Foo from beta.', + ), + array( + array( + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'NamespaceCollision\C\B\Bar', + '->loadClass() loads NamespaceCollision\C\B\Bar from beta.', + ), + array( + array( + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'PrefixCollision_C_Foo', + '->loadClass() loads PrefixCollision_C_Foo from alpha.', + ), + array( + array( + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'PrefixCollision_C_Bar', + '->loadClass() loads PrefixCollision_C_Bar from alpha.', + ), + array( + array( + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'PrefixCollision_C_B_Foo', + '->loadClass() loads PrefixCollision_C_B_Foo from beta.', + ), + array( + array( + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'PrefixCollision_C_B_Bar', + '->loadClass() loads PrefixCollision_C_B_Bar from beta.', + ), + ); + } +} diff --git a/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php b/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php index dbb78eb771..6cebd38282 100644 --- a/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php +++ b/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php @@ -36,6 +36,8 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase 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', + 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php', + 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php', )), array(__DIR__.'/Fixtures/Pearlike', array( 'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php', @@ -82,6 +84,8 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase $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', + 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php', + 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php', ), ClassMapGenerator::createMap($finder)); } diff --git a/src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Bar.php b/src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Bar.php new file mode 100644 index 0000000000..7bf42ab1b9 --- /dev/null +++ b/src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Bar.php @@ -0,0 +1,8 @@ + Date: Sun, 1 Apr 2012 23:50:54 +0200 Subject: [PATCH 2/6] Changed the test autoloading to use the new autoloader --- autoload.php.dist | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/autoload.php.dist b/autoload.php.dist index 75e4ebb441..493a00bea2 100644 --- a/autoload.php.dist +++ b/autoload.php.dist @@ -1,41 +1,33 @@ registerNamespaces(array( - 'Symfony\\Tests' => __DIR__.'/tests', - 'Symfony' => __DIR__.'/src', - 'Doctrine\\Common\\DataFixtures' => __DIR__.'/vendor/doctrine-fixtures/lib', - 'Doctrine\\Common' => __DIR__.'/vendor/doctrine-common/lib', - 'Doctrine\\DBAL' => __DIR__.'/vendor/doctrine-dbal/lib', - 'Doctrine' => __DIR__.'/vendor/doctrine/lib', - 'Monolog' => __DIR__.'/vendor/monolog/src', -)); -$loader->registerPrefixes(array( - 'Twig_' => __DIR__.'/vendor/twig/lib', +$loader = new ClassLoader(); +$loader->addPrefixes(array( + 'Symfony\\Tests\\' => __DIR__.'/tests', + 'Symfony\\' => __DIR__.'/src', + 'Doctrine\\Common\\DataFixtures\\' => __DIR__.'/vendor/doctrine-fixtures/lib', + 'Doctrine\\Common\\' => __DIR__.'/vendor/doctrine-common/lib', + 'Doctrine\\DBAL\\' => __DIR__.'/vendor/doctrine-dbal/lib', + 'Doctrine\\ORM\\' => __DIR__.'/vendor/doctrine/lib', + 'Monolog\\' => __DIR__.'/vendor/monolog/src', + 'Twig_' => __DIR__.'/vendor/twig/lib', )); if (!function_exists('intl_get_error_code')) { require_once __DIR__.'/src/Symfony/Component/Locale/Resources/stubs/functions.php'; - $loader->registerPrefixFallback(__DIR__.'/src/Symfony/Component/Locale/Resources/stubs'); + $loader->addPrefix('', __DIR__.'/src/Symfony/Component/Locale/Resources/stubs'); } if (!interface_exists('SessionHandlerInterface', false)) { - $loader->registerPrefix('SessionHandlerInterface', __DIR__.'/src/Symfony/Component/HttpFoundation/Resources/stubs'); + $loader->addPrefix('SessionHandlerInterface', __DIR__.'/src/Symfony/Component/HttpFoundation/Resources/stubs'); } $loader->register(); if (is_file(__DIR__.'/vendor/doctrine-common/lib/Doctrine/Common/Annotations/AnnotationRegistry.php')) { - AnnotationRegistry::registerLoader(function($class) use ($loader) { - $loader->loadClass($class); - return class_exists($class, false); - }); - if (is_file(__DIR__.'/vendor/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php')) { - AnnotationRegistry::registerFile(__DIR__.'/vendor/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); - } + AnnotationRegistry::registerLoader(array($loader, 'loadClass')); } if (is_file(__DIR__.'/vendor/swiftmailer/lib/classes/Swift.php')) { From eae772e480e5414c7b68a43fbd2f52edd88e24b9 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sun, 1 Apr 2012 23:51:49 +0200 Subject: [PATCH 3/6] [ClassLoader] Added an ApcClassLoader Unlike the ApcUniversalClassLoader, ApcClassLoader uses composition, meaning it can be used to wrap any object providing a findFile($class) method. Both the UniversalClassLoader and the new ClassLoader follow this convention. It can also be used to wrap the Composer autoloader. --- .../Component/ClassLoader/ApcClassLoader.php | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/Symfony/Component/ClassLoader/ApcClassLoader.php diff --git a/src/Symfony/Component/ClassLoader/ApcClassLoader.php b/src/Symfony/Component/ClassLoader/ApcClassLoader.php new file mode 100644 index 0000000000..523e9a87e5 --- /dev/null +++ b/src/Symfony/Component/ClassLoader/ApcClassLoader.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * ApcClassLoader implements a wrapping autoloader cached in APC for PHP 5.3. + * + * It expects an object implementing a findFile method to find the file. This + * allow using it as a wrapper around the other loaders of the component (the + * ClassLoader and the UniversalClassLoader for instance) but also around any + * other autoloader following this convention (the Composer one for instance) + * + * $loader = new ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new ApcClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + * + * @api + */ +class ApcClassLoader +{ + private $prefix; + private $classFinder; + + /** + * Constructor. + * + * @param string $prefix A prefix to create a namespace in APC + * @param object $classFinder + * + * @api + */ + public function __construct($prefix, $classFinder) + { + if (!extension_loaded('apc')) { + throw new \RuntimeException('Unable to use ApcUniversalClassLoader as APC is not enabled.'); + } + + $this->prefix = $prefix; + $this->classFinder = $classFinder; + } + + /** + * Registers this instance as an autoloader. + * + * @param Boolean $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return Boolean|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + return true; + } + } + + /** + * Finds a file by class name while caching lookups to APC. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + if (false === $file = apc_fetch($this->prefix.$class)) { + apc_store($this->prefix.$class, $file = $this->classFinder->findFile($class)); + } + + return $file; + } +} From 0e54a2277e56a6fe176d2c65e97dc4517d5b085f Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 2 Apr 2012 18:45:31 +0200 Subject: [PATCH 4/6] Updated the changelog --- CHANGELOG-2.1.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index 65e06aeb08..89a724b769 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -178,6 +178,9 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c ### ClassLoader + * added a new ApcClassLoader using composition to wrap other loaders + * added a new ClassLoader which does not distinguish between namespaced and pear-like classes (as the PEAR + convention is a subset of PSR-0) and supports using Composer's namespace maps * added a class map generator * added support for loading globally-installed PEAR packages From f5cb1675540bf5a4ee867b62cfef70493cbc5f56 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 2 Apr 2012 19:03:58 +0200 Subject: [PATCH 5/6] [ClassLoader] Added a DebugClassLoader using composition --- CHANGELOG-2.1.md | 1 + .../ClassLoader/DebugClassLoader.php | 90 +++++++++++++++++++ src/Symfony/Component/HttpKernel/Kernel.php | 4 +- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/ClassLoader/DebugClassLoader.php diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index 89a724b769..138b054cf0 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -178,6 +178,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c ### ClassLoader + * added a DebugClassLoader able to wrap any autoloader providing a findFile method * added a new ApcClassLoader using composition to wrap other loaders * added a new ClassLoader which does not distinguish between namespaced and pear-like classes (as the PEAR convention is a subset of PSR-0) and supports using Composer's namespace maps diff --git a/src/Symfony/Component/ClassLoader/DebugClassLoader.php b/src/Symfony/Component/ClassLoader/DebugClassLoader.php new file mode 100644 index 0000000000..b6f7968bca --- /dev/null +++ b/src/Symfony/Component/ClassLoader/DebugClassLoader.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The DebugClassLoader will wrap all registered autoloaders providing a + * findFile method and will throw an exception if a file is found but does + * not declare the class. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * + * @api + */ +class DebugClassLoader +{ + private $classFinder; + + /** + * Constructor. + * + * @param object $classFinder + * + * @api + */ + public function __construct($classFinder) + { + $this->classFinder = $classFinder; + } + + /** + * Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper. + */ + static public function enable() + { + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (is_array($function) && method_exists($function[0], 'findFile')) { + $function = array(new static($function[0]), 'loadClass'); + } + + spl_autoload_register($function); + } + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return Boolean|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->classFinder->findFile($class)) { + require $file; + + if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) { + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + + return true; + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index d1fb733d1b..85483fd3c2 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -34,7 +34,7 @@ use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\ConfigCache; use Symfony\Component\ClassLoader\ClassCollectionLoader; -use Symfony\Component\ClassLoader\DebugUniversalClassLoader; +use Symfony\Component\ClassLoader\DebugClassLoader; /** * The Kernel is the heart of the Symfony system. @@ -91,7 +91,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface ini_set('display_errors', 1); error_reporting(-1); - DebugUniversalClassLoader::enable(); + DebugClassLoader::enable(); ErrorHandler::register($this->errorReportingLevel); if ('cli' !== php_sapi_name()) { ExceptionHandler::register(); From f1f1494bbf4c742e27e86fb7b4154f8377cf8b3d Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 2 Apr 2012 19:53:22 +0200 Subject: [PATCH 6/6] Added an exception when passing an invalid object to ApcClassLoader --- src/Symfony/Component/ClassLoader/ApcClassLoader.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/ClassLoader/ApcClassLoader.php b/src/Symfony/Component/ClassLoader/ApcClassLoader.php index 523e9a87e5..bf70bfc958 100644 --- a/src/Symfony/Component/ClassLoader/ApcClassLoader.php +++ b/src/Symfony/Component/ClassLoader/ApcClassLoader.php @@ -58,6 +58,10 @@ class ApcClassLoader throw new \RuntimeException('Unable to use ApcUniversalClassLoader as APC is not enabled.'); } + if (!method_exists($classFinder, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + $this->prefix = $prefix; $this->classFinder = $classFinder; }