PHP 命名空間與自動加載機制介紹

include 和 require 是PHP中引入文件的兩個基本方法舟奠。在小規(guī)模開發(fā)中直接使用 include 和 require 沒喲什么不妥,但在大型項目中會造成大量的 include 和 require 堆積宁改。這樣的代碼既不優(yōu)雅逊躁,執(zhí)行效率也很低谢翎,而且維護起來也相當困難。

為了解決這個問題走趋,部分框架會給出一個引入文件的配置清單衅金,在對象初始化的時候把需要的文件引入。但這只是讓代碼變得更簡潔了一些簿煌,引入的效果仍然是差強人意氮唯。PHP5 之后,隨著 PHP 面向?qū)ο笾С值耐晟埔涛埃琠_autoload 函數(shù)才真正使得自動加載成為可能您觉。

  • include 和 require 功能是一樣的,它們的不同在于 include 出錯時只會產(chǎn)生警告授滓,而 require 會拋出錯誤終止腳本琳水。

  • include_once 和 include 唯一的區(qū)別在于 include_once 會檢查文件是否已經(jīng)引入,如果是則不會重復引入般堆。

=================自動加載==================

實現(xiàn)自動加載最簡單的方式就是使用 __autoload 魔術(shù)方法在孝。當需要使用的類沒有被引入時,這個函數(shù)會在PHP報錯前被觸發(fā)淮摔,未定義的類名會被當作參數(shù)傳入私沮。至于函數(shù)具體的邏輯,這需要用戶自己去實現(xiàn)和橙。

首先創(chuàng)建一個 autoload.php 來做一個簡單的測試:

// 類未定義時仔燕,系統(tǒng)自動調(diào)用
function __autoload($class)
{ /* 具體處理邏輯 */
    echo $class;// 簡單的輸出未定義的類名
} new HelloWorld(); 
/**
 * 輸出 HelloWorld 與報錯信息
 * Fatal error: Class 'HelloWorld' not found
 */

通過這個簡單的例子可以發(fā)現(xiàn)造垛,在類的實例化過程中,系統(tǒng)所做的工作大致是這樣的:

/* 模擬系統(tǒng)實例化過程 */
function instance($class)
{ // 如果類存在則返回其實例
    if (class_exists($class, false)) { return new $class();
    } // 查看 autoload 函數(shù)是否被用戶定義
    if (function_exists('__autoload')) {
        __autoload($class); // 最后一次引入的機會
 } // 再次檢查類是否存在
    if (class_exists($class, false)) { return new $class();
    } else { // 系統(tǒng):我實在沒轍了
        throw new Exception('Class Not Found');
    }
}

明白了 __autoload 函數(shù)的工作原理之后晰搀,那就讓我們來用它去實現(xiàn)自動加載五辽。

首先創(chuàng)建一個類文件(建議文件名與類名一致),代碼如下:

class [ClassName] 
{ // 對象實例化時輸出當前類名
    function __construct()
    { echo '<h1>' . __CLASS__ . '</h1>';
    }
}

(我這里創(chuàng)建了一個 HelloWorld 類用作演示)接下來我們就要定義 __autoload 的具體邏輯外恕,使它能夠?qū)崿F(xiàn)自動加載:

function __autoload($class)
{ // 根據(jù)類名確定文件名
    $file = $class . '.php'; if (file_exists($file)) { include $file; // 引入PHP文件
 }
} new HelloWorld(); /**
 * 輸出 <h1>HelloWorld</h1> */

=================命名空間==================

其實命名空間并不是什么新生事物杆逗,很多語言(例如C++)早都支持這個特性了。只不過 PHP 起步比較晚鳞疲,直到 PHP 5.3 之后才支持罪郊。

命名空間簡而言之就是一種標識,它的主要目的是解決命名沖突的問題尚洽。

就像在日常生活中悔橄,有很多姓名相同的人,如何區(qū)分這些人呢腺毫?那就需要加上一些額外的標識癣疟。

把工作單位當成標識似乎不錯,這樣就不用擔心 “撞名” 的尷尬了拴曲。

這里我們來做一個小任務争舞,去介紹百度的CEO李彥宏:

namespace 百度; class 李彥宏
{ function __construct()
    { echo '百度創(chuàng)始人';
    }
}

↑ 這就是李彥宏的基本資料了凛忿,namespace 是他的單位標識澈灼,class 是他的姓名。

命名空間通過關(guān)鍵字 namespace 來聲明店溢。如果一個文件中包含命名空間叁熔,它必須在其它所有代碼之前聲明命名空間。

new 百度\李彥宏(); // 限定類名
new \百度\李彥宏(); // 完全限定類名

↑ 在一般情況下床牧,無論是向別人介紹 "百度 李彥宏" 還是 "百度公司 李彥宏"荣回,他們都能夠明白。

在當前命名空間沒有聲明的情況下戈咳,限定類名和完全限定類名是等價的俯画。因為如果不指定空間臀栈,則默認為全局(\)。

namespace 谷歌; 
new 百度\李彥宏(); // 谷歌\百度\李彥宏(實際結(jié)果)
new \百度\李彥宏(); // 百度\李彥宏(實際結(jié)果)

↑ 如果你在谷歌公司向他們的員工介紹李彥宏,一定要指明是 "百度公司的李彥宏"截亦。否則他會認為百度是谷歌的一個部門,而李彥宏只是其中的一位員工而已碌燕。

這個例子展示了在命名空間下恒水,使用限定類名和完全限定類名的區(qū)別。(完全限定類名 = 當前命名空間 + 限定類名)

 /* 導入命名空間 */
use 百度\李彥宏; new 李彥宏(); // 百度\李彥宏(實際結(jié)果)

/* 設置別名 */
use 百度\李彥宏 AS CEO; new CEO(); // 百度\李彥宏(實際結(jié)果)

/* 任何情況 */
new \百度\李彥宏();// 百度\李彥宏(實際結(jié)果)

↑ 第一種情況是別人已經(jīng)認識李彥宏了顷蟆,你只需要直接說名字诫隅,他就能知道你指的是誰腐魂。第二種情況是李彥宏就是他們的CEO,你直接說CEO逐纬,他可以立刻反應過來蛔屹。

使用命名空間只是讓類名有了前綴,不容易發(fā)生沖突风题,系統(tǒng)仍然不會進行自動導入判导。

如果不引入文件,系統(tǒng)會在拋出 "Class Not Found" 錯誤之前觸發(fā) __autoload 函數(shù)沛硅,并將限定類名傳入作為參數(shù)眼刃。

所以上面的例子都是基于你已經(jīng)將相關(guān)文件手動引入的情況下實現(xiàn)的,否則系統(tǒng)會拋出 " Class '百度\李彥宏' not found"摇肌。

=================spl_autoload==================

接下來讓我們要在含有命名空間的情況下去實現(xiàn)自動加載擂红。這里我們使用 spl_autoload_register() 函數(shù)來實現(xiàn),這需要你的 PHP 版本號大于 5.12围小。

spl_autoload_register 函數(shù)的功能就是把傳入的函數(shù)(參數(shù)可以為回調(diào)函數(shù)或函數(shù)名稱形式)注冊到 SPL __autoload 函數(shù)隊列中昵骤,并移除系統(tǒng)默認的 __autoload() 函數(shù)。

一旦調(diào)用 spl_autoload_register() 函數(shù)肯适,當調(diào)用未定義類時变秦,系統(tǒng)就會按順序調(diào)用注冊到 spl_autoload_register() 函數(shù)的所有函數(shù),而不是自動調(diào)用 __autoload() 函數(shù)框舔。

現(xiàn)在蹦玫,我們來創(chuàng)建一個 Linux 類,它使用 os 作為它的命名空間(建議文件名與類名保持一致):

namespace os; // 命名空間

class Linux // 類名
{ function __construct()
    { echo '<h1>' . __CLASS__ . '</h1>';
    }
}

接著刘绣,在同一個目錄下新建一個 PHP 文件樱溉,使用 spl_autoload_register 以函數(shù)回調(diào)的方式實現(xiàn)自動加載:

spl_autoload_register(function ($class) { // class = os\Linux

    /* 限定類名路徑映射 */
    $class_map = array( // 限定類名 => 文件路徑
        'os\\Linux' => './Linux.php', ); /* 根據(jù)類名確定文件名 */
    $file = $class_map[$class]; /* 引入相關(guān)文件 */
    if (file_exists($file)) { include $file;
    }
}); new \os\Linux();

這里我們使用了一個數(shù)組去保存類名與文件路徑的關(guān)系,這樣當類名傳入時纬凤,自動加載器就知道該引入哪個文件去加載這個類了福贞。

但是一旦文件多起來的話,映射數(shù)組會變得很長停士,這樣的話維護起來會相當麻煩挖帘。如果命名能遵守統(tǒng)一的約定,就可以讓自動加載器自動解析判斷類文件所在的路徑恋技。接下來要介紹的PSR-4 就是一種被廣泛采用的約定方式拇舀。

=================PSR-4規(guī)范==================

PSR-4 是關(guān)于由文件路徑自動載入對應類的相關(guān)規(guī)范,規(guī)范規(guī)定了一個完全限定類名需要具有以下結(jié)構(gòu):

\<頂級命名空間>(\<子命名空間>)*\<類名></pre>

如果繼續(xù)拿上面的例子打比方的話猖任,頂級命名空間相當于公司你稚,子命名空間相當于職位,類名相當于人名。那么李彥宏標準的稱呼為 "百度公司 CEO 李彥宏"刁赖。

PSR-4 規(guī)范中必須要有一個頂級命名空間搁痛,它的意義在于表示某一個特殊的目錄(文件基目錄)。子命名空間代表的是類文件相對于文件基目錄的這一段路徑(相對路徑)宇弛,類名則與文件名保持一致(注意大小寫的區(qū)別)鸡典。

舉個例子:在全限定類名 \app\view\news\Index 中,如果 app 代表 C:\Baidu枪芒,那么這個類的路徑則是 C:\Baidu\view\news\Index.php

我們就以解析 \app\view\news\Index 為例彻况,編寫一個簡單的 Demo:

$class = 'app\view\news\Index'; /* 頂級命名空間路徑映射 */
$vendor_map = array( 'app' => 'C:\Baidu', ); /* 解析類名為文件路徑 */
$vendor = substr($class, 0, strpos($class, '\\')); // 取出頂級命名空間[app]
$vendor_dir = $vendor_map[$vendor]; // 文件基目錄[C:\Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // 相對路徑[/view/news]
$file_name = basename($class) . '.php'; // 文件名[Index.php]

/* 輸出文件所在路徑 */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;

通過這個 Demo 可以看出限定類名轉(zhuǎn)換為路徑的過程。那么現(xiàn)在就讓我們用規(guī)范的面向?qū)ο蠓绞饺崿F(xiàn)自動加載器吧舅踪。

首先我們創(chuàng)建一個文件 Index.php纽甘,它處于 \app\mvc\view\home 目錄中:

namespace app\mvc\view\home; class Index
{ function __construct()
    { echo '<h1> Welcome To Home </h1>';
    }
}

接著我們在創(chuàng)建一個加載類(不需要命名空間),它處于 \ 目錄中:

class Loader
{ /* 路徑映射 */
    public static $vendorMap = array( 'app' => __DIR__ . DIRECTORY_SEPARATOR . 'app', ); /**
     * 自動加載器 */
    public static function autoload($class)
    { $file = self::findFile($class); if (file_exists($file)) {
            self::includeFile($file);
        }
    } /**
     * 解析文件路徑 */
    private static function findFile($class)
    { $vendor = substr($class, 0, strpos($class, '\\')); // 頂級命名空間
        $vendorDir = self::$vendorMap[$vendor]; // 文件基目錄
        $filePath = substr($class, strlen($vendor)) . '.php'; // 文件相對路徑
        return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // 文件標準路徑
 } /**
     * 引入文件 */
    private static function includeFile($file)
    { if (is_file($file)) { include $file;
        }
    }
}

最后抽碌,將 Loader 類中的 autoload 注冊到 spl_autoload_register 函數(shù)中:

include 'Loader.php'; // 引入加載器
spl_autoload_register('Loader::autoload'); // 注冊自動加載

new \app\mvc\view\home\Index(); // 實例化未引用的類

/**
 * 輸出: <h1> Welcome To Home </h1> */

示例中的代碼其實就是 ThinkPHP 自動加載器源碼的精簡版悍赢,它是 ThinkPHP 5 能實現(xiàn)惰性加載的關(guān)鍵。

至此货徙,自動加載的原理已經(jīng)全部講完了左权,如果有興趣深入了解的話,可以參考下面的 ThinkPHP 源碼痴颊。

TP5 自動加載器源碼

class Loader
{
    protected static $instance = [];
    // 類名映射
    protected static $map = [];

    // 命名空間別名
    protected static $namespaceAlias = [];

    // PSR-4
    private static $prefixLengthsPsr4 = [];
    private static $prefixDirsPsr4    = [];
    private static $fallbackDirsPsr4  = [];

    // PSR-0
    private static $prefixesPsr0     = [];
    private static $fallbackDirsPsr0 = [];

    // 自動加載的文件
    private static $autoloadFiles = [];

    // 自動加載
    public static function autoload($class)
    {
        // 檢測命名空間別名
        if (!empty(self::$namespaceAlias)) {
            $namespace = dirname($class);
            if (isset(self::$namespaceAlias[$namespace])) {
                $original = self::$namespaceAlias[$namespace] . '\\' . basename($class);
                if (class_exists($original)) {
                    return class_alias($original, $class, false);
                }
            }
        }

        if ($file = self::findFile($class)) {

            // Win環(huán)境嚴格區(qū)分大小寫
            if (IS_WIN && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
                return false;
            }

            __include_file($file);
            return true;
        }
    }

    /**
     * 查找文件
     * @param $class
     * @return bool
     */
    private static function findFile($class)
    {
        if (!empty(self::$map[$class])) {
            // 類庫映射
            return self::$map[$class];
        }

        // 查找 PSR-4
        $logicalPathPsr4 = strtr($class, '\\', DS) . EXT;

        $first = $class[0];
        if (isset(self::$prefixLengthsPsr4[$first])) {
            foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
                if (0 === strpos($class, $prefix)) {
                    foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
                        if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }

        // 查找 PSR-4 fallback dirs
        foreach (self::$fallbackDirsPsr4 as $dir) {
            if (is_file($file = $dir . DS . $logicalPathPsr4)) {
                return $file;
            }
        }

        // 查找 PSR-0
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DS) . EXT;
        }

        if (isset(self::$prefixesPsr0[$first])) {
            foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (is_file($file = $dir . DS . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // 查找 PSR-0 fallback dirs
        foreach (self::$fallbackDirsPsr0 as $dir) {
            if (is_file($file = $dir . DS . $logicalPathPsr0)) {
                return $file;
            }
        }

        return self::$map[$class] = false;
    }

    // 注冊classmap
    public static function addClassMap($class, $map = '')
    {
        if (is_array($class)) {
            self::$map = array_merge(self::$map, $class);
        } else {
            self::$map[$class] = $map;
        }
    }

    // 注冊命名空間
    public static function addNamespace($namespace, $path = '')
    {
        if (is_array($namespace)) {
            foreach ($namespace as $prefix => $paths) {
                self::addPsr4($prefix . '\\', rtrim($paths, DS), true);
            }
        } else {
            self::addPsr4($namespace . '\\', rtrim($path, DS), true);
        }
    }

    // 添加Ps0空間
    private static function addPsr0($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                self::$fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    self::$fallbackDirsPsr0
                );
            } else {
                self::$fallbackDirsPsr0 = array_merge(
                    self::$fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset(self::$prefixesPsr0[$first][$prefix])) {
            self::$prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            self::$prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                self::$prefixesPsr0[$first][$prefix]
            );
        } else {
            self::$prefixesPsr0[$first][$prefix] = array_merge(
                self::$prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    // 添加Psr4空間
    private static function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                self::$fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    self::$fallbackDirsPsr4
                );
            } else {
                self::$fallbackDirsPsr4 = array_merge(
                    self::$fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            self::$prefixDirsPsr4[$prefix]                = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            self::$prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                self::$prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            self::$prefixDirsPsr4[$prefix] = array_merge(
                self::$prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    // 注冊命名空間別名
    public static function addNamespaceAlias($namespace, $original = '')
    {
        if (is_array($namespace)) {
            self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace);
        } else {
            self::$namespaceAlias[$namespace] = $original;
        }
    }

    // 注冊自動加載機制
    public static function register($autoload = '')
    {
        // 注冊系統(tǒng)自動加載
        spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
        // 注冊命名空間定義
        self::addNamespace([
            'think'    => LIB_PATH . 'think' . DS,
            'behavior' => LIB_PATH . 'behavior' . DS,
            'traits'   => LIB_PATH . 'traits' . DS,
        ]);
        // 加載類庫映射文件
        if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
            self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
        }

        // Composer自動加載支持
        if (is_dir(VENDOR_PATH . 'composer')) {
            self::registerComposerLoader();
        }

        // 自動加載extend目錄
        self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
    }

    // 注冊composer自動加載
    private static function registerComposerLoader()
    {
        if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) {
            $map = require VENDOR_PATH . 'composer/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                self::addPsr0($namespace, $path);
            }
        }

        if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) {
            $map = require VENDOR_PATH . 'composer/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                self::addPsr4($namespace, $path);
            }
        }

        if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) {
            $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php';
            if ($classMap) {
                self::addClassMap($classMap);
            }
        }

        if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) {
            $includeFiles = require VENDOR_PATH . 'composer/autoload_files.php';
            foreach ($includeFiles as $fileIdentifier => $file) {
                if (empty(self::$autoloadFiles[$fileIdentifier])) {
                    __require_file($file);
                    self::$autoloadFiles[$fileIdentifier] = true;
                }
            }
        }
    }

    /**
     * 導入所需的類庫 同java的Import 本函數(shù)有緩存功能
     * @param string $class   類庫命名空間字符串
     * @param string $baseUrl 起始路徑
     * @param string $ext     導入的文件擴展名
     * @return boolean
     */
    public static function import($class, $baseUrl = '', $ext = EXT)
    {
        static $_file = [];
        $key          = $class . $baseUrl;
        $class        = str_replace(['.', '#'], [DS, '.'], $class);
        if (isset($_file[$key])) {
            return true;
        }

        if (empty($baseUrl)) {
            list($name, $class) = explode(DS, $class, 2);

            if (isset(self::$prefixDirsPsr4[$name . '\\'])) {
                // 注冊的命名空間
                $baseUrl = self::$prefixDirsPsr4[$name . '\\'];
            } elseif ('@' == $name) {
                //加載當前模塊應用類庫
                $baseUrl = App::$modulePath;
            } elseif (is_dir(EXTEND_PATH . $name)) {
                $baseUrl = EXTEND_PATH;
            } else {
                // 加載其它模塊的類庫
                $baseUrl = APP_PATH . $name . DS;
            }
        } elseif (substr($baseUrl, -1) != DS) {
            $baseUrl .= DS;
        }
        // 如果類存在 則導入類庫文件
        if (is_array($baseUrl)) {
            foreach ($baseUrl as $path) {
                $filename = $path . DS . $class . $ext;
                if (is_file($filename)) {
                    break;
                }
            }
        } else {
            $filename = $baseUrl . $class . $ext;
        }

        if (!empty($filename) && is_file($filename)) {
            // 開啟調(diào)試模式Win環(huán)境嚴格區(qū)分大小寫
            if (IS_WIN && pathinfo($filename, PATHINFO_FILENAME) != pathinfo(realpath($filename), PATHINFO_FILENAME)) {
                return false;
            }
            __include_file($filename);
            $_file[$key] = true;
            return true;
        }
        return false;
    }

    /**
     * 實例化(分層)模型
     * @param string $name         Model名稱
     * @param string $layer        業(yè)務層名稱
     * @param bool   $appendSuffix 是否添加類名后綴
     * @param string $common       公共模塊名
     * @return Object
     * @throws ClassNotFoundException
     */
    public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
    {
        if (isset(self::$instance[$name . $layer])) {
            return self::$instance[$name . $layer];
        }
        if (strpos($name, '/')) {
            list($module, $name) = explode('/', $name, 2);
        } else {
            $module = Request::instance()->module();
        }
        $class = self::parseClass($module, $layer, $name, $appendSuffix);
        if (class_exists($class)) {
            $model = new $class();
        } else {
            $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
            if (class_exists($class)) {
                $model = new $class();
            } else {
                throw new ClassNotFoundException('class not exists:' . $class, $class);
            }
        }
        self::$instance[$name . $layer] = $model;
        return $model;
    }

    /**
     * 實例化(分層)控制器 格式:[模塊名/]控制器名
     * @param string $name         資源地址
     * @param string $layer        控制層名稱
     * @param bool   $appendSuffix 是否添加類名后綴
     * @param string $empty        空控制器名稱
     * @return Object|false
     * @throws ClassNotFoundException
     */
    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
    {
        if (strpos($name, '/')) {
            list($module, $name) = explode('/', $name);
        } else {
            $module = Request::instance()->module();
        }
        $class = self::parseClass($module, $layer, $name, $appendSuffix);
        if (class_exists($class)) {
            return new $class(Request::instance());
        } elseif ($empty && class_exists($emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix))) {
            return new $emptyClass(Request::instance());
        }
    }

    /**
     * 實例化驗證類 格式:[模塊名/]驗證器名
     * @param string $name         資源地址
     * @param string $layer        驗證層名稱
     * @param bool   $appendSuffix 是否添加類名后綴
     * @param string $common       公共模塊名
     * @return Object|false
     * @throws ClassNotFoundException
     */
    public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
    {
        $name = $name ?: Config::get('default_validate');
        if (empty($name)) {
            return new Validate;
        }

        if (isset(self::$instance[$name . $layer])) {
            return self::$instance[$name . $layer];
        }
        if (strpos($name, '/')) {
            list($module, $name) = explode('/', $name);
        } else {
            $module = Request::instance()->module();
        }
        $class = self::parseClass($module, $layer, $name, $appendSuffix);
        if (class_exists($class)) {
            $validate = new $class;
        } else {
            $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
            if (class_exists($class)) {
                $validate = new $class;
            } else {
                throw new ClassNotFoundException('class not exists:' . $class, $class);
            }
        }
        self::$instance[$name . $layer] = $validate;
        return $validate;
    }

    /**
     * 數(shù)據(jù)庫初始化 并取得數(shù)據(jù)庫類實例
     * @param mixed         $config 數(shù)據(jù)庫配置
     * @param bool|string   $name 連接標識 true 強制重新連接
     * @return \think\db\Connection
     */
    public static function db($config = [], $name = false)
    {
        return Db::connect($config, $name);
    }

    /**
     * 遠程調(diào)用模塊的操作方法 參數(shù)格式 [模塊/控制器/]操作
     * @param string       $url          調(diào)用地址
     * @param string|array $vars         調(diào)用參數(shù) 支持字符串和數(shù)組
     * @param string       $layer        要調(diào)用的控制層名稱
     * @param bool         $appendSuffix 是否添加類名后綴
     * @return mixed
     */
    public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
    {
        $info   = pathinfo($url);
        $action = $info['basename'];
        $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
        $class  = self::controller($module, $layer, $appendSuffix);
        if ($class) {
            if (is_scalar($vars)) {
                if (strpos($vars, '=')) {
                    parse_str($vars, $vars);
                } else {
                    $vars = [$vars];
                }
            }
            return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
        }
    }

    /**
     * 字符串命名風格轉(zhuǎn)換
     * type 0 將Java風格轉(zhuǎn)換為C的風格 1 將C風格轉(zhuǎn)換為Java的風格
     * @param string  $name 字符串
     * @param integer $type 轉(zhuǎn)換類型
     * @return string
     */
    public static function parseName($name, $type = 0)
    {
        if ($type) {
            return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) {
                return strtoupper($match[1]);
            }, $name));
        } else {
            return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
        }
    }

    /**
     * 解析應用類的類名
     * @param string $module 模塊名
     * @param string $layer  層名 controller model ...
     * @param string $name   類名
     * @param bool   $appendSuffix
     * @return string
     */
    public static function parseClass($module, $layer, $name, $appendSuffix = false)
    {
        $name  = str_replace(['/', '.'], '\\', $name);
        $array = explode('\\', $name);
        $class = self::parseName(array_pop($array), 1) . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
        $path  = $array ? implode('\\', $array) . '\\' : '';
        return App::$namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class;
    }

    /**
     * 初始化類的實例
     * @return void
     */
    public static function clearInstance()
    {
        self::$instance = [];
    }
}

/**
 * 作用范圍隔離
 *
 * @param $file
 * @return mixed
 */
function __include_file($file)
{
    return include $file;
}

function __require_file($file)
{
    return require $file;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赏迟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蠢棱,更是在濱河造成了極大的恐慌锌杀,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裳扯,死亡現(xiàn)場離奇詭異抛丽,居然都是意外死亡谤职,警方通過查閱死者的電腦和手機饰豺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允蜈,“玉大人冤吨,你說我怎么就攤上這事∪奶祝” “怎么了漩蟆?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妓蛮。 經(jīng)常有香客問我怠李,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任捺癞,我火速辦了婚禮夷蚊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘髓介。我一直安慰自己惕鼓,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布唐础。 她就那樣靜靜地躺著箱歧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪一膨。 梳的紋絲不亂的頭發(fā)上呀邢,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音豹绪,去河邊找鬼驼鹅。 笑死,一個胖子當著我的面吹牛森篷,可吹牛的內(nèi)容都是我干的输钩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼仲智,長吁一口氣:“原來是場噩夢啊……” “哼买乃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钓辆,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤剪验,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后前联,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體功戚,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年似嗤,在試婚紗的時候發(fā)現(xiàn)自己被綠了啸臀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡烁落,死狀恐怖乘粒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伤塌,我是刑警寧澤灯萍,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站每聪,受9級特大地震影響旦棉,放射性物質(zhì)發(fā)生泄漏齿风。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一绑洛、第九天 我趴在偏房一處隱蔽的房頂上張望聂宾。 院中可真熱鬧,春花似錦诊笤、人聲如沸系谐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纪他。三九已至,卻和暖如春晾匠,著一層夾襖步出監(jiān)牢的瞬間茶袒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工凉馆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留薪寓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓澜共,卻偏偏與公主長得像向叉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嗦董,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 本文分為兩部分:第一部分講__autoload()函數(shù)實現(xiàn)的類自動加載母谎。第二部分講spl_autoload_reg...
    舒小賤閱讀 2,554評論 1 4
  • 在PHP開發(fā)過程中,如果希望從外部引入一個class京革,通常會使用include和require方法奇唤,去把定義這...
    四月不見閱讀 1,045評論 0 0
  • 說說PHP的autoLoad(http://www.cnblogs.com/yjf512/archive/2012...
    古則閱讀 453評論 0 1
  • 一 預定義接口 1.1 遍歷 Traversable(遍歷)接口 檢測一個類是否可以被foreach遍歷,該接口不...
    coderhu閱讀 1,199評論 0 0
  • 柳影經(jīng)時綠匹摇,蟬吟弄寂寥咬扇。 湖邊風袖媚,水里藕花嬌廊勃。 好景不常在懈贺,煩心恐難消。 清波邀我處供搀,鷗伴自逍遙隅居。 寂寞應為寂...
    江南莫之閱讀 1,572評論 30 34