-->

2013-09-08

spl_autoload_registerの例

https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
http://qiita.com/Hiraku/items/72251c709503e554c280

クラス名 => ファイルパスで1:1で対応していないと非常にわかりにくいので下記の例は非推薦な例です。








spl_autoload_register(function($className)
{
    static $includePathList;
    if (!is_array($includePathList)) {
        $includePathList = explode(PATH_SEPARATOR, get_include_path());
    }
    $includedFileList = get_included_files();
    $successFlag = false;
    do {
        $classNameList = array();
        $errorFlag = false;
        preg_replace_callback(
            "/(" .
            "(?:[A-Z])" .
            "(?:" .
            "(?:[0-9]{0,15})" .
            "(?:[a-z])" .
            "(?:[0-9]{0,15})" .
            "){1,15}" .
            ")|(.)/", function($matches) use(&$classNameList, &$errorFlag) {
                if (!$errorFlag) {
                    switch (true) {
                    case (is_string($matches[1]) && $matches[1] !== ""):
                        $classNameList[] = $matches[1];
                        break;
                    case (is_string($matches[2])):
                        switch (true) {
                        case (in_array($matches[2], array("\\", "_"), true)):
                            break;
                        default:
                            $errorFlag = true;
                        }
                        break;
                    default:
                        trigger_error("\$className parse error.", E_USER_ERROR);
                    }
                }
            }, $className);
        if ($errorFlag) {
            break;
        }
        foreach ($includePathList as $includeDir) {
            $currentDir1 = $includeDir;
            $classNameListTmp = $classNameList;
            foreach ($classNameList as $val1) {
                if (count($classNameListTmp) <= 1) {
                    break;
                }
                $currentDir1 .= DIRECTORY_SEPARATOR . $val1;
                if (!is_dir($currentDir1)) {
                    break;
                }
                array_shift($classNameListTmp);
                $currentDir2 = "";
                foreach ($classNameListTmp as $val2) {
                    $file = $currentDir1 . DIRECTORY_SEPARATOR . $currentDir2 . $val2 . ".php";
                    if (!in_array($file, $includedFileList, true) && is_file($file)) {
                        require($file);
                        if (class_exists($className)) {
                            $successFlag = true;
                            break 3;
                        }
                    }
                    $currentDir2 .= $val2;
                }
            }
        }
    } while (false);
});

問題点。

後でset_include_path(...)が行われても考慮しない。

全部同じディレクトリやファイルの走査になる。
$class = new Foo_Bar_Baz;
$class = new FooBarBaz;
$class = new \Foo\Bar\Baz;

大文字の英字{1} + 小文字の英字{1,15}(半角数字が間にあるのはOK)で"\"と"_"は区切り。
foo_bar_baz は無理。
F111_B222_B333 は無理。
Foo1_Bar2_Baz3 はOK.

"$class = new FooBar;" => "./Foo/Bar.php" のように、"ディレクトリ/ファイル名.php"で最低2つ。

$class = new FooBarBaz;
./Foo/Bar/Baz.phpで定義されている場合、定義されていないが候補に上がるファイルを全部requireする。

./Foo/Bar.php <= 読む。無い。
./Foo/BarBaz.php <= 読む。無い。
./Foo/Bar/Baz.php <= 読む。あった。終わり。

include_path = "1:2:3:4:5:6:7:8:9:." の場合で
./1/Foo/Bar.php などが全部ある場合、30ファイル目をrequireしたところで終了する。
その無駄な29ファイルはrequireしただけでは何も実行部分が無い定義だけのファイルであること、を期待している。

$class = new \Foo\Bar_Baz; のように1つ目がnamespaceであることを期待しているような気もするが強制していない。
多分\Foo\であれば"use xxx as xxx"できっと名前の競合をきっと防げる、のではないか?と思うので。

多分スラッシュとバックスラッシュが見分けにくいフォントがお気に入りの場合、つらい。
ドット、カンマ、コロン、セミコロンが見分けにくいフォントがあるのと、あんまり変わらない問題のようにも感じる。
さすがにスラッシュとバックスラッシュは無いか?見分けにくいフォントを使うのは?色んな場面でかなり不便のはずだし。

(1 + (15 + 1 + 15) * 15) = 466 <= こんな長いクラス名の一部があるかどうかは別にしてマッチする可能性はある。
こういう部分で正規表現を使うのはマナー違反的な何かがある。でかい文字に正規表現処理したらセグメンテーション違反とかね。



探す順番を制御できないがinclude_onceのほうがシンプル。
<?php
spl_autoload_register(function($className)
{
    $successFlag = false;
    do {
        $classNameList = array();
        $errorFlag = false;
        preg_replace_callback(
            "/([A-Z][a-z0-9]{0,9}[a-z][a-z0-9]{0,9})|(.)/",
            function($matches) use(&$classNameList, &$errorFlag) {
                if (!$errorFlag) {
                    switch (true) {
                    case (is_string($matches[1]) && $matches[1] !== ""):
                        $classNameList[] = $matches[1];
                        break;
                    case (is_string($matches[2])):
                        switch (true) {
                        case (in_array($matches[2], array("\\", "_"), true)):
                            break;
                        default:
                            $errorFlag = true;
                        }
                        break;
                    default:
                        trigger_error("\$className parse error.", E_USER_ERROR);
                    }
                }
            }, $className);
        if ($errorFlag) {
            break;
        }
        $currentDir1 = "";
        $classNameListTmp = $classNameList;
        foreach ($classNameList as $val1) {
            $currentDir2 = "";
            foreach ($classNameListTmp as $val2) {
                $file = $currentDir1 . $currentDir2 . $val2 . ".php";
                @include_once($file);
                if (class_exists($className)) {
                    $successFlag = true;
                    break 2;
                }
                $currentDir2 .= $val2;
            }
            $currentDir1 .= $val1 .DIRECTORY_SEPARATOR;
            array_shift($classNameListTmp);
        }
    } while (false);
});

/*
set_include_path("001:002:003");
$class = new \Foo\Bar\Baz;

001/Foo/Bar/Baz.php
001/Foo/BarBaz.php
001/Foo/Bar.php
001/FooBarBaz.php
001/FooBar.php
001/Foo.php
002/Foo/Bar/Baz.php
002/Foo/BarBaz.php
002/Foo/Bar.php
002/FooBarBaz.php
002/FooBar.php
002/Foo.php
003/Foo/Bar/Baz.php
003/Foo/BarBaz.php
003/Foo/Bar.php
003/FooBarBaz.php
003/FooBar.php
003/Foo.php
*/
spl_autoload_register(function($className)
{
    static $includePathList;
    if (!is_array($includePathList)) {
        $includePathList = explode(PATH_SEPARATOR, get_include_path());
        foreach ($includePathList as $key => $dir) {
            if (!is_dir($dir) || !is_readable($dir)) {
                unset($includePathList[$key]);
            }
        }
        $includePathList = array_reverse($includePathList);
    }
    do {
        $classNameList = array();
        $errorFlag = false;
        preg_replace_callback(
            "/([A-Z][a-z0-9]{0,9}[a-z][a-z0-9]{0,9})|(.)/",
            function($matches) use(&$classNameList, &$errorFlag) {
                if (!$errorFlag) {
                    switch (true) {
                    case (is_string($matches[1]) && $matches[1] !== ""):
                        $classNameList[] = $matches[1];
                        break;
                    case (is_string($matches[2])):
                        switch (true) {
                        case (in_array($matches[2], array("\\", "_"), true)):
                            break;
                        default:
                            $errorFlag = true;
                        }
                        break;
                    default:
                        trigger_error("\$className parse error.", E_USER_ERROR);
                    }
                }
            }, $className);
        if ($errorFlag) {
            break;
        }
        $fileList = array();
        foreach ($includePathList as $includeDir) {
            $currentDir1 = $includeDir . DIRECTORY_SEPARATOR;
            $classNameListTmp = $classNameList;
            foreach ($classNameList as $val1) {
                $currentDir2 = "";
                foreach ($classNameListTmp as $val2) {
                    $file = $currentDir1 . $currentDir2 . $val2 . ".php";
                    if (is_file($file)) {
                        $fileList[] = $file;
                    }
                    $currentDir2 .= $val2;
                }
                if (count($classNameListTmp) <= 1) {
                    break;
                }
                $currentDir1 .= $val1 . DIRECTORY_SEPARATOR;
                if (!is_dir($currentDir1)) {
                    break;
                }
                array_shift($classNameListTmp);
            }
        }
        for ($i = count($fileList) - 1; $i >= 0; --$i) {
            @include_once($fileList[$i]);
            if (class_exists($className)) {
                break;
            }
        }
    } while (false);
});

0 件のコメント: