php自动加载机制简书,Composer 使用和自动加载原理

基本用法

概念

Composer 不是一个包管理器。是的,它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目的某个目录中(例如 vendor)进行安装。默认情况下它不会在全局安装任何东西。因此,这仅仅是一个依赖管理。

Composer 主要解决以下问题

你有一个项目依赖于若干个库。

其中一些库依赖于其他库。

你声明你所依赖的东西。

Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。

使用

安装Composer

使用Composer 只需要创建一个 composer.json 文件,其中定义了项目的基本信息和依赖

{

"require": {

"monolog/monolog": "1.2.*"

}

}

定义好依赖关系后,执行 composer install 即可将依赖的库或项目下载到你项目的vendor文件夹下

也可以直接使用 composer require 命令直接安装依赖。

除了 composer.json 文件,composer 还会生成 composer.lock - 锁文件,锁文件记录着详细的依赖信息,用于多人协同工作时依赖库的同步。

命令行

composer init 初始化

composer install 安装

composer update 更新

composer require 声明依赖

composer global 全局执行

composer search 搜索包

composer show 展示

composer depends 依赖性检测

(库)资源包

composer.json 结构

包名 name

描述 description

版本 version

安装类型 type

关键字 keywords

项目主页 homepage

版本发布时间 time

许可协议 license

作者 author

Package links

Composer 的自动加载

PHP自动加载

在一般的PHP程序开发中,我们通常使用 require 和 include 引入文件。

这在一般小规模的开发中没什么问题,但是开发大型项目需要多人合作时就会带来一些隐性的问题。如果一个PHP文件需要引入大量的其他的文件时,需要写大量的 require 或 include 语句,这样可能会遗漏或引入没用的文件,从而影响性能。如果各个文件之间有更加复杂的引入关系,就会很容易造成混乱。

__autoload 函数

PHP5 为了解决这个问题,引入了 类的自动加载机制 autload机制可以在类使用的时候才引入对应的包含类的文件,而不是一开始就加载进来,这种加载方式也称为 Lazy loading(惰性加载)

function __autoload() {

require_once ($classname . ".class.php");

}

__autoload 函数存在的问题

如果一个系统中需要引入大量其他的类库,这些类库可能由不同的开发人员编写的,其类名与实际的磁盘文件的映射规则不尽相同,如果这些类库要全部实现自动加载,则需要在 __autoload 函数中实现所有的映射规则。这样 __autoload 函数的实现会非常复杂和臃肿,甚至可能无法实现。

造成这样结果的主要原因是 __autoload() 是全局函数只能定义一次,不够灵活。如何解决这个问题呢?答案是使用 __autoload 调用堆栈 , 不同的映射关系写到不同的 autoload 函数中,然后统一注册管理,所以PHP5中引入了 Spl Autoload

SPL Autoload

SPL 是 Standard PHP Library(标准PHP库)的缩写。

使用 spl_autoload_register 函数 注册自动加载函数

function my_autoloader($class) {

include "classes/" . $class . ".class.php";

}

spl_autoload_register('my_autoloader');

spl_autoload_register() 就是我们上面所说的 __autoload 调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当PHP找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。

PSR 规范

PSR 是PHP标准组织推出的一套PHP开发标准,其中有7套PSR规范已通过表决并推出使用,分别是:

PSR-0 自动加载标准 (已废弃,一些旧的第三方库还有在使用)

PSR-1 基础编码标准

PSR-2 编码风格导向

PSR-3 日志接口

PSR-4 自动加载增强版

PSR-6 缓存接口规范

PSR-7 HTTP 消息接口规范

PSR4 标准

一个完整的类名需具有以下结构

///

Composer 自动加载过程

执行 composer require 时发生了什么

composer 会找到符合 PR4 规范的第三方库的源

将其加载到 vendor 目录下

初始化顶级域名的映射并写入到指定的文件里

写好一个 autoload 函数,并且注册到spl_autoload_register() 里

Composer 实现自动加载的文件

autoload_real.php 自动加载功能的引导类

composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册

ClassLoader.php composer 加载类

composer 自动加载功能的核心类

autoload_static.php 顶级命名空间初始化类

用于给核心类初始化顶级域名空间

autoload_classmap.php 自动加载的最简单形式

有完整的命名空间和文件目录的映射

autoload_files.php 用于加载全局函数的文件

存放各个全局函数所在的文件路径名

autoload_namespaces.php 符合PSR0标准的自动加载文件

存放着顶级命名空间与文件的映射

autoload_psr4.php 符合PSR4标准的自动加载文件

存放着顶级命名空间与文件的映射

Composer 源码分析

启动

很多框架都会在初始化的时候通过 composer 实现自动加载,以下代码以 laravel 为例

define('LARAVEL_START', microtime(true));

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

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit56e0da52b922ff4669d17341a618d14e::getLoader();

引入composer 的自动加载文件,调用 getLoader 函数,Compoer 正式开始

autoload_real 引导类

在 autoload.php 文件中我们可以看出,程序主要调用了引导类的静态方法 getLoader() 来实现自动加载的注册。

接下来我们来详细的看一下这个函数

单例

一开始先实现了一个简单的单例模式,自动加载类只能有一个

if (null !== self::$loader) {

return self::$loader;

}

构造 ClassLoader 核心类

将 loadClassLoader 方法注册自动加载,并实例化一个自动加载实现的核心类 ClassLoader

spl_autoload_register(array('ComposerAutoloaderInit46b6d6d7d0d5071f29f6ebcb25245e20', 'loadClassLoader'), true, true);

self::$loader = $loader = new \Composer\Autoload\ClassLoader();

spl_autoload_unregister(array('ComposerAutoloaderInit46b6d6d7d0d5071f29f6ebcb25245e20', 'loadClassLoader'));

loadClassLoader 方法代码:

public static function loadClassLoader($class)

{

if ('Composer\Autoload\ClassLoader' === $class) {

require __DIR__ . '/ClassLoader.php';

}

}

初始化核心类对象

$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

if ($useStaticLoader) {

require_once __DIR__ . '/autoload_static.php';

call_user_func(\Composer\Autoload\ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::getInitializer($loader));

} else {

$map = require __DIR__ . '/autoload_namespaces.php';

foreach ($map as $namespace => $path) {

$loader->set($namespace, $path);

}

$map = require __DIR__ . '/autoload_psr4.php';

foreach ($map as $namespace => $path) {

$loader->setPsr4($namespace, $path);

}

$classMap = require __DIR__ . '/autoload_classmap.php';

if ($classMap) {

$loader->addClassMap($classMap);

}

}

这个部分主要是对自动加载类的初始化,给自动加载类定义各顶级命名空间的映射。

加载顶级命令空间映射主要有两种方式

通过 autoload_static.php 静态初始化

调用核心类接口初始化

autoload_static 静态初始化 PHP 版本必须 >= 5.6,而且不支持 HHVM 虚拟机使用。该方法主要通过 getInitializer方法实现初始化,这个方法的实现也非常简单,最终会将自己类中的顶级命名空间的映射给 ClassLoader 自动加载类。

public static function getInitializer(ClassLoader $loader)

{

return \Closure::bind(function () use ($loader) {

$loader->prefixLengthsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixLengthsPsr4;

$loader->prefixDirsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixDirsPsr4;

}, null, ClassLoader::class);

}

至此自动加载类就初始化完成了。

运行第三方库

​ 终于到了最核心的部分了,composer 实现自动加载的秘密,如何把获取到的命名空间转换为对应的文件目录。

​ ClassLoder 的 register() 函数将 loadClass() 函数注册到PHP的 SPL 函数堆栈中,每当PHP遇到不认识的类命名空间时就会自动调用堆栈中的每个函数直到类加载成功或函数调用完为止。所以loadClass() 函数就是类自动加载的关键了。

loadClass() 函数

public function loadClass($class)

{

if ($file = $this->findFile($class)) {

includeFile($file);

return true;

}

}

public function findFile($class)

{

// class map lookup

if (isset($this->classMap[$class])) {

return $this->classMap[$class];

}

if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {

return false;

}

if (null !== $this->apcuPrefix) {

$file = apcu_fetch($this->apcuPrefix.$class, $hit);

if ($hit) {

return $file;

}

}

$file = $this->findFileWithExtension($class, '.php');

// Search for Hack files if we are running on HHVM

if (false === $file && defined('HHVM_VERSION')) {

$file = $this->findFileWithExtension($class, '.hh');

}

if (null !== $this->apcuPrefix) {

apcu_add($this->apcuPrefix.$class, $file);

}

if (false === $file) {

// Remember that this class does not exist.

$this->missingClasses[$class] = true;

}

return $file;

}

从代码中可以看到 loadClass() 函数主要调用 findFile 方法,findFile() 方法主要找到每个类对应的文件所在的位置。

寻找对应的目录文件主要分为两个部分 classMap 和 findFileWithExtension 。classMap 实现较为简单,就是 直接看命名空间是否在映射数组中即可。findFileWithExtension 的实现较为复杂,他需要根据 PSR0 和 PSR4 标准找到对应的文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值