首頁  >  文章  >  php框架  >  解析Laravel框架下的Contracts契約

解析Laravel框架下的Contracts契約

藏色散人
藏色散人轉載
2020-01-31 20:08:142034瀏覽

解析Laravel框架下的Contracts契約

Contracts

Laravel 的契約是一組定義框架提供的核心服務的接口, 例如我們在介紹用戶認證的章節中到的使用者看守器契約IllumninateContractsAuthGuard 和使用者提供器契約IlluminateContractsAuthUserProvider以及框架自帶的App\User模型所實作的IlluminateContracts##Authenttabletabletables#Authenticatabletable ##契約。

為什麼使用契約

透過上面幾個契約的原始碼檔案我們可以看到,Laravel提供的契約是為核心模組定義的一組interface 。 Laravel為每個契約都提供了相應的實作類,下表列出了Laravel為上述的三個契約提供的實作類別。

解析Laravel框架下的Contracts契約所以在自己開發的專案中,如果Laravel提供的使用者認證系統無法滿足需求,你可以根據需求定義看守器和使用者提供器的實作類,例如我之前做的專案就是使用者認證依賴公司的員工管理系統的API,所以我就自己寫了看守器和使用者提供器契約的實現類,讓Laravel透過自訂的Guard和UserProvider來完成使用者認證。自訂使用者認證的方法在介紹使用者認證的章節中我們介紹過,讀者可以去翻閱那塊的文章。

所以Laravel為所有的核心功能定義契約接口的目的就是為了讓開發者能夠根據自己項目的需要自己定義實現類,而對於這些接口的消費者(比如:Controller、或者內核提供的AuthManager這些)他們不需要關心介面提供的方法具體是怎麼實現的, 只關心接口的方法能提供什麼功能然後去使用這些功能就可以了,我們可以根據需求在必要的時候為接口更換實現類,而消費端不用進行任何改動。

定義並使用契約

上面我們提到的都是Laravel核心提供的契約, 在開發大型專案的時候我們也可以自己在專案中定義契約和實作類,你有可能會覺得自帶的Controller、Model兩層就已經足夠你編寫程式碼了,憑空多出來契約和實作類別會讓開發變得繁瑣。我們先從一個簡單的例子出發,考慮下面的程式碼有什麼問題:

class OrderController extends Controller
{
    public function getUserOrders()
    {
        $orders= Order::where('user_id', '=', \Auth::user()->id)->get();
        return View::make('order.index', compact('orders'));
    }
}

這段程式碼很簡單,但我們要想測試這段程式碼的話就一定會和實際的資料庫發生聯繫。

也就是說, ORM和這個控制器有著緊密耦合。如果不使用Eloquent ORM,不連接到實際資料庫,我們就沒辦法運行或測試這段程式碼。這段程式碼同時也違背了「關注分離」這個軟體設計原則。

簡單講:這個控制器知道的太多了。 

控制器不需要去了解資料是從哪裡來的,只要知道如何存取就好。控制器也不需要知道這資料是從MySQL或哪裡來的,只需要知道這資料目前是可用的。

Separation Of Concerns 關注分離

Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

每個類別都應該只有單一的職責,而且職責裡所有的東西都應該由這個類別封裝

#接下來我們定義一個接口,然後實作該介面

interface OrderRepositoryInterface 
{
    public function userOrders(User $user);
}
 
class OrderRepository implements OrderRepositoryInterface
{
    public function userOrders(User $user)
    {
        Order::where('user_id', '=', $user->id)->get();
    }
}

將介面的實作綁定到Laravel的服務容器中

App::singleton('OrderRepositoryInterface', 'OrderRespository');

然後我們將該介面的實作注入我們的控制器

class UserController extends Controller
{
    public function __construct(OrderRepositoryInterface $orderRepository)
    {
        $this->orders = $orderRespository;
    }
   
    public function getUserOrders()
    {
        $orders = $this->orders->userOrders();
        return View::make('order.index', compact('orders'));
    }
}

現在我們的控制器就完全和資料層面無關了。在這裡我們的資料可能來自MySQL,MongoDB或Redis。我們的控制器不知道也不需要知道他們的差別。這樣我們就可以獨立於資料層來測試Web層了,將來切換儲存實作也會很容易。

介面與團隊開發

當你的團隊在開發大型應用程式時,不同的部分有著不同的開發速度。

例如一個開發人員在開發資料層,另一個開發人員在做控制器層。

寫入控制器的開發者想測試他的控制器,不過資料層開發較慢沒辦法同步測試。那如果兩個開發者能先以interface的方式達成協議,後台開發的各種類別都遵循這種協議。

一旦建立了約定,就算約定還沒實現,開發者也可以為這介面寫個「假」實作

class DummyOrderRepository implements OrderRepositoryInterface 
{
    public function userOrders(User $user)
    {
        return collect(['Order 1', 'Order 2', 'Order 3']);
    }
}

一旦假實作寫好了,就可以被綁定到IoC容器裡

App::singleton('OrderRepositoryInterface', 'DummyOrderRepository');

然後這個應用程式的視圖就可以用假資料填滿了。接下來一旦後台​​開發者寫完了真正的實作程式碼,例如叫

RedisOrderRepository

<p>那么使用IoC容器切换接口实现,应用就可以轻易地切换到真正的实现上,整个应用就会使用从Redis读出来的数据了。</p> <p><strong>接口与测试</strong></p> <p>建立好接口约定后也更有利于我们在测试时进行<code>Mock

public function testIndexActionBindsUsersFromRepository()
{    
    // Arrange...
    $repository = Mockery::mock(&#39;OrderRepositoryInterface&#39;);
    $repository->shouldReceive(&#39;userOrders&#39;)->once()->andReturn([&#39;order1&#39;, &#39;order2]);
    App::instance(&#39;OrderRepositoryInterface&#39;, $repository);
    // Act...
    $response  = $this->action(&#39;GET&#39;, &#39;OrderController@getUserOrders&#39;);
         
    // Assert...
    $this->assertResponseOk();
    $this->assertViewHas(&#39;order&#39;, [&#39;order1&#39;, &#39;order2&#39;]);
 }

总结

接口在程序设计阶段非常有用,在设计阶段与团队讨论完成功能需要制定哪些接口,然后设计出每个接口具体要实现的方法,方法的入参和返回值这些,每个人就可以按照接口的约定来开发自己的模块,遇到还没实现的接口完全可以先定义接口的假实现等到真正的实现开发完成后再进行切换,这样既降低了软件程序结构中上层对下层的耦合也能保证各部分的开发进度不会过度依赖其他部分的完成情况。

更多laravel框架相关技术文章,请访问laravel教程栏目!

以上是解析Laravel框架下的Contracts契約的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除