小滕的博客

小滕的技术点滴

ThinkPHP3.2.3的Autoload分析及七牛云存储的实例应用

1 year ago · 3 MIN READ
#PHP 

文件定位:ThinkPHP/Library/Think/Think.class.php:

class Think {
    // 类映射
    private static $_map      = array();

    // 实例化对象
    private static $_instance = array();

    /**
     * 应用程序初始化
     * @access public
     * @return void
     */
    static public function start() {
      // 注册AUTOLOAD方法
      spl_autoload_register('Think\Think::autoload');      
      // 设定错误和异常处理
      register_shutdown_function('Think\Think::fatalError');
      set_error_handler('Think\Think::appError');
      set_exception_handler('Think\Think::appException');

      // 初始化文件存储方式
      Storage::connect(STORAGE_TYPE);
      // 省略Coding...
    }
}

可以看到,在 start() 方法中使用了 spl_autoload_register(),锁定到 Think\Think::autoload

    /**
     * 类库自动加载
     * @param string $class 对象类名
     * @return void
     */
    public static function autoload($class) {
        // 检查是否存在映射
        if(isset(self::$_map[$class])) {
            include self::$_map[$class];
        }elseif(false !== strpos($class,'\\')){
          $name           =   strstr($class, '\\', true);
          if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){ 
              // Library目录下面的命名空间自动定位
              $path       =   LIB_PATH;
          }else{
              // 检测自定义命名空间 否则就以模块为命名空间
              $namespace  =   C('AUTOLOAD_NAMESPACE');
              $path       =   isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH;
          }
          $filename       =   $path . str_replace('\\', '/', $class) . EXT;
          if(is_file($filename)) {
              // Win环境下面严格区分大小写
              if (IS_WIN && false === strpos(str_replace('/', '\\', realpath($filename)), $class . EXT)){
                  return ;
              }
              include $filename;
          }
        }elseif (!C('APP_USE_NAMESPACE')) {
            // 自动加载的类库层
            foreach(explode(',',C('APP_AUTOLOAD_LAYER')) as $layer){
                if(substr($class,-strlen($layer))==$layer){
                    if(require_cache(MODULE_PATH.$layer.'/'.$class.EXT)) {
                        return ;
                    }
                }            
            }
            // 根据自动加载路径设置进行尝试搜索
            foreach (explode(',',C('APP_AUTOLOAD_PATH')) as $path){
                if(import($path.'.'.$class))
                    // 如果加载类成功则返回
                    return ;
            }
        }
    }

$class 的结构是这样的:NameSpace\ClassName.

从整体的结构来看,整个 autoload() 分为三大结构:

  • 已经加载的类直接返回(可以直接忽略)
if(isset(self::$_map[$class])) {
    include self::$_map[$class];
}
  • 命名空间的定位加载
    • 预定义命名空间
$name = strstr($class, '\\', true);
if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){ 
    // Library目录下面的命名空间自动定位
    $path       =   LIB_PATH;
}

预定义的有:

命名空间 路径
Think\ /ThinkPHP/Library/Think
Org\ /ThinkPHP/Library/Org
Behavior\ /ThinkPHP/Library/Behavior
Com\ /ThinkPHP/Library/Com
Vendor\ /ThinkPHP/Library/Vendor
  • 命名空间的定位加载
    • 自定义命名空间(通过配置AUTOLOAD_NAMESPACE
$name  = strstr($class, '\\', true);
if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){ 
    //...
}else{
    // 检测自定义命名空间 否则就以模块为命名空间
    $namespace  =   C('AUTOLOAD_NAMESPACE');
    $path       =   isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH;
}

这里是通过下面的配置实现的:

array(
    'AUTOLOAD_NAMESPACE' => array(
        // 命名空间 => 路径
        'Demo\Demo' => APP_PATH . '/DEMO/',
    ),
);
  • 通过 import() 加载

import() 代码如下:

/**
 * 导入所需的类库 同java的Import 本函数有缓存功能
 * @param string $class 类库命名空间字符串
 * @param string $baseUrl 起始路径
 * @param string $ext 导入的文件扩展名
 * @return boolean
 */
function import($class, $baseUrl = '', $ext=EXT) {
    static $_file = array();
    $class = str_replace(array('.', '#'), array('/', '.'), $class);
    if (isset($_file[$class . $baseUrl]))
        return true;
    else
        $_file[$class . $baseUrl] = true;
    $class_strut     = explode('/', $class);
    if (empty($baseUrl)) {
        if ('@' == $class_strut[0] || MODULE_NAME == $class_strut[0]) {
            //加载当前模块的类库
            $baseUrl = MODULE_PATH;
            $class   = substr_replace($class, '', 0, strlen($class_strut[0]) + 1);
        }elseif ('Common' == $class_strut[0]) {
            //加载公共模块的类库
            $baseUrl = COMMON_PATH;
            $class   = substr($class, 7);
        }elseif (in_array($class_strut[0],array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$class_strut[0])) {
            // 系统类库包和第三方类库包
            $baseUrl = LIB_PATH;
        }else { // 加载其他模块的类库
            $baseUrl = APP_PATH;
        }
    }
    if (substr($baseUrl, -1) != '/')
        $baseUrl    .= '/';
    $classfile       = $baseUrl . $class . $ext;
    if (!class_exists(basename($class),false)) {
        // 如果类不存在 则导入类库文件
        return require_cache($classfile);
    }
    return null;
}

其加载有两个方向:

  • 第一:不指定 baseUrl 那就根据 $class 分析 namespace 对应的目录 去搜索加载。
  • 第二:从指定 baseUrl 目录直接加载。

综上所述,ThinkPHP 给了开发者三个加载类的选项:

  • 一、将类放在预定义的命名空间的对应目录中,修改的类的文件名使得文件名如这种格式:ClassName.class.php,然后再需要用到的地方直接 use NameSpace\ClassName;即可。

  • 二、预先定义好 NameSpacePath 的关联数组,使用方法同上。

  • 三、通过 import() 函数加载,不需要解释吧? ^ - ^。

代码逻辑很简单,就分析到这里了。接下来我将用上面的分析结果来加载最新的七牛SDK。当前七牛版本:v7.1.3

七牛SDK结构如下:
QQ截图20170618091046.png

  • 首先,我在 ThinkPHP 的根目录创建一个 vendor 目录,然后将 src/Qiniu 解压到这个目录。

  • 通过分析七牛SDK的根命名空间为:Qiniu,所以配置如下(我在 Home/Conf/config.php 中配置):
<?php
return array(
    /** 注册命名空间 */
    'AUTOLOAD_NAMESPACE' => array(
        'Qiniu' => APP_PATH . '../vendor/Qiniu/',
    ),
);
  • 第三、将 vendor/src/Qiniu 中的文件名修改如下图所示:
    QQ截图20170618092452.png

Q:为什么这样修改?

A:因为 ThinkPHP 的默认后缀是 class.php,如果是 .php 就不需要修改啦。

配置到这里似乎已经结束,于是在 IndexController 写上下面代码:

<?php
namespace Home\Controller;

use Qiniu\Auth;
use Qiniu\Storage\UploadManager;
use Think\Controller;

class IndexController extends Controller {
    public function index(){
        $upManager = new UploadManager();
        $auth = new Auth('key', 'secret');
        $token = $auth->uploadToken('test');
        var_dump($token);
    }
}

运行代码发现,报错:

Call to undefined function Qiniu\base64_urlSafeEncode()

从错误中可以看到七牛SDK使用了命名空间封装了部分函数,但是 ThinkPHPautoload() 并没有对此进行加载处理,那怎么办呢?这里需要用到 ThinkPHP 提供的 load() 方法,是专门用来加载命名空间的函数的。其代码如下:

/**
 * 基于命名空间方式导入函数库
 * load('@.Util.Array')
 * @param string $name 函数库命名空间字符串
 * @param string $baseUrl 起始路径
 * @param string $ext 导入的文件扩展名
 * @return void
 */
function load($name, $baseUrl='', $ext='.php') {
    $name = str_replace(array('.', '#'), array('/', '.'), $name);
    if (empty($baseUrl)) {
        if (0 === strpos($name, '@/')) {//加载当前模块函数库
            $baseUrl    =   MODULE_PATH.'Common/';
            $name       =   substr($name, 2);
        } else { //加载其他模块函数库
            $array      =   explode('/', $name);
            $baseUrl    =   APP_PATH . array_shift($array).'/Common/';
            $name       =   implode('/',$array);
        }
    }
    if (substr($baseUrl, -1) != '/')
        $baseUrl       .= '/';
    require_cache($baseUrl . $name . $ext);
}

所以,我们可以这样加载七牛云的命名空间函数:

load('functions', APP_PATH . '../vendor/Qiniu/', '.class.php');

注意:前面我们修改了 vendor/Qiniu 目录下的所有文件后缀为 .class.php, 所以这里需要指明文件后缀。

完整Demo代码如下:

<?php
namespace Home\Controller;

use Qiniu\Auth;
use Qiniu\Storage\UploadManager;
use Think\Controller;

class IndexController extends Controller {
    public function index(){
        /** 加载七牛函数库 */
        load('functions', APP_PATH . '../vendor/Qiniu/');

        $upManager = new UploadManager();
        $auth = new Auth('key', 'secret');
        $token = $auth->uploadToken('test');
        var_dump($token);
    }
}

其打印结果:

string(274) "81yrIjhoaYsavkp4WuL5BzhtbygqUyWChBdndzK0:9LGNl5njR8V7P73pW1NPdwJ2Rug=:eyJzY29wZSI6InRlc3QiLCJkZWFkbGluZSI6MTQ5Nzc1MjYwOSwidXBIb3N0cyI6WyJodHRwOlwvXC91cC16Mi5xaW5pdS5jb20iLCJodHRwOlwvXC91cGxvYWQtejIucWluaXUuY29tIiwiLUggdXAtejIucWluaXUuY29tIGh0dHA6XC9cLzE4My42MC4yMTQuMTk4Il19"

That's ALL.^ - ^.

···

xiao teng



备案号:皖ICP备14012032号-5