基本用法
概念
Composer 不是一個(gè)包管理器。是的,它涉及 "packages" 和 "libraries",但它在每個(gè)項(xiàng)目的基礎(chǔ)上進(jìn)行管理,在你項(xiàng)目的某個(gè)目錄中(例如
vendor)進(jìn)行安裝。默認(rèn)情況下它不會在全局安裝任何東西。因此,這僅僅是一個(gè)依賴管理。
Composer 主要解決以下問題
你有一個(gè)項(xiàng)目依賴于若干個(gè)庫。
其中一些庫依賴于其他庫。
你聲明你所依賴的東西。
Composer 會找出哪個(gè)版本的包需要安裝,并安裝它們(將它們下載到你的項(xiàng)目中)。
使用
安裝Composer
使用Composer 只需要?jiǎng)?chuàng)建一個(gè) composer.json 文件,其中定義了項(xiàng)目的基本信息和依賴
{
"require": {
"monolog/monolog": "1.2.*"
}
}
定義好依賴關(guān)系后,執(zhí)行 composer install 即可將依賴的庫或項(xiàng)目下載到你項(xiàng)目的vendor文件夾下
也可以直接使用 composer require 命令直接安裝依賴。
除了 composer.json 文件,composer 還會生成 composer.lock - 鎖文件,鎖文件記錄著詳細(xì)的依賴信息,用于多人協(xié)同工作時(shí)依賴庫的同步。
命令行
-
composer init初始化
composer install安裝composer update更新composer require聲明依賴composer global全局執(zhí)行composer search搜索包composer show展示composer depends依賴性檢測
(庫)資源包
composer.json 結(jié)構(gòu)
包名 name
描述 description
版本 version
安裝類型 type
關(guān)鍵字 keywords
項(xiàng)目主頁 homepage
版本發(fā)布時(shí)間 time
許可協(xié)議 license
作者 author
Package links
Composer 的自動(dòng)加載
PHP自動(dòng)加載
在一般的PHP程序開發(fā)中,我們通常使用 require 和 include 引入文件。
這在一般小規(guī)模的開發(fā)中沒什么問題,但是開發(fā)大型項(xiàng)目需要多人合作時(shí)就會帶來一些隱性的問題。如果一個(gè)PHP文件需要引入大量的其他的文件時(shí),需要寫大量的 require 或 include 語句,這樣可能會遺漏或引入沒用的文件,從而影響性能。如果各個(gè)文件之間有更加復(fù)雜的引入關(guān)系,就會很容易造成混亂。
__autoload 函數(shù)
PHP5 為了解決這個(gè)問題,引入了 類的自動(dòng)加載機(jī)制 autload機(jī)制可以在類使用的時(shí)候才引入對應(yīng)的包含類的文件,而不是一開始就加載進(jìn)來,這種加載方式也稱為 Lazy loading(惰性加載)
function __autoload() {
require_once ($classname . ".class.php");
}
__autoload 函數(shù)存在的問題
如果一個(gè)系統(tǒng)中需要引入大量其他的類庫,這些類庫可能由不同的開發(fā)人員編寫的,其類名與實(shí)際的磁盤文件的映射規(guī)則不盡相同,如果這些類庫要全部實(shí)現(xiàn)自動(dòng)加載,則需要在 __autoload 函數(shù)中實(shí)現(xiàn)所有的映射規(guī)則。這樣 __autoload 函數(shù)的實(shí)現(xiàn)會非常復(fù)雜和臃腫,甚至可能無法實(shí)現(xiàn)。
造成這樣結(jié)果的主要原因是 __autoload() 是全局函數(shù)只能定義一次,不夠靈活。如何解決這個(gè)問題呢?答案是使用 __autoload 調(diào)用堆棧 , 不同的映射關(guān)系寫到不同的 autoload 函數(shù)中,然后統(tǒng)一注冊管理,所以PHP5中引入了 Spl Autoload
SPL Autoload
SPL 是 Standard PHP Library(標(biāo)準(zhǔn)PHP庫)的縮寫。
使用 spl_autoload_register 函數(shù) 注冊自動(dòng)加載函數(shù)
function my_autoloader($class) {
include "classes/" . $class . ".class.php";
}
spl_autoload_register('my_autoloader');
spl_autoload_register() 就是我們上面所說的 __autoload 調(diào)用堆棧,我們可以向這個(gè)函數(shù)注冊多個(gè)我們自己的 autoload() 函數(shù),當(dāng)PHP找不到類名時(shí),PHP就會調(diào)用這個(gè)堆棧,然后去調(diào)用自定義的 autoload() 函數(shù),實(shí)現(xiàn)自動(dòng)加載功能。如果我們不向這個(gè)函數(shù)輸入任何參數(shù),那么就會默認(rèn)注冊 spl_autoload() 函數(shù)。
PSR 規(guī)范
PSR 是PHP標(biāo)準(zhǔn)組織推出的一套PHP開發(fā)標(biāo)準(zhǔn),其中有7套PSR規(guī)范已通過表決并推出使用,分別是:
PSR-0 自動(dòng)加載標(biāo)準(zhǔn) (已廢棄,一些舊的第三方庫還有在使用)
PSR-1 基礎(chǔ)編碼標(biāo)準(zhǔn)
PSR-2 編碼風(fēng)格導(dǎo)向
PSR-3 日志接口
PSR-4 自動(dòng)加載增強(qiáng)版
PSR-6 緩存接口規(guī)范
PSR-7 HTTP 消息接口規(guī)范
PSR4 標(biāo)準(zhǔn)
一個(gè)完整的類名需具有以下結(jié)構(gòu)
/<命名空間>/<字命名空間>/<類名>
Composer 自動(dòng)加載過程
執(zhí)行 composer require 時(shí)發(fā)生了什么
- composer 會找到符合 PR4 規(guī)范的第三方庫的源
- 將其加載到 vendor 目錄下
- 初始化頂級域名的映射并寫入到指定的文件里
- 寫好一個(gè) autoload 函數(shù),并且注冊到spl_autoload_register() 里
Composer 實(shí)現(xiàn)自動(dòng)加載的文件
-
autoload_real.php 自動(dòng)加載功能的引導(dǎo)類
- composer 加載類的初始化(頂級命名空間與文件路徑映射初始化)和注冊
-
ClassLoader.php composer 加載類
- composer 自動(dòng)加載功能的核心類
-
autoload_static.php 頂級命名空間初始化類
- 用于給核心類初始化頂級域名空間
-
autoload_classmap.php 自動(dòng)加載的最簡單形式
- 有完整的命名空間和文件目錄的映射
-
autoload_files.php 用于加載全局函數(shù)的文件
- 存放各個(gè)全局函數(shù)所在的文件路徑名
-
autoload_namespaces.php 符合PSR0標(biāo)準(zhǔn)的自動(dòng)加載文件
- 存放著頂級命名空間與文件的映射
-
autoload_psr4.php 符合PSR4標(biāo)準(zhǔn)的自動(dòng)加載文件
- 存放著頂級命名空間與文件的映射
Composer 源碼分析
啟動(dòng)
很多框架都會在初始化的時(shí)候通過 composer 實(shí)現(xiàn)自動(dòng)加載,以下代碼以 laravel 為例
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit56e0da52b922ff4669d17341a618d14e::getLoader();
引入composer 的自動(dòng)加載文件,調(diào)用 getLoader 函數(shù),Compoer 正式開始
autoload_real 引導(dǎo)類
在 autoload.php 文件中我們可以看出,程序主要調(diào)用了引導(dǎo)類的靜態(tài)方法 getLoader() 來實(shí)現(xiàn)自動(dòng)加載的注冊。
接下來我們來詳細(xì)的看一下這個(gè)函數(shù)
-
單例
一開始先實(shí)現(xiàn)了一個(gè)簡單的單例模式,自動(dòng)加載類只能有一個(gè)
if (null !== self::$loader) { return self::$loader; }
-
構(gòu)造 ClassLoader 核心類
將
loadClassLoader方法注冊自動(dòng)加載,并實(shí)例化一個(gè)自動(dòng)加載實(shí)現(xiàn)的核心類ClassLoaderspl_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); } }這個(gè)部分主要是對自動(dòng)加載類的初始化,給自動(dòng)加載類定義各頂級命名空間的映射。
加載頂級命令空間映射主要有兩種方式
- 通過 autoload_static.php 靜態(tài)初始化
- 調(diào)用核心類接口初始化
autoload_static靜態(tài)初始化 PHP 版本必須 >= 5.6,而且不支持 HHVM 虛擬機(jī)使用。該方法主要通過 getInitializer方法實(shí)現(xiàn)初始化,這個(gè)方法的實(shí)現(xiàn)也非常簡單,最終會將自己類中的頂級命名空間的映射給ClassLoader自動(dòng)加載類。public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixDirsPsr4; }, null, ClassLoader::class); }
至此自動(dòng)加載類就初始化完成了。
運(yùn)行第三方庫
? 終于到了最核心的部分了,composer 實(shí)現(xiàn)自動(dòng)加載的秘密,如何把獲取到的命名空間轉(zhuǎn)換為對應(yīng)的文件目錄。
? ClassLoder 的 register() 函數(shù)將 loadClass() 函數(shù)注冊到PHP的 SPL 函數(shù)堆棧中,每當(dāng)PHP遇到不認(rèn)識的類命名空間時(shí)就會自動(dòng)調(diào)用堆棧中的每個(gè)函數(shù)直到類加載成功或函數(shù)調(diào)用完為止。所以loadClass() 函數(shù)就是類自動(dòng)加載的關(guān)鍵了。
loadClass() 函數(shù)
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() 函數(shù)主要調(diào)用 findFile 方法,findFile() 方法主要找到每個(gè)類對應(yīng)的文件所在的位置。
尋找對應(yīng)的目錄文件主要分為兩個(gè)部分 classMap 和 findFileWithExtension 。classMap 實(shí)現(xiàn)較為簡單,就是 直接看命名空間是否在映射數(shù)組中即可。findFileWithExtension 的實(shí)現(xiàn)較為復(fù)雜,他需要根據(jù) PSR0 和 PSR4 標(biāo)準(zhǔn)找到對應(yīng)的文件。