1. 程式人生 > >實現 PSR-0和PSR-4的類自動載入器並帶案例說明

實現 PSR-0和PSR-4的類自動載入器並帶案例說明

大家在閱讀文件 或者使用一些第三方的框架或者軟體的時候,都聽過或者看過裡面要求說實現了psr0或者psr4的規範。

我也一直在查資料,找痕跡。現在我的理解是,其實這2個規範就是對類的裝載,實現自動尋路徑


首先我們看下 PSR0

我寫程式碼實現了它的自動載入器

這是載入器程式碼

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:27
 */
class Psr0{
    /* psr-0 規範說明
     * 1.一個完整的標準的類檔案格式是這樣的 \vendor\namespace\class
     * 2.每個名稱空間必須有一個頂級名稱空間 子名稱空間可以有多個 或者沒有
     * 3.載入檔案的時候 名稱空間分隔符會被轉換為 DIRECTORY_SEPARATOR
     * 4.類名如果帶'_',都會轉換為DIRECTORY_SEPARATOR,_的拼接部分必須是類目錄的子目錄部分並且是一一對應
     * 打個比方 Tik_Tb_Order.php  其中 Tik和Tb必須是該類檔案所在的子目錄。
     * 5.載入的檔案字尾必須以.php結尾
     * 6.verdor namespace class必須由大小寫字母組合而成
     */
    public static function autoload($className)
    {
        //去掉最左邊的\
        $className = ltrim($className,'\\');
        //獲取名稱空間
        $position = strrpos($className,'\\');
        $strnamespaces = substr($className,0,$position);
        //獲取類名
        $class = substr($className,$position+1);
        $namespaces = explode('\\',$strnamespaces);
        //組裝類路徑。。psr0規定 名稱空間分隔符要被DIRECTORY_SEPARATOR替換。
        $file_path = '';
        foreach ($namespaces as $namespace)
        {
            $file_path .= $namespace.DIRECTORY_SEPARATOR;
        }
        //類名下劃線處理
        $class_file = str_replace('_',DIRECTORY_SEPARATOR,$class);
        //最終的file
        $file = './vendor/'.$file_path.$class_file.'.php';
        include $file;
    }
}

然後我的檔案目錄


我的呼叫程式碼 是 start.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:22
 */
header("Content-type: text/html; charset=utf-8");
require 'Psr0.php';
//定義類的自動載入器
spl_autoload_register('Psr0::autoload');
//測試
$three = new \Com\Three();
$one = new \Com\One\One();
$two = new \Com\One\Fki_Tb_Two();
$three->sh();
$one->sh();
$two->sh();

這是執行效果



看懂了沒 沒的話 我帶大家分析一波。

1.首先 psr0規定 必須有個組織名 我這裡是vendor 然後得有個頂級名稱空間 我這裡是Com

開啟我的three.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:49
 */
namespace Com;

class Three{
    public function sh()
    {
        echo '我是three'.'<br>';
    }
}

我這個類就在當前頂級名稱空間下 且沒有子名稱空間 當然也可以被載入到。


再看one.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 10:30
 */
namespace Com\One;
class One{
    public function sh()
    {
        echo '我是one'.'<br>';
    }
}

我這裡是設定了子名稱空間 為One 根據psr0規範。子名稱空間必須是類的檔案系統載入的子目錄。所以也能通過。

我們再看fki_Tb_two.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 10:34
 */
namespace Com\One;

class Fki_Tb_Two{
    public function sh(){
        echo '我是tow'.'<br>';
    }
}

這個類名帶下劃線 psr0規範說  下劃線最後的一部分才是類名。其餘部分充當該類被載入的子目錄部分,並且和子目錄名字母大小一一對應 在本例中。根據我上面目錄結構圖,它是這個類的2個上一級目錄名。所以也能被載入。


我們再看psr4

我先貼出載入器程式碼

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:27
 */
//單名稱空間
class Psr4_1{
    public static function autoload($className)
    {
        /*
         * Psr-4規範說明
         * 1.合法的類的完全限定名格式是 \namespacename\subnamespace\class
         * 2.必須有一個頂級的名稱空間
         * 3.必須有一個終止類名
         * 4.下劃線在類中無特殊意義
         * 5.子名稱空間必須對於從檔案系統載入類檔案的一個子目錄
         * 6.子目錄和子名稱空間必須大小寫和字母一一對應。子名稱空間分割符表示子目錄分隔符
         * 7.
         */



        //設定名稱空間目錄對映
        $namespace_prefix='Lib1\\Red1';
        $base_dir='./Lib1/Red';

        //去掉最左邊的\
        $className = ltrim($className,'\\');
        //獲取最右邊的分隔符位置 用來做名稱空間和class的分界點
        $position = strrpos($className,'\\');
        //獲取名稱空間部分
        $strnamespaces = substr($className,0,$position);
        //判斷字首是否存在 。0表示是合法的,false直接返回錯誤
        $is_exsists = strpos($strnamespaces,$namespace_prefix);
        if($is_exsists!==0)
        {
            return;
        }
        //獲取子名稱空間部分
        $Len = strlen($namespace_prefix);
        $str_sub_namespace = substr($strnamespaces,$Len);
        $str_sub_namespace_path = str_replace('\\',DIRECTORY_SEPARATOR,$str_sub_namespace);
        //獲取類名
        $class = substr($className,$position+1);

        //拼接目錄
        $file = $base_dir.$str_sub_namespace_path.DIRECTORY_SEPARATOR.$class.'.php';
        include $file;

    }
}

在貼出呼叫程式碼

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:22
 */
header("Content-type: text/html; charset=utf-8");
require 'Psr4_1.php';
//定義類的自動載入器
spl_autoload_register('Psr4_1::autoload');

$red = new \Lib1\Red1\Red();

$green = new \Lib1\Red1\Sub\Green();

$red_green = new \Lib1\Red1\Red_Green();

$red->sh();

$green->sh();

$red_green->sh();

效果圖



再是目錄結構圖



我分析一下吧。

首先 psr4是要求名稱空間字首和base_dir有個設定好的對應關係。

我這個psr4_1是一個單名稱空間的載入器。稍後我發一個批量的。先拿這個單的說明

我們開啟red.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 14:05
 */
namespace Lib1\Red1;

class Red{
    public function sh(){
        echo '我是red<br>';
    }
}


這裡 我們的名稱空間 符合之前預設定好的對映關係。使用能被載入。


再看一個帶子名稱空間的例子

green.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 15:24
 */
namespace Lib1\Red1\Sub;

class Green{
    public function sh(){
        echo '我是green<br>';
    }
}

這裡的子名稱空間的意思 就是他不在預設值的名稱空間字首裡面 也就是沒包含他。

那麼要讓載入器載入到他  我們必須把這個Sub也就是子名稱空間 對應一個子目錄。也就是在設定好的那個對映關係basedir後面新增一個子目錄  要求字母大小一一對應即可。

詳情請看我的目錄結構圖 

psr4規範說明 類名的下劃線格式 並沒有實際的意義..這個和psr0是區別的。

看red_green.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 14:05
 */
namespace Lib1\Red1;

class Red_Green{
    public function sh(){
        echo '我是red_green<br>';
    }
}

把這個當成是一個普通的類檔案進行載入

明白這個原理後  我貼出多名稱空間支援的psr4載入器 也就是我的psr4_2.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:27
 */
//支援多名稱空間部署
class Psr4_2{
    private static $_classMapper = '';
    public static function addClassMapper($prefix,$dir)
    {
        self::$_classMapper[$prefix] = $dir;
    }
    public static function autoload($className)
    {
        //去掉最左邊的\
        $className = ltrim($className,'\\');
        //獲取最右邊的分隔符位置 用來做名稱空間和class的分界點
        $position = strrpos($className,'\\');
        //獲取名稱空間部分
        $strnamespaces = substr($className,0,$position);

        //判斷字首是否存在 。0表示是合法的,false直接返回錯誤
        foreach (self::$_classMapper as $mapperkey=> $classmapper)
        {
            $namespace_prefix = $mapperkey;

            $is_exsists = strpos($strnamespaces,$namespace_prefix);

            if($is_exsists!==false)
            {
                //獲取子名稱空間部分
                $Len = strlen($namespace_prefix);
                $str_sub_namespace = substr($strnamespaces,$Len);
                $str_sub_namespace_path = str_replace('\\',DIRECTORY_SEPARATOR,$str_sub_namespace);
                //獲取類名
                $class = substr($className,$position+1);
                //拼接目錄
                $base_dir = self::$_classMapper[$namespace_prefix];
                $file = $base_dir.$str_sub_namespace_path.DIRECTORY_SEPARATOR.$class.'.php';
                include $file;
                return;
            }
        }
    }
}

我在貼出我的呼叫程式碼

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:22
 */
header("Content-type: text/html; charset=utf-8");
require 'Psr4_2.php';
spl_autoload_register('Psr4_2::autoload');
//定義類的自動載入器
Psr4_2::addClassMapper('Lib1\Red1','./Lib1/Red');
Psr4_2::addClassMapper('Lib2','./Lib2');
Psr4_2::addClassMapper('Lib3\Hei','./lib3/hei/src');

$red = new \Lib1\Red1\Red();
$red->sh();

$green = new \Lib1\Red1\Sub\Green();
$green->sh();

$red_green = new \Lib1\Red1\Red_Green();
$red_green->sh();

$bule = new \Lib2\Bule();
$bule->sh();

$ming = new \Lib1\Red1\sub\Subb\Ming();
$ming->sh();

$fi_cc = new \Lib2\Fi_CC();
$fi_cc->sh();

$hei = new \Lib3\Hei\Hei();
$hei->sh();

$req = new \Lib3\Hei\Req\Req();
$req->sh();

效果圖




抽幾個具有代表性的分析一下 給大家看。

看我的req.php


檔案程式碼

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 17:56
 */

namespace Lib3\Hei\Req;

class Req{
    public function sh(){
        echo '我是req<br>';
    }
}

也是存在一個子名稱空間的問題 我們只要指定對應的子目錄就能實現對他的載入。

總結一下  :根據psr4的自動載入規則 我們可以根據配置好的名稱空間字首。快速定位類的檔案位置。還有一點,為了開發的便捷性 我們可以配置多個名稱空間字首 ,使類檔案避免深度索引。