轉自 PHP / Laravel 開發者社區 laravel-china.org/topics/2191…php
我很是喜歡編寫基於模塊化設計的軟件和編程方式,但我不太喜歡依賴第三方軟件包和類庫來處理一些瑣碎的事情,由於它們不會讓你的編程水平獲得很好的提高。因此這兩年來,我一直在用Laravel編寫基於模塊的軟件,如今我對這個結果很是滿意。laravel
推進我走向基於模塊化設計的軟件和編程方式的決定性因素是我想持續提高個人編程水平。想象一下,你構建了一個項目結構,6個月後你發現這個項目存在不少bug。在不影響6個月現有代碼的狀況下,一般不會輕易改變項目架構。在分析這個項目時,我注意到了兩個要點:你要麼在整個項目中都有一個標準,要麼堅持下去,要麼模塊化並逐個模塊地改進。web
有些人傾向於不惜一切代價、固守標準地開發,即便這可能意味着要堅持一個你再也不喜歡的標準。就我我的來言,我更喜歡持續地改進,如果第 20 個模塊和第 1 個模塊寫得徹底不同也不要緊。若是某天我須要回到模塊 1 修復 BUG 或重構,我能夠將其改進爲第 20 個模塊使用的最新標準。數據庫
假設,你也像我同樣喜歡基於模塊化開發 Laravel 應用、儘量避免在項目中添加沒必要要的第三方依賴——本文是個人一點經驗。編程
Laravel 路由系統能夠說是整個應用的入口。首先須要修改的是默認的 RouteServiceProvider.php
文件,它應當將現有路由模塊化。api
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* 定義應用路由。
*
* @return void
*/
public function map()
{
$this->mapModulesRoutes();
}
protected function mapModulesRoutes()
{
// 若是你在編寫傳統 Web 應用而非 HTTP API,請使用 `web` 中間件。
Route::middleware('api')
->group(base_path('routes/modules.php'));
}
}
複製代碼
如上,咱們能夠直接擺脫該文件的整個樣板,只需設置一個模塊化的路由文件便可。bash
Laravel 在 routes
文件夾中自帶了一些文件。因爲咱們已經不在 RouteServiceProvider
中映射這些路由,因此能夠直接刪除它們。接下來,咱們建立一個 modules.php
路由文件。架構
<?php
use Illuminate\Support\Facades\Route;
Route::group([], base_path('app/Modules/Books/routes.php'));
Route::group([], base_path('app/Modules/Authors/routes.php'));
複製代碼
在 app
文件夾中,建立 Modules/Books/routes.php
文件。在此文件中,咱們能夠定義該應用 Books 模塊的路由規則。app
<?php
use App\Modules\Books\ListBooks;
use Illuminate\Support\Facades\Route;
Route::get('/books', ListBooks::class);
複製代碼
你可使用基於控制器——也就是 Laravel 中默認標準的路由方式,但我我的更喜歡 Good bye controllers, hello Request Handlers(放棄控制器,採用請求處理器) 的方式。 以下是 ListBooks
的實現。框架
<?php
namespace App\Modules\Books;
use App\Eloquent\Book;
use App\Modules\Books\Resources\BookResource;
class ListBooks
{
public function __invoke(Book $book)
{
return BookResource::collection($book->paginate());
}
}
複製代碼
以上代碼中 BookResource
是 Laravel 的資源轉換層。按照官方對於命名空間的建議,咱們能夠在 app/Modules/Books/Resources
文件夾中建立它。
<?php
namespace App\Modules\Books\Resources;
use Illuminate\Http\Resources\Json\Resource;
class BookResource extends Resource
{
public function toArray($request)
{
return [
'id' => $this->resource->id,
'title' => $this->resource->title,
];
}
}
複製代碼
咱們還能夠經過 Routes 文件來啓動 Authors 模塊。
<?php
use App\Modules\Authors\ListAuthors;
use Illuminate\Support\Facades\Route;
Route::get('/authors', ListAuthors::class);
複製代碼
注意: app/Modules/Authors
這個命名空間正表示咱們所編寫的文件,對於請求處理程序來講也是很是簡單的。
<?php
namespace App\Modules\Authors;
use App\Eloquent\Author;
use App\Modules\Authors\Resources\AuthorResource;
class ListAuthors
{
public function __invoke(Author $author)
{
return AuthorResource::collection($author->paginate());
}
}
複製代碼
最後,咱們將編寫的 Resource 類轉變爲響應式的 JSON 格式。
<?php
namespace App\Modules\Authors\Resources;
use App\Modules\Books\Resources\BookResource;
use Illuminate\Http\Resources\Json\Resource;
class AuthorResource extends Resource
{
public function toArray($request)
{
return [
'id' => $this->resource->id,
'name' => $this->resource->name,
'books' => $this->whenLoaded('books', function () {
return BookResource::collection($this->resource->books);
})
];
}
}
複製代碼
注意資源是如何進入另外一個模塊以重用 BookResource
。 這一般不是一個比較好的選擇,由於模塊應該是徹底自給自足的,而且只能重用標準類,例如 Eloquent Models
或設計用於在任何模塊上通用的通用的組件。 這個問題的解決方案一般是將 BookResource
複製到 Authors
模塊中,從而能夠在不使用另外一個模塊的狀況下進行更改,反之亦然。 我決定保留這個跨模塊的用法,這個例子表現出一個很好的經驗方法,就是讓模塊之間彼此隔離,可是若是你認爲上面的例子很簡單而且不太可能帶來任何問題。 始終確保編寫測試以涵蓋您編寫的功能,以免其餘人在不知不覺中修改您的應用程序。
雖然這是一個很是簡單的例子,但我但願它可以讓人們根據本身的須要來輕鬆操做使用 Laravel 框架的結構標準。您能夠很是輕鬆地更改文件的位置,以便構建基於模塊化的應用程序。個人大多數項目都附帶了 App / Components
模塊,能夠適用於任何模塊可重用的泛類型的基礎類; App / Eloquent
,Modules
文件夾能夠用於保存 Eloquent 模型和數據庫關係模型,咱們能夠在其中構建任何基於模塊化的功能。 這是我最近開始研究的應用程序的文件夾目錄結構:
我但願每一個人都能從中獲得這個概念,每一個模塊都有本身的需求,而且能夠擁有本身的文件夾/實體/類/方法/屬性。沒有必要將全部模塊標準化徹底相同,由於某些模塊比其餘模塊簡單得多,而且不須要大量的結構設計。此示例顯示AccountChurn
模塊經過 HTTP 文件夾提供 API,同時仍經過控制檯提供 Artisan 命令。另外一方面,AccountOverview
則僅提供 HTTP API,而且依賴倉庫、值對象(bags
)以及服務類(paginators
)來提供更大的數據價值。