Laravel的核心概念

Laravel的核心概念

 

工欲善其事,必先利其器。在開發Xblog的過程當中,稍微領悟了一點Laravel的思想。確實如此,這篇文章讀完你可能並不能從無到有寫出一個博客,但知道Laravel的核心概念以後,當你再次寫起Laravel時,會變得一目瞭然成竹在胸。php

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

  1. 模塊初始化(MINIT),即調用php.ini中指明的擴展的初始化函數進行初始化工做,如mysql擴展。
  2. 請求初始化(RINIT),即初始化爲執行本次腳本所須要的變量名稱和變量值內容的符號表,如$_SESSION變量。
  3. 執行該PHP腳本。
  4. 請求處理完成(Request Shutdown),按順序調用各個模塊的RSHUTDOWN方法,對每一個變量調用unset函數,如unset $_SESSION變量。
  5. 關閉模塊(Module Shutdown) , PHP調用每一個擴展的MSHUTDOWN方法,這是各個模塊最後一次釋放內存的機會。這意味着沒有下一個請求了。

WEB模式和CLI(命令行)模式很類似,區別是:CLI 模式會在每次腳本執行經歷完整的5個週期,由於你腳本執行完不會有下一個請求;而WEB模式爲了應對併發,可能採用多線程,所以生命週期15有可能只執行一次,下次請求到來時重複2-4的生命週期,這樣就節省了系統模塊初始化所帶來的開銷。laravel

能夠看到,Php生命週期是很對稱的。說了這麼多,就是爲了定位Laravel運行在哪裏,沒錯,Laravel僅僅運行再第三個階段:git

file

知道這些有什麼用?你能夠優化你的Laravel代碼,能夠更加深刻的瞭解Larave的singleton(單例)。至少你知道了,每一次請求結束,Php的變量都會unset,Laravel的singleton只是在某一次請求過程當中的singleton;你在Laravel 中的靜態變量也不能在多個請求之間共享,由於每一次請求結束都會unset。理解這些概念,是寫高質量代碼的第一步,也是最關鍵的一步。所以記住,Php是一種腳本語言,全部的變量只會在這一次請求中生效,下次請求之時已被重置,而不像Java靜態變量擁有全局做用。github

好了,開始Laravel的生命週期。web

Laravel的生命週期

概述

Laravel 的生命週期從public\index.php開始,從public\index.php結束。redis

file

注意:如下幾圖箭頭均表明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實例,並向容器註冊核心組件(HttpKernelConsoleKernelExceptionHandler)(對應代碼2,容器很重要,後面詳細講解)。
3.處理請求,生成併發送響應(對應代碼3,絕不誇張的說,你99%的代碼都運行在這個小小的handle方法裏面)。
4.請求結束,進行回調(對應代碼4,還記得可終止中間件嗎?沒錯,就是在這裏回調的)。

file

啓動Laravel基礎服務

咱們不妨再詳細一點:

第一步註冊加載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先於ServiceProvidersFacades也是重點,後面說,這裏簡單提一下,註冊Facades就是註冊config\app.php中的aliases 數組,你使用的不少類,如AuthCache,DB等等都是Facades;而ServiceProvidersregister方法永遠先於boot方法執行,以避免產生boot方法依賴某個實例而該實例還未註冊的現象。

因此,你能夠在ServiceProvidersregister方法中使用任何Facades,在ServiceProvidersboot方法中使用任何register方法中註冊的實例或者Facades,這樣毫不會產生依賴某個類而未註冊的現象。

將請求傳遞給路由

注意到目前爲止,Laravel 尚未執行到你所寫的主要代碼(ServiceProviders中的除外),由於尚未將請求傳遞給路由。

在Laravel基礎的服務啓動以後,就要把請求傳遞給路由了。傳遞給路由是經過Pipeline(另開篇章講解)來傳遞的,可是Pipeline有一堵牆,在傳遞給路由以前全部請求都要通過,這堵牆定義在app\Http\Kernel.php中的$middleware數組中,沒錯就是中間件,默認只有一個CheckForMaintenanceMode中間件,用來檢測你的網站是否暫時關閉。這是一個全局中間件,全部請求都要通過,你也能夠添加本身的全局中間件。

而後遍歷全部註冊的路由,找到最早符合的第一個路由,通過它的路由中間件,進入到控制器或者閉包函數,執行你的具體邏輯代碼。

因此,在請求到達你寫的代碼以前,Laravel已經作了大量工做,請求也通過了千難萬險,那些不符合或者惡意的的請求已被Laravel隔離在外。

file

服務容器


服務容器就是一個普通的容器,用來裝類的實例,而後在須要的時候再取出來。用更專業的術語來講是服務容器實現了控制反轉(Inversion of Control,縮寫爲IoC),意思是正常狀況下類A須要一個類B的時候,咱們須要本身去newB,意味着咱們必須知道類B的更多細節,好比構造函數,隨着項目的複雜性增大,這種依賴是毀滅性的。控制反轉的意思就是,將類A主動獲取類B的過程顛倒過來變成被動,類A只須要聲明它須要什麼,而後由容器提供。

file

這樣作的好處是,類A不依賴於類B的實現,這樣在必定程度上解決了耦合問題。

在Laravel的服務容器中,爲了實現控制反轉,能夠有如下兩種:

  1. 依賴注入(Dependency Injection)。
  2. 綁定。

依賴注入

依賴注入是一種類型提示,舉官網的例子:

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\KernelIlluminate\Contracts\Console\KernelIlluminate\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存儲。

Contracts & Facades(合同&假象)


Laravel 還有一個強大之處是,好比你只需在配置文件中指明你須要的緩存驅動(redismemcachedfile......),Laravel 就自動辦你切換到這種驅動,而不須要你針對某種驅動更改邏輯和代碼。Why? 很簡單,Laravel定義了一系列Contracts(翻譯:合同),本質上是一系列PHP接口,一系列的標準,用來解耦具體需求對實現的依賴關係。其實真正強大的公司是制定標準的公司,程序也是如此,好的標準(接口)尤其重要。當程序變得越來大,這種經過合同或者接口來解耦所帶來的可擴展性和可維護性是無可比擬的。

file

上圖不使用Contracts的狀況下,對於一種邏輯,咱們只能獲得一種結果(方塊),若是變動需求,意味着咱們必須重構代碼和邏輯。可是在使用Contracts的狀況下,咱們只須要按照接口寫好邏輯,而後提供不一樣的實現,就能夠在不改動代碼邏輯的狀況下得到更加多態的結果。

這麼說有點抽象,舉一個真實的例子。在完成Xblog的初期,我使用了緩存,因此致使Repository中充滿了和cache相關的方法:rememberflushforget等等。後來國外網友反映,簡單的博客並不必定須要緩存。因此我決定把它變成可選,但由於代碼中充滿和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(); }

而後,我又完成了兩個實現類:CacheableNoCache

  1. 實現具體緩存。

    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; } }
  2. 不緩存。
    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強大是由於他提供的不少功能,而是它的設計模式和思想。

  1. 理解Laravel生命週期和請求的生命週期概念。
  2. 全部的靜態變量和單例,在下一個請求到來時都會從新初始化。
  3. 將耗時的類或者頻繁使用的類用singleton綁定。
  4. 將變化選項的抽象爲Contracts,依賴接口不依賴具體實現。
  5. 善於利用Laravel提供的容器。

參考:

  1. 深刻理解php底層:php生命週期
  2. Laravel 官方文檔
  3. laravel/framework

-- END

相關文章
相關標籤/搜索