composer 的自动载入 autoload 可以很方便的帮我们快速的构建一套自己的框架结构。
而自动载入本身其实是利用命名空间进行对应规则或标准的路径映射,从而找到我们所需的类文件,读取载入都当前运行时。利用命名空间的自动载入都是懒加载形式的,并不会让程序变得臃肿,但懒加载在一定程度上也影响了程序的性能,要在程序规模和运行效率上做一个折中的选择。
四种模式
psr-0 标准 autoload_namespaces
懒加载,将目标目录作为基目录再进行命名空间和路径的映射后继续向后加载
// php 的 psr-0 规范的自动载入,是将目标目录作为命名空间的基目录再进行路径映射载入类文件
"psr-0": {
"Psr0\\Lib\\": "psr0/lib/src/"
}
这里还有一点是需要注意的,下划线 _ 对 psr-0 是有特殊意义的。psr-0 的加载器会将类名中的 _ 解析成目录分隔符。
即 Foo_Bar_Test 类会去加载 Foo/Bar/Test.php 文件。
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
也可以看下composer 的源码,可以看到是将 _ 替换成了 DIRECTORY_SEPARATOR(目录分隔符)。
在 php 5.2 之前,是没有 namespace 关键字去友好的管理类包的,是 php 5.3 以后才有新特性:命名空间,匿名函数,静态延迟绑定。
然而有很多普遍使用的第三方类库还是使用的下划线的方式去命名类,比如 PHPExcel,这里我们可以看下其官方的 composer.json 文件里自动载入的配置:
"autoload": {
"psr-0": {
"PHPExcel": "Classes/"
}
}
则 PHPExcel 顶级类下的检索都会以 Classes 文件夹为起始目录,比如我想使用 PHPExcel_Cell_DataType 这个类。我就可以判断出这个类是在 Classes/PHPExcel/Cell/DataType.php 文件中。
// 则会加载 Classes/PHPExcel/Cell/DataType.php
use PHPExcel_Cell_DataType;
psr-4 标准 autoload_psr4
懒加载,将目标目录直接映射为命名空间对应的目录继续向后加载
// php 的 psr-4 规范的自动载入,是将目标目录直接影射为命名空间的
"psr-4": {
"Psr4\\Lib\\": "psr4/lib/src/",
"App\\Controllers\\": "app/controllers/",
"App\\Models\\": "app/models/"
}
classmap 模式 autoload_classmap
懒加载,扫描目录下的所有类文件,支持递归扫描, 生成对应的类名=>路径的映射,当载入需要的类时直接取出路径,速度最快
// classmap 扫描目录下的所有类文件 生成对应的类名=>路径的映射
"classmap": [
"classmap/lib/src/"
]
files 模式
自动载入的文件,主要用来载入一些没办法懒加载的公共函数
// 扫描目录下的所有文件生成 hash => 路径的映射 运行时实时加载
// 主要用来载入工具函数
"files": [
"ext/common/functions.php",
"ext/system/functions.php"
]
框架结构
这里我要尽可能把四种自动载入模式的特征举例出来,所以框架结构并不很合理,不要在意
./
├── app
│ ├── controllers
│ └── models
├── classmap
│ └── lib
│ └── src
├── composer.json
├── ext
│ ├── common
│ │ └── functions
│ └── system
│ └── functions
├── psr0
│ └── lib
│ └── src
└── psr4
└── lib
└── src
autoload 规则
编辑 composer.json 文件
{
"autoload": {
"psr-0": {
"Psr0\\Lib\\": "psr0/lib/src/"
},
"psr-4": {
"Psr4\\Lib\\": "psr4/lib/src/",
"App\\Controllers\\": "app/controllers/",
"App\\Models\\": "app/models/"
},
"classmap": [
"classmap/lib/src/"
],
"files": [
"ext/common/functions.php",
"ext/system/functions.php"
]
}
}
刷新 autoload 规则
composer dump-autoload
当你添加了新的 psr-0/psr-4 的规则,或者在 classmap/files 规则相应的目录下新增了文件时,都需要执行 dump-autoload 来刷新系统的自动载入。
我们可以看一下生成的 psr-0 和 psr-4 标准的自动载入
psr-0:vendor/composer/autoload_namespaces.php
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Psr0\\Lib\\' => array($baseDir . '/psr0/lib/src'),
);
psr-4:vendor/composer/autoload_psr4.php
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Psr4\\Lib\\' => array($baseDir . '/psr4/lib/src'),
'App\\Models\\' => array($baseDir . '/app/models'),
'App\\Controllers\\' => array($baseDir . '/app/controllers'),
);
其实 psr-0/psr-4 的自动载入都将命名空间映射为相应的目录。只不过 psr-0 是映射到目标地址作为基目录再解析命名空间,而 psr-4 是直接映射。
实现一个 psr-0 规范的类文件
psr0/lib/src/Psr0/Lib/Psr0LibClass.php
<?php
// psr-0: "Psr0\\Lib\\": "psr0/lib/src/"
namespace Psr0\Lib;
class Psr0LibClass
{
public static function index()
{
echo __CLASS__ . "@" . __FILE__ . PHP_EOL;
}
}
实现一个 psr-4 规范的类文件
psr4/lib/src/Psr4LibClass.php
<?php
// psr-4: "Psr4\\Lib\\": "psr4/lib/src/"
namespace Psr4\Lib;
class Psr4LibClass
{
public static function index()
{
echo __CLASS__ . "@" . __FILE__ . PHP_EOL;
}
}
classmap 就没什么规范可言了,绝对的一对一映射而已
因为它本身就没遵循命名空间和路径映射的半点规范...随便写一个好了,他的模式其实就是扫描指定目录下的所有文件,把类采集出来,做一个映射表,这个类在这个文件里,完了。
classmap/lib/src/ClassMapAutoload.php
<?php
// classmap 放在 classmap/lib/src 下就好,autoload 会自动扫描记录它
namespace ClassMap\Lib;
class ClassMapLibClass
{
public static function index()
{
echo __CLASS__ . "@" . __FILE__ . PHP_EOL;
}
}
files 是在运行时就直接载入的一些函数文件(非类文件)
ext/common/functions.php
ext/system/functions.php
<?php
//ext/common/functions.php
function hello_common()
{
echo __FUNCTION__ . "@" . __FILE__ . PHP_EOL;
}
<?php
//ext/system/functions.php
function hello_system()
{
echo __FUNCTION__ . "@" . __FILE__ . PHP_EOL;
}
运行一下 compser dump-autoload 来刷新自动载入
实例
<?php
//为了方便 不标准 标准的引入文件和执行应该分开
require_once(__DIR__ . "/vendor/autoload.php");
use Psr0\Lib\Psr0LibClass;
use Psr4\Lib\Psr4LibClass;
use ClassMap\Lib\ClassMapLibClass;
use App\Controllers\IndexController;
use App\Models\User;
//psr-0
Psr0LibClass::index();
//psr-4
Psr4LibClass::index();
//classmap
ClassMapLibClass::index();
// psr-4 规范的自动载入
// 你应该知道去哪写代码
IndexController::index();
User::index();
//files 自动加载
hello_common();
hello_system();
运行结果
#我把全路径隐藏了 就当作在当前工作目录下好了
Psr0\Lib\Psr0LibClass@psr0/lib/src/Psr0/Lib/Psr0LibClass.php
Psr4\Lib\Psr4LibClass@psr4/lib/src/Psr4LibClass.php
ClassMap\Lib\ClassMapLibClass@classmap/lib/src/ClassMapAutoload.php
App\Controllers\IndexController@app/controllers/IndexController.php
App\Models\User@app/models/User.php
hello_common@ext/common/functions.php
hello_system@ext/system/functions.php
标注:
psr-0 规范同时兼容了 PHP5.2 版本前的为命名空间风格
比如有一个类名为 Foo_Bar_HelloWorld 的类文件存放在
app/Foo/Bar/HelloWorld.php 文件中
"psr-0": {
"Foo\\": "app/"
}
便可以将 Foo_Bar_HelloWorld 加载进来,原理其实就是把 _ 替换成路径分隔符 Foo/Bar/HelloWorld 然后进行加载
Foo 命名空间会被基址到 app 目录下,继续按解析好的路径进行加载
new Foo_Bar_HelloWorld();