工欲善其事,必先利其器。在開發Xblog的過程當中,稍微領悟了一點Laravel的思想。確實如此,這篇文章讀完你可能並不能從無到有寫出一個博客,但知道Laravel的核心概念以後,當你再次寫起Laravel時,會變得一目瞭然成竹在胸。php
萬物皆有他的生命週期。熟悉Android的同窗必定熟悉Android最經典的Activity的生命週期,Laravel 也不例外,Laravel應用程序也有本身的生命週期。Laravel是什麼?一個PHP框架。因此要想真正說清Laravel的生命週期,必須說清PHP的生命週期。原文參考這裏,這裏作個總結。mysql
Php有兩種運行模式,WEB模式和CLI(命令行)模式。當咱們在終端敲入php這個命令的時候,使用的是CLI模式;當使用Nginx或者別web服務器做爲宿主處理一個到來的請求時,會調用Php運行,此時使用的是WEB模式。當咱們請求一個Php文件時,好比Laravel 的public\index.php
文件時,Php 爲了完成此次請求,會發生5個階段的生命週期切換:nginx
php.ini
中指明的擴展的初始化函數進行初始化工做,如mysql
擴展。$_SESSION
變量。RSHUTDOWN
方法,對每一個變量調用unset
函數,如unset $_SESSION
變量。MSHUTDOWN
方法,這是各個模塊最後一次釋放內存的機會。這意味着沒有下一個請求了。WEB模式和CLI(命令行)模式很類似,區別是:CLI 模式會在每次腳本執行經歷完整的5個週期,由於你腳本執行完不會有下一個請求;而WEB模式爲了應對併發,可能採用多線程,所以生命週期1
和5
有可能只執行一次,下次請求到來時重複2-4
的生命週期,這樣就節省了系統模塊初始化所帶來的開銷。laravel
能夠看到,Php生命週期是很對稱的。說了這麼多,就是爲了定位Laravel運行在哪裏,沒錯,Laravel僅僅運行再第三個階段:git
知道這些有什麼用?你能夠優化你的Laravel代碼,能夠更加深刻的瞭解Larave的singleton
(單例)。至少你知道了,每一次請求結束,Php的變量都會unset
,Laravel的singleton
只是在某一次請求過程當中的singleton
;你在Laravel 中的靜態變量也不能在多個請求之間共享,由於每一次請求結束都會unset
。理解這些概念,是寫高質量代碼的第一步,也是最關鍵的一步。所以記住,Php是一種腳本語言,全部的變量只會在這一次請求中生效,下次請求之時已被重置,而不像Java靜態變量擁有全局做用。github
好了,開始Laravel的生命週期。web
Laravel 的生命週期從public\index.php
開始,從public\index.php
結束。redis
注意:如下幾圖箭頭均表明Request流向sql
這麼說有點草率,但事實確實如此。下面是public\index.php
的所有源碼(Laravel源碼的註釋是最好的Laravel文檔
),更具體來講能夠分爲四步:bootstrap
1. require __DIR__.'/../bootstrap/autoload.php'; 2. $app = require_once __DIR__.'/../bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 3. $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); $response->send(); 4. $kernel->terminate($request, $response);
這四步詳細的解釋是:
1.註冊加載composer自動生成的class loader
,包括全部你composer require
的依賴(對應代碼1
).
2.生成容器Container
,Application實例,並向容器註冊核心組件(HttpKernel
,ConsoleKernel
,ExceptionHandler
)(對應代碼2
,容器很重要,後面詳細講解)。
3.處理請求,生成併發送響應(對應代碼3
,絕不誇張的說,你99%的代碼都運行在這個小小的handle
方法裏面)。
4.請求結束,進行回調(對應代碼4
,還記得可終止中間件嗎?沒錯,就是在這裏回調的)。
咱們不妨再詳細一點:
第一步註冊加載composer自動生成的class loader
就是加載初始化第三方依賴,不屬於Laravel核心,到此爲止。
第二步生成容器Container
,並向容器註冊核心組件,這裏牽涉到了容器Container
和合同Contracts
,這是Laravel的重點,下面將詳細講解。
重點是第三步處理請求,生成併發送響應。
首先Laravel框架捕獲到用戶發到public\index.php
的請求,生成Illuminate\Http\Request
實例,傳遞給這個小小的handle
方法。在方法內部,將該$request
實例綁定到第二步生成的$app
容器上。而後在該請求真正處理以前,調用bootstrap
方法,進行必要的加載和註冊,如檢測環境,加載配置,註冊Facades(假象),註冊服務提供者,啓動服務提供者等等。這是一個啓動數組,具體在Illuminate\Foundation\Http\Kernel
中,包括:
protected $bootstrappers = [ 'Illuminate\Foundation\Bootstrap\DetectEnvironment', 'Illuminate\Foundation\Bootstrap\LoadConfiguration', 'Illuminate\Foundation\Bootstrap\ConfigureLogging', 'Illuminate\Foundation\Bootstrap\HandleExceptions', 'Illuminate\Foundation\Bootstrap\RegisterFacades', 'Illuminate\Foundation\Bootstrap\RegisterProviders', 'Illuminate\Foundation\Bootstrap\BootProviders', ];
看類名知意,Laravel是按順序遍歷執行註冊這些基礎服務的,注意順序:Facades
先於ServiceProviders
,Facades
也是重點,後面說,這裏簡單提一下,註冊Facades
就是註冊config\app.php
中的aliases
數組,你使用的不少類,如Auth
,Cache
,DB
等等都是Facades
;而ServiceProviders
的register
方法永遠先於boot
方法執行,以避免產生boot
方法依賴某個實例而該實例還未註冊的現象。
因此,你能夠在ServiceProviders
的register
方法中使用任何Facades
,在ServiceProviders
的boot
方法中使用任何register
方法中註冊的實例或者Facades
,這樣毫不會產生依賴某個類而未註冊的現象。
注意到目前爲止,Laravel 尚未執行到你所寫的主要代碼(ServiceProviders
中的除外),由於尚未將請求傳遞給路由。
在Laravel基礎的服務啓動以後,就要把請求傳遞給路由了。傳遞給路由是經過Pipeline
(另開篇章講解)來傳遞的,可是Pipeline
有一堵牆,在傳遞給路由以前全部請求都要通過,這堵牆定義在app\Http\Kernel.php
中的$middleware
數組中,沒錯就是中間件,默認只有一個CheckForMaintenanceMode
中間件,用來檢測你的網站是否暫時關閉。這是一個全局中間件,全部請求都要通過,你也能夠添加本身的全局中間件。
而後遍歷全部註冊的路由,找到最早符合的第一個路由,通過它的路由中間件,進入到控制器或者閉包函數,執行你的具體邏輯代碼。
因此,在請求到達你寫的代碼以前,Laravel已經作了大量工做,請求也通過了千難萬險,那些不符合或者惡意的的請求已被Laravel隔離在外。
服務容器就是一個普通的容器,用來裝類的實例,而後在須要的時候再取出來。用更專業的術語來講是服務容器實現了控制反轉(Inversion of Control,縮寫爲IoC),意思是正常狀況下類A
須要一個類B
的時候,咱們須要本身去new
類B
,意味着咱們必須知道類B
的更多細節,好比構造函數,隨着項目的複雜性增大,這種依賴是毀滅性的。控制反轉的意思就是,將類A
主動獲取類B
的過程顛倒過來變成被動,類A
只須要聲明它須要什麼,而後由容器提供。
這樣作的好處是,類A
不依賴於類B
的實現,這樣在必定程度上解決了耦合問題。
在Laravel的服務容器中,爲了實現控制反轉,能夠有如下兩種:
依賴注入是一種類型提示,舉官網的例子:
class UserController extends Controller { /** * The user repository implementation. * * @var UserRepository */ protected $users; /** * Create a new controller instance. * * @param UserRepository $users * @return void */ public function __construct(UserRepository $users) { $this->users = $users; } /** * Show the profile for the given user. * * @param int $id * @return Response */ public function show($id) { $user = $this->users->find($id); return view('user.profile', ['user' => $user]); } }
這裏UserController
須要一個UserRepository
實例,咱們只需在構造方法中聲明咱們須要的類型,容器在實例化UserController
時會自動生成UserRepository
的實例(或者實現類,由於UserRepository
能夠爲接口),而不用主動去獲取UserRepository
的實例,這樣也就避免了了解UserRepository
的更多細節,也不用解決UserRepository
所產生的依賴,咱們所作的僅僅是聲明咱們所須要的類型,全部的依賴問題都交給容器去解決。(Xblog使用了Repository的是設計模式,你們能夠參考)
綁定操做通常在ServiceProviders
中的register
方法中,最基本的綁定是容器的bind
方法,它接受一個類的別名或者全名和一個閉包來獲取實例:
$this->app->bind('XblogConfig', function ($app) { return new MapRepository(); });
還有一個singleton
方法,和bind
寫法沒什麼區別。你也能夠綁定一個已經存在的對象到容器中,上文中提到的request
實例就是經過這種方法綁定到容器的:$this->app->instance('request', $request);
。綁定以後,咱們能夠經過一下幾種方式來獲取綁定實例:
1. app('XblogConfig'); 2. app()->make('XblogConfig'); 3. app()['XblogConfig']; 4. resolve('XblogConfig');
以上四種方法均會返回得到MapRepository
的實例,惟一的區別是,在一次請求的生命週期中,bind
方法的閉包會在每一次調用以上四種方法時執行,singleton
方法的閉包只會執行一次。在使用中,若是每個類要獲的不一樣的實例,或者須要「個性化」的實例時,這時咱們須要用bind
方法以避免此次的使用對下次的使用形成影響;若是實例化一個類比較耗時或者類的方法不依賴該生成的上下文,那麼咱們可使用singleton
方法綁定。singleton
方法綁定的好處就是,若是在一次請求中咱們屢次使用某個類,那麼只生成該類的一個實例將節省時間和空間。
你也能夠綁定接口與實現,例如:
$app->singleton(
Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class );
上文講述的Laravel的生命週期的第二步,Laravel默認(在bootstrap\app.php
文件中)綁定了Illuminate\Contracts\Http\Kernel
,Illuminate\Contracts\Console\Kernel
,Illuminate\Contracts\Debug\ExceptionHandler
接口的實現類,這些是實現類框架的默認自帶的。可是你仍然能夠本身去實現。
還有一種上下文綁定,就是相同的接口,在不一樣的類中能夠自動獲取不一樣的實現,例如:
$this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('local'); }); $this->app->when(VideoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('s3'); });
上述代表,一樣的接口Filesystem
,使用依賴注入時,在PhotoController
中獲取的是local
存儲而在VideoController
中獲取的是s3
存儲。
Laravel 還有一個強大之處是,好比你只需在配置文件中指明你須要的緩存驅動(redis
,memcached
,file
......),Laravel 就自動辦你切換到這種驅動,而不須要你針對某種驅動更改邏輯和代碼。Why? 很簡單,Laravel定義了一系列Contracts
(翻譯:合同),本質上是一系列PHP接口,一系列的標準,用來解耦具體需求對實現的依賴關係。其實真正強大的公司是制定標準的公司,程序也是如此,好的標準(接口)尤其重要。當程序變得越來大,這種經過合同或者接口來解耦所帶來的可擴展性和可維護性是無可比擬的。
上圖不使用Contracts
的狀況下,對於一種邏輯,咱們只能獲得一種結果(方塊),若是變動需求,意味着咱們必須重構代碼和邏輯。可是在使用Contracts
的狀況下,咱們只須要按照接口寫好邏輯,而後提供不一樣的實現,就能夠在不改動代碼邏輯的狀況下得到更加多態的結果。
這麼說有點抽象,舉一個真實的例子。在完成Xblog的初期,我使用了緩存,因此致使Repository
中充滿了和cache
相關的方法:remember
,flush
,forget
等等。後來國外網友反映,簡單的博客並不必定須要緩存。因此我決定把它變成可選,但由於代碼中充滿和cache
相關的方法,實現起來並非很容易。因而想起Laravel的重要概念Contracts
。因而,我把與緩存有關的方法抽象出來造成一個Contracts:XblogCache,實際操做只與Contracts有關,這樣問題就獲得瞭解決,而幾乎沒有改變原有的邏輯。XblogCache的代碼以下(源碼點擊這裏):
namespace App\Contracts; use Closure; interface XblogCache { public function setTag($tag); public function setTime($time_in_minute); public function remember($key, Closure $entity, $tag = null); public function forget($key, $tag = null); public function clearCache($tag = null); public function clearAllCache(); }
而後,我又完成了兩個實現類:Cacheable
和NoCache
:
實現具體緩存。
class Cacheable implements XblogCache { public $tag; public $cacheTime; public function setTag($tag) { $this->tag = $tag; } public function remember($key, Closure $entity, $tag = null) { return cache()->tags($tag == null ? $this->tag : $tag)->remember($key, $this->cacheTime, $entity); } public function forget($key, $tag = null) { cache()->tags($tag == null ? $this->tag : $tag)->forget($key); } public function clearCache($tag = null) { cache()->tags($tag == null ? $this->tag : $tag)->flush(); } public function clearAllCache() { cache()->flush(); } public function setTime($time_in_minute) { $this->cacheTime = $time_in_minute; } }
class NoCache implements XblogCache { public function setTag($tag) { // Do Nothing } public function setTime($time_in_minute) { // Do Nothing } public function remember($key, Closure $entity, $tag = null) { /** * directly return */ return $entity(); } public function forget($key, $tag = null) { // Do Nothing } public function clearCache($tag = null) { // Do Nothing } public function clearAllCache() { // Do Nothing } }
而後再利用容器的綁定,根據不一樣的配置,返回不一樣的實現(源碼):
public function register() { $this->app->bind('XblogCache', function ($app) { if (config('cache.enable') == 'true') { return new Cacheable(); } else { return new NoCache(); } }); }
這樣,就實現了緩存的切換而不須要更改你的具體邏輯代碼。固然依靠接口而不依靠具體實現的好處不只僅這些。實際上,Laravel全部的核心服務都是實現了某個Contracts
接口(都在Illuminate\Contracts\
文件夾下面),而不是依賴具體的實現,因此徹底能夠在不改動框架的前提下,使用本身的代碼改變Laravel框架核心服務的實現方式。
說一說Facades
。在咱們學習了容器的概念後,Facades
就變得十分簡單了。在咱們把類的實例綁定到容器的時候至關於給類起了個別名,而後覆蓋Facade
的靜態方法getFacadeAccessor
並返回你的別名,而後你就可使用你本身的Facade
的靜態方法來調用你綁定類的動態方法了。其實Facade
類利用了__callStatic()
這個魔術方法來延遲調用容器中的對象的方法,這裏不過多講解,你只須要知道Facade
實現了將對它調用的靜態方法映射到綁定類的動態方法上,這樣你就可使用簡單類名調用而不須要記住長長的類名。這也是Facades
的中文翻譯爲假象的緣由。
Laravel強大之處不只僅在於它給你提供了一系列腳手架,好比超級好用的ORM
,基於Carbon
的時間處理,以及文件存儲等等功能。可是Laravel的核心很是很是簡單:利用容器和抽象解耦,實現高擴展性。容器和抽象是全部大型框架必須解決的問題,像Java的Spring,Android的Dagger2等等都是圍繞這幾個問題的。因此本質上講,Laravel之因此強大出名,是由於它的設計,思想,可擴展性。而Laravel的好用功能只是官方基於這些核心提供的腳手架,你一樣也能夠很輕鬆的添加本身的腳手架。
因此不要以爲Laravel強大是由於他提供的不少功能,而是它的設計模式和思想。
singleton
綁定。Contracts
,依賴接口不依賴具體實現。參考:
-- END