Laravel深入學習5 – 應用架構

laravel發表於2019-02-16

宣告:本文並非博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當然也不是原汁原味的翻譯,能保證90%的原汁性,另外因為是理解翻譯,肯定會有錯誤的地方,歡迎指正。

歡迎轉載,轉載請註明出處,謝謝!

應用架構

簡介

這一章是哪齣戲?對於使用框架建立應用這是非常普遍的。很多開發者會提出這樣的問題,因為在他們腦仁裡已經存在這樣的觀念,“模型”就是“資料庫”。所以通常,控制器被用來和HTTP互動,模型就是和資料庫_打交道_,檢視就是還有HTML程式碼的那部分。但是,對於那些比如傳送郵件的類、驗證資料類、訪問介面的類該怎麼區分呢?本章我們就使用Laravel構建好的架構進行探討,打破那些固話在你心中的概念,讓開發迴歸本質。

MVC會弄死你的

阻礙我們的一種設計即:M-V-C。模型,檢視,控制器,這種框架思維已經控制開發人員很多年了。這種思維來源於Ruby On Rails。如果,讓一個程式設計師去解釋什麼是“模型”,通常都會聽到將其和“資料庫”關聯的答案。據說,模型_就是_資料庫。模型包含了資料庫的一切。但是,很快你就會發現,在簡單的資料庫訪問類之上還有很多額外的邏輯。他需要我們進行資料驗證,調取額外的服務,傳送郵件,等等。

什麼是模型?

模型現在已經變的模稜兩可,很難具體指代什麼。根據開發中遇到的那麼多詞彙,我們可以理解認為,他就是為了將應用切分成小而清晰,具有特定職責的類。

那麼,這種困境中的解決方案是什麼?很多開發人員會在控制器之上新增更多的邏輯。當控制器變的很大的時候,需要複用其他控制器中的一些邏輯層。很多人會錯誤的認為_需要_在當前控制器呼叫其他控制器,而不是講邏輯抽象成單獨的類。這種模式通常稱為“HMVC”。不幸的是,這也是糟糕的設計,通常控制器會很複雜。

HMVC(通常)預示著糟糕的設計

當覺得須要在控制器中呼叫其他控制器?這意味著當前設計是糟糕的,控制器裡面的業務邏輯太負責。我們可以講邏輯抽象成通用類,以便在其他控制器中進行呼叫。

總會有更好的程式設計。我們需要忘記以前在腦海中殘留的那種“模型”的設計理念,乾脆讓我們刪除模型目錄,並重新開始。

再見,模型

是否已經把你的models目錄刪除?如果沒有,沒關係,別再去理他。我們來在app下建立一個新資料夾,簡單的起個應用名字即可QuickBill,在後續的討論中,我們之前舉例的那些例子都會出現。

注意使用場景

記住,如果你建立的是小型的Laravel應用,在models下建立幾個Eloquent模型還是很合適的。而在本章中,我們關注的是擁有更多“層次”架構的複雜應用。

我們已經有了app/QuickBill目錄,他和controllersviews目錄同級。我們可以在QuickBill下建立一些其他目錄,比如RepositoriesBilling目錄。建好目錄之後,記得在composer.json註冊PSR-0自動載入。

"autoload": {
    "psr-0":    {
        "QuickBill":    "app/"
    }
}

現在,我們把Eloquent類放到QuickBill根目錄下,我們就能輕鬆的訪問到QuickBillUser,以及QuickBillPayment等。在Respositories目錄中建立PaymentRepositoryUserRepository類,並編碼資料訪問的方法getRecentPayments以及getRichestUser。在Billing目錄則包含使用第三方服務,諸如“Stripe”、“Balanced”的類和介面。上述目錄結構如下:

// app
    // QuickBill
        // Repositories
            -> UserRepository.php
            -> PaymentRepository.php
        // Billing
            -> BillerInterface.php
            -> StripeBiller.php
        // Notifications
            -> BillingNotifierInterface.php
            -> SmsBillingNotifier.php
        User.php
        Payment.php

資料驗證放哪

這個問題通常讓我們頭大。可以考慮把他們放到“實體”類中,比如User.php或者Payment.php中,方法名可以叫:validForCreationhasValidDomain。或者也可以建立一個名稱空間為ValidationUserValidator類,並注入到repository類中。兩種方法看個人喜好。

擺脫models目錄,我們就能衝破枷鎖,實現好的設計。當然,我們建立的很多專案都會有相似之處,無論多麼複雜的專案也都會有資料接入(儲存)層,以及其他服務層等等。

不要害怕資料夾

不要因為多建資料夾而害怕,他是組織應用程式的很好方式。通常我們希望以此講應用分割成很小的元件,每個元件都有自己特定的職責。別被“模型”束縛了思維。就像上面舉的例子,我們可以建立一個Repository來存放所有資料接入的相關類。

處處皆分層

你應該已經注意到,好的應用設計應擁有明確的職責劃分,有明確的邏輯分成。控制只是用來接收HTTP請求並請求邏輯處理類。業務處理層_才是_整個應用的中心。它包含了像資料獲取,資料驗證,支付處理,郵件傳送類庫,以及應用中的各種函式等。事實上,業務邏輯無需感知“網路”,網路僅僅接入應用的傳輸機制,他不應超出應用中的路由和控制器的範疇。好的架構是經得起考研的,是由清晰程式碼組成的可持續發展的架構。

例如,我們使用向控制器中傳入網路請求輸入來替代在類中直接訪問網路請求例項的方法。簡單的改動即將類從“網路”中解耦出來,這種方式也不用擔心在測試時對請求的再次模擬了:

class BillingController extends BaseController{
    public function __construct(BillerInterface $biller)
    {
        $this->biller = $biller;
    }
    public function postCharge()
    {
        $this->biller->chargeAccount(Auth::user(), Input::get(`amount`));
        return View::make(`charge.success`);
    }
}

chargeAccount方法可以很容易進行測試,只需傳入測試資料用到的整型資料,而不是使用一個含有RequestInput類的BillerInterface介面的實現類庫來作為引數傳入該方法。

職責分離是編寫健壯應用的關鍵。這種關鍵就是一個類是否管的太多。你應該時常問自己:“是不是這個類還要關心X?”,如果答案是“不”,就將邏輯抽象出來,並用依賴注入的方式處理。

改變的原因很簡單

決定類庫是否足夠職責分離的一個非常有用的方法就是檢驗自己為什麼要更改這些程式碼。比如,在調整通知邏輯的時候,是否Biller介面的實現也要修改?當然不,Biller的實現只和支付有關,只需按照約定和通知邏輯互動。保持這樣的思維觀念,就能幫你快速改進應用中的各個部分,使之變得健壯起來。

“瓶瓶罐罐”都放哪

當使用Laravel開發應用的時候,會經常有這樣的疑問,很多“東西”不知道放哪。比如,“helper”函式放哪?事件堅挺程式放哪?檢視元件又該在哪?答案可能會讓你凌亂:“哪都可以”!Laravel沒有檔案該歸屬哪裡的概念。然而這個答案並不是讓人滿意的,在繼續深入之前,讓我們先就上面的問題探討下。

輔助函式

Laravel的輔助函式放在support/helpers.php檔案中。或者你也想建立這樣一個自己的輔助函式檔案,“start”目錄就是個不錯的地方。在請求應用時,start/global.php都會被引用到,我們可以在這新增上載入自己的helpers.php檔案:

// Within app/start/global.php

require_once __DIR__.`/../helpers.php`;

事件監聽器

事件監聽器當然不能屬於routes.php檔案,放在start檔案也不合適,我們要另擇地方安放他。服務提供器的目錄就不錯,之前我們知道,服務提供不僅是容器註冊繫結的服務,還可以做很多其他事情。它可以講很多監聽器組織起來,這種方式是程式碼清理整潔,也不影響應用邏輯。檢視元件也可以放到這個位置,他和監聽器其實是類似的,都能收納在服務提供器中。

比如,用服務提供器組織監聽器:

<?php namespace QuickBillProviders;

use IlluminateSupportServiceProvider;

class BillingEventsProvider extends ServiceProvider{

    public function boot()
    {
        Event::listen(`billing.failed`, function($bill)
        {
            // Handle failed billing event...
        });
    }
}

建立完提供器後,只需要簡單的在app/config/app.php配置中providers陣列新增上它就好。

注意boot方法

記住,上例中我們使用boot的原因,register方法僅僅是用來將服務註冊到容器的方法。

錯誤處理

如果應用中我們自定義了錯誤處理,別接管在“start”檔案中,同樣,像事件監聽器一樣,最好還放在服務提供器中進行組織。提供器可以像這樣命名QuickBillErrorProvider,並在boot方法中講所有自定義的錯誤處理註冊進來,重申一下:我們要將這些程式碼和我們的邏輯分離開來。最終,自定義的錯誤處理程式如下:

<?php namespace QuickBillProviders;

use App, IlluminateSupportServiceProvider;

class QuickBillErrorProvider extends ServiceProvider {

    public function register()
    {    
        //
    }

    public function boot()
    {
        App::error(function(BillingFailedException $e)
        {
            // Handle failed billing exceptions ...
        });
    }
}

簡潔的方案

當然在只有一兩個錯誤處理方式的情況下,把他放到“start”檔案也是一種簡潔的方式。

其他

通常,類庫應該以PSR-0規範組織在我們的應用中。命令式程式碼如事件監聽、錯誤處理、以及其他“註冊”型別的服務最好組織在服務提供器中。基於如上原則,我們就能決策出程式碼的組織規律。有一點不要太猶豫,Laravel是為了讓工作方便於我們的業務,這也是Laravel的宗旨。尋找適合自己應用的結構,並分享給其他人。

如上,我們可以為所有自定義的服務提供器新增一個名稱空間Providers並建立目錄組織起來:

// app
    // QuickBill
        // Billing
        // Extensions
            //Pagination
                -> Environment.php
        // Providers
            -> EventPusherServiceProvider.php
        // Repositories
        User.php
        Payment.php

上例中,有兩個名稱空間ExtensionsProviders,自定義的服務放到Providers目錄下,對框架擴充套件的元件以Extensions名稱空間的方式組織到同名目錄下。

相關文章