11

说明:本文主要以Laravel的容器类Container为例做简单说明Composer的自动加载机制。

注:上篇文章基于Laravel Task-Scheduler定时发送邮件小程序聊到本打算接下来聊聊Laravel的设计模式,不过作者水平有限还需提高一段时间,故暂不先误人子弟了。这次先一起聊聊Composer的自动加载机制,并以Laravel的Container为例举例。

Composer的自动加载机制

1、初始化一个composer项目
在一个空目录下composer安装Laravel的容器Container包:

composer require illuminate/container

然后在该目录下新建一个index.php文件,然后分析下Container类为何能被实例化:

<?php
/**
 * Created by PhpStorm.
 * User: liuxiang
 * Date: 16/5/12
 * Time: 19:59
 */
require_once __DIR__.'/vendor/autoload.php';

$container = new Illuminate\Container\Container();
var_dump($container);

2、分析下composer加载类的流程
图片描述

使用composer最大的好处是只需最开始require一个autoload.php文件,就可以new你所需要的类了,不再需要传统的方式A文件内各种include跳到B文件又各种include,非常头疼。

Composer按照四种规范来加载文件:

  • psr-4

  • psr-0(这种规范某些部分不是很优雅)

  • classmap(命名空间和文件路径的映射)

  • files

ComposerAutoloaderInit88609474169d8656473fa0223c682a7a这个类是composer为了防止类冲突搞了一个命名ComposerAutoloaderInit+hash,不管咋样,require_once这个类后需要返回的是一个加载器$loader,而这个加载器经过四种规范遍历后,由null被填充为含有各种变量值的ClassLoader对象。

如果仔细观察autoload_classmap.php、autoload_namespaces.php、autoload_psr4.php和autoload_files(这里用了Container包是没有这个文件的,但Laravel整个项目是有的)文件后,这些都按照对应的规范返回要么命名空间与路径的映射,要么完整路径与某个哈希的映射。

从上图中能看出这个composer初始化路径的流程,重点是ClassLoader这个类的loadClass($class)这个方法,是通过spl_autoload_register这个PHP自动加载函数来注册到autoload函数栈中,最后返回一个$loader加载器,而这个加载器是包含一些私有变量的,由于本Container包只包含IlluminateContainer和IlluminateContracts,且都是psr-4规范,则私有变量$prefixLengthsPsr4和$prefixDirsPsr4就包含了命名空间路径映射的数组值,其余私有变量就是空。

最后返回一个加载器$loader,然后需要实例化一个类时,就会根据loadClass($class)来寻找对应的文件,看下文。

Container类的实例化过程

图片描述

$loader这个加载器已经有了,而且它还是塞满了各种私有变量,这些变量值为命名空间路径映射或者路径哈希映射等,当然这里只有命名空间路径映射这种psr-4规范了。也就是说,一句require_once这个autoload.php文件后就拿到了一个饱满的$loader,然后现在开始new一个类Container,那如何找到这个Container.php文件路径的呢?

从第二个序列图就可看出,首先调用ClassLoader中的loadClass()这个函数来找文件路径,传入的$class变量值是“IlluminateContainerContainer”这个字符串,然后又继续调用findFile($class)函数先做classmap查找,然后进入findFileWithExtension($class,'.php')中做psr-4/psr-0查找,其实就是搜寻这些私有变量值,比如这里Container类是psr-4规范,那就去$prefixLengthsPsr4/$prefixDirsPsr4这些psr-4私有变量中查找文件绝对路径,返回一个$file,再include下就等于这个类可以被实例化了。当然,这里Container.php文件绝对路径被找到后,发现它还实现了一个接口ContractContainer,那就再去同样方式找这个文件:psr-4根据命名空间IlluminateContractsContainerContainer去找这个接口对应的绝对路径。

总之,当实例化一个类时,这个$loader就去根据四种规范找该文件的绝对路径,如果这个类还有继承或实现关系,那就递归找。

自定义一个类文件

现在自己写一个类文件,当实例化的时候,然后让composer来自动加载,怎么做?

修改composer.json文件:

{
    "require": {
        "illuminate/container": "^5.2"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    }
}

这里按照psr-4规范来,然后在项目根目录下使用命令:

composer install

发现autoload_psr4.php文件会多一个数组值:

return array(
    'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
    'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),
    'App\\' => array($baseDir . '/app'),
);

然后在项目根目录下新建文件:

// app/Test/Test.php文件
<?php
/**
 * Created by PhpStorm.
 * User: liuxiang
 * Date: 16/5/12
 * Time: 21:52
 */

namespace App\Test;


class Test
{
    public function index()
    {
        echo "This is a custom class which will be autoload by composer\n";
    }
}

在index.php文件中就可以实例化Test类并调用其对象函数了:

require_once __DIR__.'/vendor/autoload.php';

//$container = new Illuminate\Container\Container();
//var_dump($container);

$test = new App\Test\Test();
$test->index();

终端执行输出:
图片描述

通过在Composer中注册下,Composer就可以帮我们找到类文件,就不需要自己各种include,只需开始一句require_once就行,真的很方便。

One More Thing...

配置Xdebug。强烈推荐在自己的IDE中配置Xdebug,作者使用PHPStorm,并配置了Xdebug,这会提高阅读源码的效率。具体操作流程可以谷歌文档,应该很多,Netbeans或者ZendStudio应该也有很多配置文档。如果有配置不成功的,可以在本文留言下问题,作者会尽量解答。

PlantUML插件的安装。本文UML序列图用的是PlantUML这个插件来做的,还比较好用,推荐下。可以在PHPStorm插件库里搜UML就行,然后新建一个文件时会发现多了好几个UML选项,并且还有一个PlantUML窗口:
图片描述
图片描述
关于这个PlantUML有一篇文章还挺好:Create Beautiful UML Diagrams in Minutes from the JetBrains IDE,还有它的官网(就是有各种广告):PlantUML

总结:本文主要聊了下Composer的加载流程,并以Laravel的IlluminateContainer包为例具体说明实例化类时是如何找到其文件的,并讲述如何自定义自己的类并通过Composer来注册和加载。过两天还想结合PHP的字符串和数组这些基础知识新开篇章,到时见。

欢迎关注Laravel-China

RightCapital招聘Laravel DevOps


lx1036
3.1k 声望923 粉丝

为五斗米折腰