個人筆記向,存在有問題的描述請多包涵
Composer 的型別
首先我們先執行指令
$ composer init
$ Package name (<vendor>/<name>) [admin/learncomposer]: zxyy/composerlearn
$ Description []: study composer
$ Author [JunYu <1016673080@qq.com>, n to skip]:
$ Minimum Stability []: stable
$ Package Type (e.g. library, project, metapackage, composer-plugin) []: library
$ License []: mit
Define your dependencies.
Would you like to define your dependencies (require) interactively [yes]? n
Would you like to define your dev dependencies (require-dev) interactively [yes]? n
第一行是初始化composer的指令。第二行是問你所寫的composer專案名是什麼 作者/專案名。第三行當然就是專案的簡介描述。第四行就是作者資訊,直接回車跳過。第五行就是最小相容版本。第六行就是問你你的這個composer專案是作為第三方擴充套件還是專案還是meta包,還是外掛。第七行是授權模式。之後的就是問你是否需要第三方的依賴。我這邊都寫了N。
然後就得到了這麼個檔案
composer.json
{
"name": "zxyy/composerlearn",
"description": "study composer",
"type": "library",
"license": "mit",
"authors": [
{
"name": "JunYu",
"email": "1016673080@qq.com"
}
],
"minimum-stability": "stable",
"require": {}
}
關於這裡的type
我主要說一下這個library和project的區別。前者的話所有的檔案資料都會在vendor裡面目錄結構為vendor/admin/names 後者則在一級目錄下。
composer自動載入
那麼說到composer那首先想到的就是自動載入
自動載入有classmap
,psr-4
,psr-0
這幾個,還有一個file
我們先舉一個classmap的例子
composer.json
{
"name": "zxyy/composerlearn",
"description": "study composer",
"type": "library",
"license": "mit",
"autoload": {
"classmap": [
"Test/ClassMap"
]
},
"authors": [
{
"name": "JunYu",
"email": "1016673080@qq.com"
}
],
"minimum-stability": "stable",
"require": {}
}
然後在目錄下建立一個Test/ClassMap資料夾且有一個Job類
Test/ClassMap/Job.php
<?php
namespace Test\ClassMap;
class Job
{
}
然後我們去終端執行
$ composer dump-autoload
然後我們再去看
vendor/composer/autoload_static.php
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e
{
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Test\\ClassMap\\Job' => __DIR__ . '/../..' . '/Test/ClassMap/Job.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->classMap = ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::$classMap;
}, null, ClassLoader::class);
}
}
我們可以發現在名為ComposerStaticInit的類檔案中出現了一條
'Test\\ClassMap\\Job' => __DIR__ . '/../..' . '/Test/ClassMap/Job.php',
的記錄,這條記錄就是獲取到了Job檔案的絕對路徑。
熟悉Laravel的同學們肯定是對file和psr-4有所瞭解。
在laravel中有一個叫App的名稱空間,那麼我們自己寫要怎麼去寫呢?
composer.json
{
"name": "zxyy/composerlearn",
"description": "study composer",
"type": "library",
"license": "mit",
"autoload": {
"classmap": [
"Test/ClassMap"
],
"psr-4": {
"App\\":"app/"
}
},
"authors": [
{
"name": "JunYu",
"email": "1016673080@qq.com"
}
],
"minimum-stability": "stable",
"require": {}
}
在psr-4裡面,我們前面所寫的是你所取的名稱空間名:
對應的則是實際目錄的位置
所以我們現在就可以在檔案目錄中建立app\Psr4\Job.php
<?php
namespace App\Psr4;
class Job
{
}
然後我們再去執行一下
$ composer dump-autoload
看一下
vendor/composer/autoload_static.php
...
public static $prefixLengthsPsr4 = array (
'A' =>
array (
'App\\' => 4,
),
);
public static $prefixDirsPsr4 = array (
'App\\' =>
array (
0 => __DIR__ . '/../..' . '/app',
),
);
...
我們可以看到prefixlengthsPsr4和prefixDirsPsr4這倆個兄弟
前者是名稱空間的長度,以及首字母開頭
後者是名稱空間所對應的資料夾的絕對路徑
當然也可以做到一個名稱空間對應多個資料夾
composer.json
...
"App\\":["app/","app1/"]
...
然後我們再去執行一下
$ composer dump-autoload
看一下
vendor/composer/autoload_static.php
...
public static $prefixDirsPsr4 = array (
'App\\' =>
array (
0 => __DIR__ . '/../..' . '/app',
1 => __DIR__ . '/../..' . '/app1',
),
);
...
然後我們可以發現這裡多一個app1的絕對路徑
psr-4還支援無名稱空間指定具體寫法如下
"psr-4": {
"App\\":["app/","app1/"],
"":"nullspace/"
}
……
public static $fallbackDirsPsr4 = array (
0 => __DIR__ . '/../..' . '/NullSpace',
);
這個意思是說NullSpace目錄下的所有不帶名稱空間的檔案都通過psr-4載入
那麼psr-0其實在使用上就和psr-4是一樣的在此我就不多贅述。
我們來看最後一個就是files載入
使用laravel的朋友應該知道的,helps助手函式就是通過file載入在全域性的。
composer.json
...
"files": [
"helpers.php",
"app/hello.php"
]
...
然後我們再去執行一下
$ composer dump-autoload
看一下
vendor/composer/autoload_static.php
public static $files = array (
'cf234aa6b2b7e8258c522027a10f3d31' => __DIR__ . '/../..' . '/helpers.php',
'4c89b7b03f917434285aa13e4af37b9f' => __DIR__ . '/../..' . '/app/hello.php',
);
我們就可以發現我們獲取到了他們所處的絕對路徑
Composer的載入過程
首先我們可以看到vendor\autoload.php
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit63ea353d816bb31af54de6d6f84e4d3e::getLoader();
我們可以發現先引入了/composer/autoload_real.php
返回了執行ComposerAutoloaderInit::getLoader()
的結果。
接下來的文字將用...
省略與我講解無關的程式碼內容
我們開啟/composer/autoload_real.php
檔案
class ComposerAutoloaderInit63ea353d816bb31af54de6d6f84e4d3e
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
// 如果你是第一次執行就不會進入這個地方的判斷
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit63ea353d816bb31af54de6d6f84e4d3e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit63ea353d816bb31af54de6d6f84e4d3e', 'loadClassLoader'));
...
...
}
好我們先講解以上的程式碼。
getLoader這個靜態函式,首先判斷,你是不是第一次執行,如果不是的話,就把已有的$loader也就是裝載資訊返回給你。
之後我們可以看到spl_autoload_register
這個函式,函式的第一個傳參是一個陣列[要執行的類名
,要執行的方法
],第二個傳參是是否丟擲錯誤,第三個是新增進autoload佇列隊首。
案例如下:
class A
{
public static function demo()
{
echo '我是'.__ClASS__;
require_once 'dede.php';
}
}
class B
{
public static function demo()
{
echo '我是'.__ClASS__;
}
}
class C
{
public static function demo()
{
echo '我是'.__ClASS__;
}
}
spl_autoload_register(['A','demo'],false,true);
spl_autoload_register(['B','demo'],false,true);
spl_autoload_register(['C','demo'],false,true);
// 當我們例項化一個PHP無法找到的類時,PHP就會去執行autoload列表
// 當然如果執行了以後還是沒有這個類那就會理所當然的報錯。
new dede();
當我們例項化一個PHP無法找到的類時,PHP就會去執行autoload列表。所以上面的案例執行後的結果如下
我是C我是B我是A
因為執行了spl三行以後 在autoload佇列中是 C-B-A
然後new dede的時候發現當前檔案中不存在,那就去執行了autoloader佇列,然後從輸出了我是C我是B我是A 輸出我是A以後require了dede.php dede.php裡面有dede這個類,所以例項化成功了。
還有一點要說明的是,實力化哪個類的時候出現錯誤,去觸發了autoload列表時,我們可以在函式中用變數獲取到類名(可以加上名稱空間)
class C
{
public static function demo($class)
{
echo '觸發的類名是'.$class;
// echo '我是'.__ClASS__;
}
}
spl_autoload_register(['C','demo'],false,true);
// 當我們例項化一個PHP無法找到的類時,PHP就會去執行autoload列表
new \Test\dede();
執行結果
觸發的類名是Test\dede我是C
當然這樣子還是有錯誤資訊的,因為你沒這個類嘛。
寫一個簡單的按需載入demo:
start.php
<?php
class start
{
public static function demo($class)
{
require_once __DIR__.'/'.$class.'.php';
}
public static function getLoader()
{
spl_autoload_register(['start','demo'],true,true);
}
}
start::getLoader();
$test = new momo();
momo.php
<?php
class momo
{
public function __construct()
{
echo '123';
}
}
執行start結果:
123
誒其實最簡單的自動載入也就是這樣子。
好接下來我們繼續去讀composer的程式碼
spl_autoload_register(array('ComposerAutoloaderInit63ea353d816bb31af54de6d6f84e4d3e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit63ea353d816bb31af54de6d6f84e4d3e', 'loadClassLoader'));
如果接下來出現例項化,未找到的類就去執行composerAutoloaderInit::loadClassLoader
這邊$loader
變數在例項化的時候並沒有找到,所以就執行了spl_autoload_register
然後我們看loadClassLoader
public static function loadClassLoader($class)
{
var_dump($class);
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
我們可以去看輸出的結果
"Composer\Autoload\ClassLoader"
如果此時為真,那麼就引入ClassLoader.php
那麼這個時候我們的
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
就可以執行成功了,例項化了類載入器,順便傳入了檔案路徑。
之後便是把註冊器取消註冊spl_autoload_unregister
我們繼續往下看
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
這句程式碼的意思就是,現在使用的PHP版本是否大於5.6 是否使用了HHVM虛擬機器(Facebook寫的可以自己去百度) 和有沒有使用zend加密PHP
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::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);
}
}
我這邊就看第一個分支(我已經不用5.6 也沒有用hhvm和zend)
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::getInitializer($loader));
call_user_func()可以執行回撥,這個就不用我展開解釋了吧?emm 說句不太正經的就是你return了一個function call_user_func幫你把它執行了~
我們開啟這個autoload_static.php
重點看這個getInitializer
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::$prefixDirsPsr4;
$loader->fallbackDirsPsr4 = ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::$fallbackDirsPsr4;
$loader->classMap = ComposerStaticInit63ea353d816bb31af54de6d6f84e4d3e::$classMap;
}, null, ClassLoader::class);
}
我就簡單說一下這個Closure::bind
上案例了:
Class A
{
private static $name = '我是A';
public function getName(){
return self::$name;
}
}
Class B
{
private static $name = '我是B';
public function getName(){
return self::$name;
}
}
$closure = Closure::bind(function (){
echo $this->getName();
},new A(),null);
$closure();
輸出
我是A
程式已結束,退出程式碼為 0
我們可以發現此時輸出的是A
因為我們在第二個形參處丟了個例項化A的物件進去。
你如果要在這個閉包中用到$this關鍵字你就必須要在第二個形參處傳入物件,否則就填寫null 不使用
$closure = Closure::bind(function (){
echo $this->getName();
},null,null);
這樣子就會報錯
Fatal error: Uncaught Error: Using $this when not in object context in
那麼第三個是什麼呢?
$aa = new A();
$closure = Closure::bind(function () use ($aa){
echo $aa::$name;
},null,A::class);
$closure();
輸出
我是A
程式已結束,退出程式碼為 0
$aa = new A();
$closure = Closure::bind(function () use ($aa){
echo $aa::$name;
},null,null);
$closure();
輸出
Fatal error: Uncaught Error: Cannot access private property A::$name
由此我們可以得出第三個其實是newScope 就是一個作用域,我們填入以後我們就可以在這個閉包內使用到$aa 裡面這個物件裡面的私有成員。
好了接下來我們繼續去看getInitializer
其實這個閉包就是將autoload_static.php
裡面所記錄的檔案的所有絕對路徑,賦值給autoload_real.php
裡面的$loader實際上這個loader就是ClassLoader.php
裡面的類。
然後程式碼就執行到
$loader->register(true);
這個register
其實就是classloader.php
中的。
好!接下來我們開啟對應的部分。
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
這裡我們可以看到$this::loadClass()
這麼一回事,我們直接去看一下。
public function loadClass($class)
{
var_dump($class);
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
return null;
}
我們還可以發現接收了一個$class
那麼這個class就是那個需要載入的類了。
我們也可以從findFile
這個函式可以發現一定有一個尋找檔案的步驟。
然後檔案尋找到以後,includeFile
就是引入檔案了呀。
我們新建一個index.php 然後new一個之前使用classmap自動載入的類。
index.php
<?php
require_once "vendor/autoload.php";
new \Test\ClassMap\Job();
輸出的內容為
string(17) "Test\ClassMap\Job"
接下來我們去看findFile
的部分
public function findFile($class)
{
// class map lookup
//$this->classMap 其實就是 autoload_static.php public static $classMap
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
//$this->classMapAuthoritative 布林屬性 如果True 或者 屬於丟失類中就直接不載入
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
// apcu是PHP的一個自帶快取功能,判斷你的檔案路徑是不是在快取裡面。
// 詳情可見https://www.php.net/manual/en/book.apcu.php
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
// 到這裡其實就是psr-4 和 psr-0這些方法的載入了
$file = $this->findFileWithExtension($class, '.php');
// 如果你用了hhvm虛擬機器那就執行下面這個判斷,組成的檔案字尾為.hh
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
// 如果apcu快取裡木得 那就加進去
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;
}
幾乎每句程式碼我都註釋了一下。
當classmap中不存在之後,composer就會為我們去psr-4或0也就是你設定的別的自動載入模式裡面找。
$file = $this->findFileWithExtension($class, '.php');
接下來我們來看一下這個findFilewithExtension
此時我的index.php
為
<?php
require_once "vendor/autoload.php";
new \App\Psr4\Job();
這邊只讀psr-4
因為psr-0
其實流程上大家自己看也差不多的。
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
}
$logicalPathPsr4
其實就是更具psr-4
規則拼裝好的一個檔案路徑
接下來我們去看autoload_static.php
中生成的資料 再結合這個部分的程式碼我相信就可以理解了
public static $prefixLengthsPsr4 = array (
'A' =>
array (
'App\\' => 4,
),
);
public static $prefixDirsPsr4 = array (
'App\\' =>
array (
0 => __DIR__ . '/../..' . '/app',
1 => __DIR__ . '/../..' . '/app1',
),
);
我來逐句解釋其含義
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
首先將斜線改為相對系統所對應的分割線也就是DIRECTORY_SEPARATOR
的作用
我此時的$class
為App\Psr4\Job
然後取出首字母,也就是我這邊的A
if (isset($this->prefixLengthsPsr4[$first])){
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
var_dump($lastPos);// 第一次下標為8
$subPath = substr($subPath, 0, $lastPos);
var_dump(substr($subPath, 0, $lastPos));
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
var_dump($pathEnd);
var_dump($logicalPathPsr4);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
判斷是否存在$prefixLengthsPsr4[A]
這個元素
存在的話將其設定為$subPath
並且計算出最後一個反斜線出現的下標—–藉助strrpos
函式
開始迴圈while第一次得出下標為8,藉助substr提取出檔案目錄為App\Psr4
然後加上反斜線成為App\Psr4\
去prefixDirsPsr4
對比結果發現不存在,那麼開始第二次迴圈。$search
成為了App\
成功進入判斷$pathEnd='\Psr4\Job.php'
進入foreach
去組合絕對路徑去判斷檔案是否存在,存在就返回路徑。
最後執行到includeFile()
而includeFile?也就是如下:
function includeFile($file)
{
include $file;
}
一般的載入流程大概就是這樣子了。
本作品採用《CC 協議》,轉載必須註明作者和本文連結