剛開始接觸laravel,一天時間走馬觀花的看了一些官方文檔之後便開始了laravel的學習。這裡談到的都是最基礎的東西,各路大神,可直接略過。
composer概述
一開始,最吸引我的當屬 Composer 了,因為之前從沒用過 Composer 。
Composer 是PHP中用來管理依賴關系的工具,你只需在自己的項目中聲明所依賴的外部工具庫,Composer就會幫你安裝這些依賴的庫文件。運行 Composer 需要 PHP 5.3.2+ 以上版本。
使用composer
第一步,聲明依賴關系。比方說,你正在創建的一個項目需要一個庫來做日志記錄。你決定使用 monolog。為了將它添加到你的項目中,你所需要做的就是創建一個 composer.json 文件,其中描述了項目的依賴關系。
{
"require": {
"monolog/monolog": "1.2.*"
}
}
第二步,使用composer。在項目根目錄,執行安裝命令,執行完畢後,monolog就會被下載到vendor/monolog/monolog 目錄。
$ php composer.phar install
第三步,類的自動加載。除了庫的下載,Composer 還准備了一個自動加載文件,它可以加載 Composer 下載的庫中所有的類文件。使用它,你只需要將下面這行代碼添加到你項目的引導文件中:
require 'vendor/autoload.php';
這使得你可以很容易的使用第三方代碼。例如:如果你的項目依賴 monolog,你就可以像這樣開始使用這個類庫,並且他們將被自動加載。
$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->addWarning('Foo');
Composer 自動加載探秘
在現實世界中使用工具時,如果理解了工具的工作原理,使用起來就會更加有底氣。對於一個第一次接觸laravel,且是第一次接觸 composer 的新手來說,如果理解Composer 是如何工作的,使用起來將會更加自如。
我的理解是,composer 根據聲明的依賴關系,從相關庫的 源 下載代碼文件,並根據依賴關系在 Composer 目錄下生成供類自動加載的 PHP 腳本,使用的時候,項目開始處引入 “/vendor/autoload.php” 文件,就可以直接實例化這些第三方類庫中的類了。那麼,Composer 是如何實現類的自動加載的呢?接下來,我們從 laravel 的入口文件開始順籐摸瓜往裡跟進,來一睹 Composer 自動加載的奧妙。
1.代碼清單 laravel/public/index.php
#laravel/public/index.php
require __DIR__.'/../bootstrap/autoload.php'; $app = require_once __DIR__.'/../bootstrap/start.php'; $app->run();
第一行先是引入了 laravel/bootstrap/autoload.php,不做解釋,打開該文件。
2.代碼清單 laravel/bootstrap/autoload.php
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
if (file_exists($compiled = __DIR__.'/compiled.php'))
{
require $compiled;
}
Patchwork\Utf8\Bootup::initMbstring();
第一行定義了程序開始執行的時間點。緊接著第二行,引入了 laravel/vendor/autoload.php。
第七行,前面說過,引入Composer的autoload.php之後就可以直接使用第三方類庫中的類了,這裡就是直接使用的 Bootup 類。下面來看看 /vendor/autoload.php 到底做了什麼。
3.代碼清單 laravel/vendor/autoload.php
1 // autoload.php @generated by Composer 2 3 require_once __DIR__ . '/composer' . '/autoload_real.php'; 4 5 return ComposerAutoloaderInit9b2a1b1cf01c9a870ab98748dc5f1256::getLoader();
到這裡,馬上就進入自動加在的大門了。
這個文件很簡單,第5行的函數名是不是看的一頭霧水?別被嚇到了,他就是個類名而已。這個類是在第3行引入的文件 laravel/vendor/composer/autoload_real.php 裡頭聲明的,接下來打開該文件看 getLoader();
4.代碼清單laravel/vendor/composer/autoload_real.php
1 <?php
2
3 // autoload_real.php @generated by Composer
4
5 class ComposerAutoloaderInit9b2a1b1cf01c9a870ab98748dc5f1256
6 {
7 private static $loader;
8
9 public static function loadClassLoader($class)
10 {
11 if ('Composer\Autoload\ClassLoader' === $class) {
12 require __DIR__ . '/ClassLoader.php';
13 }
14 }
15
16
17 public static function getLoader()
18 {
19 if (null !== self::$loader) {
20 return self::$loader;
21 }
22
23 spl_autoload_register(array('ComposerAutoloaderInit9b2a1b1cf01c9a870ab98748dc5f1256', 'loadClassLoader'), true, true);
24 self::$loader = $loader = new \Composer\Autoload\ClassLoader();
25 spl_autoload_unregister(array('ComposerAutoloaderInit9b2a1b1cf01c9a870ab98748dc5f1256', 'loadClassLoader'));
26
27 $vendorDir = dirname(__DIR__);
28 $baseDir = dirname($vendorDir);
29
30 $includePaths = require __DIR__ . '/include_paths.php';
31
32 array_push($includePaths, get_include_path());
33 set_include_path(join(PATH_SEPARATOR, $includePaths));
34
35
36 $map = require __DIR__ . '/autoload_namespaces.php';
37 foreach ($map as $namespace => $path) {
38 $loader->set($namespace, $path);
39 }
40
41 $map = require __DIR__ . '/autoload_psr4.php';
42 foreach ($map as $namespace => $path) {
43 $loader->setPsr4($namespace, $path);
44 }
45
46 $classMap = require __DIR__ . '/autoload_classmap.php';
47 if ($classMap) {
48 $loader->addClassMap($classMap);
49 }
50
51
52 $loader->register(true);
53
54 $includeFiles = require __DIR__ . '/autoload_files.php';
55 foreach ($includeFiles as $file) {
56 composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file);
57 }
58
59 return $loader;
60 }
61 }
62
63 function composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file)及 $loader->addClassMap()
64 {
65 require $file;
66 }
第17行,getLoader()中先是判斷當前類中的 $loader 值,如果不是 null 就返回,這個可以略過。接著實例化了 ClassLoader 類給 $loader ,laravel/vendor/composer/ClassLoader.php
這裡引入了幾個文件,這些文件是由composer自動生成的,當依賴關系發生改變時不需要修改這些腳本,運行composer重新生成即可。
laravel/vendor/composer/autoloade_namespace.php
laravel/vendor/composer/autoloade_prs4.php
laravel/vendor/composer/autoloade_classmap.php
laravel/vendor/composer/autoloade_files.php
在設置完一堆的 path 信息後,執行了$loader->set()和 $loader->setPsr4()及$loader->addClassMap(),然後 進行了$loader->register(true);現在我們一個個來看。
5.代碼清單laravel/vendor/composer/ClassLoader.php

$loader->set($namespace, $path);Psr0標准
設置命名空間對應的路徑,以便於隨後自動加載相關類文件。
$loader->setPsr4($namespace, $path);Psr4標准
設置命名空間對應的路徑,以便於隨後自動加載相關類文件。
$loader->addClassMap($classMap);
設置類文件路徑與類名的對應關系,以便於隨後自動加載相關類文件。
$loader->register(true);
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
這裡設置了 欲注冊的自動裝載函數 $this->loadClass(),關於 spl_autoload_register 和 spl_autoload_unregister 的更多信息隨後會有專門的解釋。現在打開loadClass()的定義
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
這裡有個 findFile() 函數,如果相關類的聲明所在文件的路徑找到了,就包含並運行該文件,然後返回 true 。接著打開findFile()的定義
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
先是判斷類名是否以'\'開始,如果是的話,清除開頭的'\'
接著,檢查當前類的名字是否在 類名與聲明當前類的文件的路徑的關系數組 中,如果存在,直接返回相關鍵值(類文件路徑信息)
如果上一步沒有返回路徑信息,執行 findFileWithExtension($class, '.php') 繼續查找類文件路徑信息,findFileWithExtension的定義後面將會列出。
如果仍未找到類的文件路徑信息,返回值為 null 且定義了 HHVM_VERSION 信息,則用“.hh”後綴繼續查找類文件信息。
HHVM_VERSION 是 HHVM版本信息? HHVM 是 Facebook 開發的高性能 PHP 虛擬機,宣稱比官方的快9倍。
如果仍未找到,則返回 false 。Remember that this class does not exist.(這句注釋很有喜感?哈哈)
代碼清單 findFileWithExtension()
1 private function findFileWithExtension($class, $ext)
2 {
3 // PSR-4 lookup
4 $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
5
6 $first = $class[0];
7 if (isset($this->prefixLengthsPsr4[$first])) {
8 foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
9 if (0 === strpos($class, $prefix)) {
10 foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
11 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
12 return $file;
13 }
14 }
15 }
16 }
17 }
18
19 // PSR-4 fallback dirs
20 foreach ($this->fallbackDirsPsr4 as $dir) {
21 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
22 return $file;
23 }
24 }
25
26 // PSR-0 lookup
27 if (false !== $pos = strrpos($class, '\\')) {
28 // namespaced class name
29 $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
30 . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
31 } else {
32 // PEAR-like class name
33 $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
34 }
35
36 if (isset($this->prefixesPsr0[$first])) {
37 foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
38 if (0 === strpos($class, $prefix)) {
39 foreach ($dirs as $dir) {
40 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
41 return $file;
42 }
43 }
44 }
45 }
46 }
47
48 // PSR-0 fallback dirs
49 foreach ($this->fallbackDirsPsr0 as $dir) {
50 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
51 return $file;
52 }
53 }
54
55 // PSR-0 include paths.
56 if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
57 return $file;
58 }
59 }
該函數唯一的目的就是根據剛才通過$loader->set($namespace, $path)和$loader->setPsr4($namespace, $path)方法設置的信息找出類文件的路徑信息。
接下來,我們回到代碼清單laravel/vendor/composer/autoload_real.php ,為精簡篇幅,這裡只貼出片段(續上節的 $loader->register(true))。
$loader->register(true);
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $file) {
composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file);
}
return $loader;
}
}
function composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file)
{
require $file;
}
在經歷了一番長途跋涉後,終於從 laravel/vendor/composer/ClassLoader.php 中出來了。繼 $loader->register(true) 之後,又引入了laravel/vendor/composer/autoload_files.php,相比之下,這個文件要簡單得多,只是個數組,列出了幾個文件路徑。
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
$vendorDir . '/ircmaxell/password-compat/lib/password.php',
$vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
$vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Random.php',
$vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
);
接著就是用 composerRequire9b2a1b1cf01c9a870ab98748dc5f1256() 函數 包含並運行 這幾個文件。這四個文件的具體信息,隨後會專門寫博文來認識。
最後 , 返回 ClassLoader 類的實例 $loader 。
現在在回到 代碼清單 laravel/bootstrap/autoload.php 的第7行
1 define('LARAVEL_START', microtime(true));
2 require __DIR__.'/../vendor/autoload.php';
3 if (file_exists($compiled = __DIR__.'/compiled.php'))
4 {
5 require $compiled;
6 }
7 Patchwork\Utf8\Bootup::initMbstring();
注意這裡第7行,之所以可以直接像 Patchwork\Utf8\Bootup::initMbstring() 這麼使用而不需要手動required Bootup類文件,是因為 前面在ClassLoader中的register() 函數用 spl_autoload_register() 對Bootup類進行了注冊。下面說一下 spl_autoload_register 。
spl_autoload_register
要使用 spl_autoload_register ,請保證你的PHP版本(PHP 5 >= 5.1.2)。
www.php.net 對 spl_autoload_register 的解釋如下:注冊__autoload()函數,將函數注冊到SPL __autoload函數棧中。如果該棧中的函數尚未激活,則激活它們。剛接觸 PHP 的同學肯定覺得這個解釋雲裡霧裡的,看完依然不知道什麼意思。要想理解這句話,首先要弄明白 __autoload() 是個什麼東西。
__autoload()
__autoload 的作用是嘗試加載未定義的類,可以通過定義這個函數來啟用類的自動加載。下面舉個例子:
function __autoload($class)
{
echo '嘗試加載的類的名字是:'.$class;
}
$say= @ new say();
上例會輸出:"嘗試加載的類的名字是 say "。由於最後一行引用了尚未定義的類 box ,所以 __autoload 函數將被執行。
再看下面這段
class say
{
public function __construct()
{
echo 'say 類存在,並說出了hello,所以 __autoload 函數不會執行。';
}
}
function __autoload($class)
{
echo '嘗試加載的類的名字是:'.$class;
}
$say= @ new say();
這將會輸出 : say 類存在,並說出了hello,所以 __autoload 函數不會執行。
理解完 __autoload 就好辦了,再看上面:“將函數注冊到SPL __autoload函數棧中”,意思是我們可以自定義 嘗試加載未定義的類時 使用的函數。現在返回代碼片段
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
這下是不是很明白了,當實例化一個類的時候,如果這個類沒有定義,就執行 ClassLoader 類中的 loadClass 函數。loadClass 的定義前面我們說過了,就是找到聲明相關類的文件,然後包含並運行該文件,隨後加載相關類。至此,composer的自動加載機制學習完畢。