转自 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
)来提供更大的数据价值。