Laravel中的核心概念

Rootrl發表於2019-02-16

本文最早釋出於 Rootrl的Blog

導言

Laravel是一款先進的現代化框架,裡面有一些概念非常重要。在上手Laravel之前,我認為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP OOP知識。我相信很多人對比如getter setter以及__invoke、__call、__callStatic這些魔術方法甚至this、
self、static這些關鍵字作用都還是很模糊的(我上一個老大喜歡問這種基礎問題,然後答不上來-_-`)。

DI & IoC

首先名詞解釋,DI全稱是Dependency injection,依賴注入的意思。而IoC是Inversion of control 控制反轉。

要了解依賴注入和控制反轉,首先我們不得不提到物件導向設計中的五大設計原則:S.O.L.I.D。

S.O.L.I.D – 物件導向五大設計原則

SRP The Single Responsibility Principle 單一責任原則
OCP The Open Closed Principle 開放封閉原則
LSP The Liskov Substitution Principle 里氏替換原則
ISP The Interface Segregation Principle 介面分離原則
DIP The Dependency Inversion Principle 依賴倒置原則

這五種思想原則對我們平常的軟體開發設計非常重要,大家可以具體去了解下。

依賴倒置原則

這裡我們重點講下依賴倒置原則:實體必須依靠抽象而不是具體實現。它表示高層次的模組不應該依賴於低層次的模組,它們都應該依賴於抽象。

在傳統軟體設計中,我們一般都是上層程式碼依賴下層程式碼,當下層程式碼變動時,我們上層程式碼要跟著變動,維護成本比較高。這時我們可以上層定義介面,下層來實現這個介面,從而使得下層依賴於上層,降低耦合度。(PC主機板和滑鼠鍵盤介面就是一個很好的例子,各資料廠商根據主機板上的介面來生產自己的滑鼠鍵盤產品,這樣滑鼠壞了後我們可以隨便換個符合介面要求的滑鼠,而不用修改主機板上的什麼東西)

控制反轉

上面講的依賴倒置是一種原則,而控制反轉就是實現依賴倒置的一種具體方法。控制反轉核心是把上層(類)所依賴單元的例項化過程交由第三方實現,而類中不允許存在對所依賴單元的例項化語句。舉個例子:


class Comment
{
    ...

    public function afterInsert()
    {
        $notification = new EmailNotification(...);
        $notification->send(...);
    }
}

如上,假如我們在使用者提交評論後通知被評論者,這裡通知方式是郵件,而且是直接在類中例項化郵件通知類,這樣程式碼耦合度高,如果換個簡訊通知方式就不得不改這裡面程式碼,具體好的實現我們下面會講到。

依賴注入

依賴注入是一種設計模式,是一種IoC的具體實現,實現了IoC自然就符合依賴倒置原則。依賴注入的核心思想是把類中所依賴單元的例項化過程放到類外面中去實現,然後把依賴注入進來。常用的依賴注入方式有屬性注入和建構函式注入。比如用建構函式注入解耦上面程式碼:


// 通知介面
interface Notifaction
{
    public function send(...);
}


// 簡訊通知實現通知介面
class SmsNotification implements Notification
{
    public function send(...)
    {
        ...
    }
}

// 評論類
class Comment
{
    ...

    protected $notification;

    public function __construct(Notification $smsNotification)
    {
        $this->notification = $smsNotification;
    }

    public function afterInsert()
    {
        $this->notification->send(...);
    }
}

// 例項化簡訊通知類
$smsNotification = new SmsNotification(...);

// 通過建構函式方法注入
$comment = new Comment($smsNotification);

...

$comment->save();

這樣,我們先定義Notification介面,裡面有個send方法,讓後面的通知者不管是郵件類還是簡訊類都實現這個介面,然後在外面通過建構函式方式注入進來,這樣就解決了Comment類對具體通知方法的依賴,只要是實現了Notification介面的,都可以通過建構函式傳進來,Comment類完全不用做任何修改。這樣無論對於程式碼維護還是單元測試(可以模擬實現一個Notification類),都非常方便。

依賴注入是IoC的一種具體實現,是一種解耦手段。當然IoC不止這一種實現,比如Yii中的Service Locator(服務定位器)

IoC container/DI container

當專案比較大時,就會有許多類似上面評論類和通知類這種依賴關係,整個專案會非常複雜。這時候就需要一個集中的地方來管理這些依賴,我們把它叫IoC container 控制反轉容器,它提供了動態地建立、注入依賴單元、對映依賴關係等功能。這樣可以集中管理依賴關係,也減少很多程式碼量。

Service container 服務容器

Laravel官方文件這樣定義服務容器:Laravel服務容器是用於管理類的依賴和執行依賴注入的工具。

首先,服務容器通過DI依賴注入方式實現了IoC,然後它還支援另一種實現:繫結與解析。

繫結

幾乎所有服務容器繫結操作都是Service provider(服務提供器)中註冊繫結的,服務提供器中可以通過$this->app方式獲取服務容器,然後通過服務容器提供的方法比如$this->app->bind(…)等進行具體服務繫結。類似支援的繫結方式還有:

  • 簡單繫結
  • 繫結單例
  • 繫結例項
  • 繫結初始資料
  • 繫結介面到實現
  • 上下文繫結
  • 標記
  • 擴充套件繫結

具體可以檢視官方文件:https://laravel.com/docs/5.6/…

解析

繫結後可以從服務容器中解析出物件才能夠使用。解析方法包括:

  • 通過 make 方法,接收一個你想要解析的類或者介面
  • 通過陣列方式從容器中解析物件
  • 自動注入
示例

我們先定義一個自己的類


class Foo
{
    public function bar()
    {
        ...
    }
}

我們把Foo類簡單繫結到服務容器:


App::bind("foo", function($app){
    return new Foo();
})

平時在上下文獲取這個例項:


$foo = App::make("foo");    // $foo就是Foo類的例項

當然,這種繫結和解析平時我們在程式碼中隨便可以寫到哪裡,但是多了的話就亂起來了。所以我開頭說幾乎所有這種依賴服務繫結操作都是在Service provider中進行的。

下面就給大家介紹Service provider。

Service provider 服務提供器

為了讓依賴注入的程式碼不至於混亂,Laravel提供了一個服務提供器(Service Provider),它將這些依賴聚集在了一塊,統一申明和管理,讓依賴變得更加容易維護。

下面都是一些抄來的官話、套話(-_-`),大家可以直接跳到程式碼示例,後續再檢視官方文件加深理解。

所有服務提供者都需要繼承IlluminateSupportServiceProvider類。大多數服務提供者都包含 register 和 boot 方法。register方法中,只能將事務繫結到服務容器。不應該在register方法中嘗試註冊任何事件監聽器,路由或者任何其他功能。可以為服務提供者的boot方法設定型別提示。服務容器會自動注入需要的任何依賴。boot方法將在所有其他服務提供者均已註冊之後呼叫。

所有服務提供者都在 config/app.php 配置檔案中註冊。可以選擇推遲服務提供者的註冊,直到真正需要註冊繫結時,這樣可以提供應用程式的效能。

示例

上一個示例我們是自己在上下文中隨意定義、獲取。下面我們以服務提供者的方式進行:


use IlluminateSupportServiceProvider;

class FooServiceProvider extends ServiceProvider {

    public function register()
    {
        $this->app->bind(`foo`, function()
        {
            return new Foo();
        });
    }

}

上面實現了一個Foo的服務提供,我們可以手動注入到上下文中:


App::register(`FooServiceProvider`);

當然我們更多的是通過配置檔案來完成的,在app/config/app.php中的providers陣列裡面增加一行:


`providers` => [
    …
       ‘FooServiceProvider’,
],

這樣我們可以在上下文中直接獲取例項:


App::make(‘foo’)

當然,我們還可以通過門面方式,更方便的操作Foo類。

Facades 門面

門面實際上是應用了設計模式中的外觀模式:

外觀模式(Facade),他隱藏了系統的複雜性,並向客戶端提供了一個可以訪問系統的介面。這種型別的設計模式屬於結構性模式。為子系統中的一組介面提供了一個統一的訪問介面,這個介面使得子系統更容易被訪問或者使用。

Laravel中隨處可見這些靜態方法的呼叫:


$value = Cache::get(`key`);

這些靜態呼叫實際上呼叫的並不是靜態方法,而是通過PHP的魔術方法 __callStatic() 將請求轉到了相應的方法上。

比如如果我們看一下 IlluminateSupportFacadesCache 這個類,你會發現類中根本沒有 get 這個靜態方法:


class Cache extends Facade
{
    /**
     * 獲取元件的註冊名稱。
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return `cache`; }
}

這其中的奧祕在基類Facade中:


public static function __callStatic($method, $args)
{
    // 獲取例項
    $instance = static::getFacadeRoot();

    if (!$instance) {
        throw new RuntimeException(`A facade root has not been set.`);
    }

    // 真正調取對應的方法
    return $instance->$method(...$args);
}

這裡面有一個獲取例項的過程,然後去呼叫具體方法。

示例

接上一個示例,我們平常是通過App::make(`foo`)來獲取例項,然後再呼叫具體方法。現在我們通過門面的方式簡化這個流程:

先定義一個門面:


use IlluminateSupportFacadesFacade;

class Foo extends Facade {

    protected static function getFacadeAccessor() { return ‘foo’; }

}

然後我們可以很方便的使用Foo類某個方法:


Foo::bar();

Contracts 契約

Laravel的契約是一組定義框架提供的核心服務的介面。後續針對這個介面可以有多種實現,解耦了具體實現的依賴,在不改變程式碼邏輯的情況下獲得更加多型的結果。

比如你只需在配置檔案中指明你需要的快取驅動(redis,memcached,file等),Laravel會自動幫你切換到這種驅動,而不需要你針對某種驅動更改邏輯和程式碼。

總結

這些都是些基礎的抽象概念,但是是非常重要的,Laravel中隨處可見這些思想,是一切實現的基石。

學習的過程中基礎是非常重要的,知其然必知其所以然。就像道與術,道是在術之前的,老子說過:”有道無術,術尚可求也,有術無道,止於術“。不過實際中應該是相輔相成的關係,“以道統術,以術得道”。

引用

https://laravel.com/docs/5.6/…
https://laravel-china.org/doc…
http://www.digpage.com/di.html
http://yansu.org/2014/12/06/i…

相關文章