Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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.
- Loading branch information
Showing
19 changed files
with
464 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of 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 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 <fabien@symfony.com> | ||
* @author Jordi Boggiano <j.boggiano@seld.be> | ||
*/ | ||
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; | ||
} | ||
} | ||
} |
163 changes: 163 additions & 0 deletions
163
src/Symfony/Component/ClassLoader/Tests/ClassLoaderTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of 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 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.', | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Bar.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Namespaced2; | ||
|
||
class Bar | ||
{ | ||
public static $loaded = true; | ||
} |
8 changes: 8 additions & 0 deletions
8
src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Baz.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Namespaced2; | ||
|
||
class Baz | ||
{ | ||
public static $loaded = true; | ||
} |
8 changes: 8 additions & 0 deletions
8
src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced2/Foo.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Namespaced2; | ||
|
||
class Foo | ||
{ | ||
public static $loaded = true; | ||
} |
Oops, something went wrong.