Skip to content

Instantly share code, notes, and snippets.

@k-holy
Created August 5, 2011 06:42
Show Gist options
  • Save k-holy/1127033 to your computer and use it in GitHub Desktop.
Save k-holy/1127033 to your computer and use it in GitHub Desktop.
PSR-0対応クラスローダ試案
<?php
// PSR-0 の対応実験 こんなふうに書きたい
namespace Holy\Example;
$lib_path = realpath(__DIR__ . '/../lib');
$test_path = realpath(__DIR__ . '/../tests');
set_include_path($lib_path . PATH_SEPARATOR . get_include_path());
require_once realpath($lib_path . '/vendor/Holy/Loader.php');
\Holy\Loader::getInstance()
->set('Holy' , realpath($lib_path . '/vendor'))
->set('Smorty' , realpath($lib_path . '/vendor/Smorty/libs'), '.class.php')
->enableAutoload();
// 現在の名前空間以下のクラス
$foo = new Foo(); // /path/to/lib/vendor/Holy/Example/Foo.php
$bar = new Foo\Bar(); // /path/to/lib/vendor/Holy/Example/Foo/Bar.php
$baz = new Foo\Bar\Baz(); // /path/to/lib/vendor/Holy/Example/Foo/Bar/Baz.php
$sub_package_klass_file = new sub_package\Klass\File(); // /path/to/lib/vendor/Holy/sub_package/Klass/File.php
// singletonなので設定し直しても前の設定は残ります
\Holy\Loader::getInstance()
->set('AutoloadSample\Foo' , realpath($lib_path . '/vendor'))
->set('AutoloadSample\Test', $test_path)
->enableAutoload();
// すまてぃ形式のクラスファイルもOK
$smorty = new \Smorty(); // /path/to/lib/vendor/Smorty/libs/Smorty.class.php
// PEAR形式 (include_path以下) Holy/Example.php
$example = new \Holy_Example();
// 別名前空間のクラス
$sample = new \AutoloadSample\Foo\Bar(); // /path/to/lib/vendor/AutoloadSample/Foo/Bar.php
// サブ名前空間がTestの場合のみ別ディレクトリから読み込む
$test = new \AutoloadSample\Test\FooTest(); // /path/to/tests/AutoloadSample/Test/FooTest.php
//$example = new \Holy_Example_Foo(); // include_path内のトップディレクトリ名とLoader::set()で指定したベンダー名が同じ場合はベンダーの方がよばれて二重定義エラーになってしまう…
<?php
namespace Holy;
/**
* Loader
*
* @author [email protected]
*/
class Loader
{
protected static $instance = null;
protected $autoloadEnabled = false;
protected $vendorInfo = array();
protected $fileExtension = '.php';
protected $namespaceSeparator = '\\';
protected function __construct()
{
$this->vendorInfo = array();
$this->autoloadEnabled = false;
}
/**
* シングルトンインスタンスを返します。
* @return object Holy\Loader
*/
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* シングルトンインスタンスをクリアします。
*/
public static function resetInstance()
{
self::$instance = null;
}
/**
* ベンダー名および設置パス、拡張子を設定します。
* @param string ベンダー名(namespace)
* @param string 設置パス
* @param string 拡張子
*/
public function set($name, $includeDir, $fileExtension=null)
{
$includeDir = rtrim($includeDir, '\\/');
if ('\\' === DIRECTORY_SEPARATOR) {
$includeDir = str_replace('/', '\\', $includeDir);
}
$this->vendorInfo[$name] = array($includeDir, $fileExtension);
return $this;
}
/**
* オートローダーによるクラスローディングを有効にします。
*
* @param bool SPLオートローダースタックの先頭に追加するかどうか
*/
public function enableAutoload($prepend = false)
{
if ($this->autoloadEnabled) {
spl_autoload_unregister(array($this, 'load'));
}
spl_autoload_register(array($this, 'load'), true, $prepend);
$this->autoloadEnabled = true;
return $this;
}
/**
* オートローダーによるクラスローディングを無効にします。
*/
public function disableAutoload()
{
if ($this->autoloadEnabled) {
spl_autoload_unregister(array($this, 'load'));
}
return $this;
}
/**
* 指定されたクラスのファイルを読み込みます。
* @param string クラス名
*/
public function load($className)
{
$filePath = $this->findFile($className);
if (false === $filePath) {
return false;
}
include $filePath;
return true;
}
protected function findFile($className)
{
$className = ltrim($className, $this->namespaceSeparator); // 先頭の名前空間セパレータは無条件で除去
$useNamespace = false;
if (false !== ($pos = strrpos($className, $this->namespaceSeparator))) { // 名前空間セパレータが使われてるかどうか
$useNamespace = true;
$namespace = substr($className, 0, $pos);
$className = substr($className, $pos + 1);
$fileName = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) // 名前空間に含まれるアンダースコアを DIRECTORY_SEPARATOR に
. DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className); // クラス名に含まれるアンダースコアを DIRECTORY_SEPARATOR に
} else {
$fileName = str_replace('_', DIRECTORY_SEPARATOR, $className); // クラス名に含まれるアンダースコアを DIRECTORY_SEPARATOR に
}
$requirePath = null;
foreach ($this->vendorInfo as $vendorName => $info) { // ベンダー毎に指定されたディレクトリと拡張子でファイルを検索
$includeDir = $info[0];
$fileExtension = (isset($info[1])) ? $info[1] : $this->fileExtension;
if (0 === strpos(($useNamespace) ? $namespace : $className, $vendorName)) {
$path = $includeDir . DIRECTORY_SEPARATOR . $fileName . $fileExtension;
if (file_exists($path)) {
$requirePath = $path;
break;
}
}
}
if (is_null($requirePath)) {
$requirePath = stream_resolve_include_path($fileName . $this->fileExtension); // include_path からファイルを検索
}
return $requirePath;
}
}
<?php
namespace Holy\Tests;
use Holy\Loader;
/**
* LoaderTest
*
* @author [email protected]
*/
class LoaderTest extends \PHPUnit_Framework_TestCase
{
protected $defaultLoaders = array();
protected $defaultIncludePath = null;
public function setUp()
{
$this->defaultLoaders = spl_autoload_functions();
if (!is_array($this->defaultLoaders)) {
$this->defaultLoaders = array();
}
$this->defaultIncludePath = get_include_path();
Loader::resetInstance();
}
public function tearDown()
{
$loaders = spl_autoload_functions();
if (is_array($loaders)) {
foreach ($loaders as $loader) {
spl_autoload_unregister($loader);
}
}
if (is_array($this->defaultLoaders)) {
foreach ($this->defaultLoaders as $loader) {
spl_autoload_register($loader);
}
}
set_include_path($this->defaultIncludePath);
Loader::resetInstance();
}
public function testSingleton()
{
$this->assertInstanceOf('\Holy\Loader', Loader::getInstance());
$this->assertSame(
Loader::getInstance(),
Loader::getInstance());
}
public function testLoadingNamespacedClass()
{
Loader::getInstance()
->set('LoadSample', realpath(__DIR__ . '/LoaderTest/lib/vendor'))
->load('\LoadSample\Foo');
$this->assertInstanceOf('\LoadSample\Foo', new \LoadSample\Foo());
Loader::getInstance()
->load('\LoadSample\Foo\Bar');
$this->assertInstanceOf('\LoadSample\Foo\Bar', new \LoadSample\Foo\Bar());
Loader::getInstance()
->load('\LoadSample\Foo\Bar\Baz');
$this->assertInstanceOf('\LoadSample\Foo\Bar\Baz', new \LoadSample\Foo\Bar\Baz());
}
public function testAutoloadingNamespacedClass()
{
Loader::getInstance()
->set('AutoloadSample', realpath(__DIR__ . '/LoaderTest/lib/vendor'))
->enableAutoload(true);
$this->assertInstanceOf('\AutoloadSample\Foo', new \AutoloadSample\Foo());
$this->assertInstanceOf('\AutoloadSample\Foo\Bar', new \AutoloadSample\Foo\Bar());
$this->assertInstanceOf('\AutoloadSample\Foo\Bar\Baz', new \AutoloadSample\Foo\Bar\Baz());
}
public function testAutoloadingLegacyClassWithDirectoryAndExtension()
{
Loader::getInstance()
->set('Smorty', realpath(__DIR__ . '/LoaderTest/lib/vendor/Smorty/libs'), '.class.php')
->enableAutoload();
$this->assertInstanceOf('\Smorty', new \Smorty());
}
public function testAutoloadingLegacyClassInIncludePath()
{
set_include_path(realpath(__DIR__ . '/LoaderTest/include_path'));
Loader::getInstance()
->enableAutoload();
$this->assertInstanceOf('\PearStyleClass_Example', new \PearStyleClass_Example());
}
}
@k-holy
Copy link
Author

k-holy commented Jan 27, 2012

ユニットテスト用にinclude_path前提の別解
https://gist.github.com/1675742

include_path利用かつPEAR形式と名前空間クラスの併設時に起こるエラーの検証
https://gist.github.com/1680669

クラスとしての現実的な対応は、名前空間クラスはベンダー名と基点ディレクトリを直接指定、PEAR形式クラスはinclude_path前提かなあ。

@k-holy
Copy link
Author

k-holy commented Feb 9, 2012

こっちが最新版
https://gist.github.com/1707998

singletonやめて、__invoke()を実装して、spl_autoload_register()をユーザ側に任せただけですが…。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment