存储库可以被定义为领域和数据映射层之间的一个抽象层,它通过一个类似于集合的接口来访问领域对象,在两者之间提供一个调解的途径。
现代PHP框架,如Laravel和Symfony,通过对象关系映射器(ORM)与数据库交互;Symfony使用Doctrine作为其默认的ORM,Laravel使用Eloquent。
两者在数据库互动方面采取了不同的方法。在Eloquent中,模型是为每个数据库表生成的,形成互动的基础。而Doctrine则使用Repository模式,每个Entity都有一个相应的repository,包含与数据库交互的辅助函数。虽然Laravel没有提供开箱即用的功能,但在Laravel项目中使用Repository模式也是可能的。
存储库模式的一个关键好处是,它允许我们使用依赖性反转原则(或代码的抽象,而不是具体的)。这使得我们的代码对变化更加稳健,例如,如果后来决定切换到一个Eloquent不支持的数据源。
这也有助于保持代码的条理性和避免重复,因为数据库相关的逻辑被保存在一个地方。虽然这种好处在小项目中不会立即显现出来,但在需要维护多年的大型项目中会变得更加明显。
在这篇文章中, 我将向你展示如何在你的Laravel应用程序中实现Repository模式.为了做到这一点, 我们将建立一个API来管理一个公司从客户那里收到的订单.
开始使用
创建一个新的Laravel项目并使用以下命令cd到目录中:
laravel new order_api
cd order_api
设置数据库
在本教程中, 我们将使用MySQL作为我们的数据库.要做到这一点, 在*.env*文件中, 更新数据库相关的参数,如下图所示:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=order_api
DB_USERNAME=<YOUR_DATABASE_USERNAME>
DB_PASSWORD=<YOUR_DATABASE_PASSWORD>
最后,使用你喜欢的数据库管理程序,创建一个名为order_api 的新数据库。
生成数据库的初始数据
我们正在建立一个订单管理应用程序,所以让我们通过运行以下命令为它创建模型:
php artisan make:model Order -a
-a 参数让Artisan知道我们要为订单模型创建一个迁移文件、播种机、工厂和控制器。
上面的命令将创建五个新文件:
- 一个控制器,位于app/Http/Controllers/OrderController.php。
- 在数据库/factories/orderFactory.php中创建一个数据库工厂
- 一个迁移文件,位于数据库/migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php
- 一个位于app/Models/Order.php中的模型
- 在 数据库/seeders/OrderSeeder.php中的一个播种器文件和
在数据库/migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php中,更新up 函数,使其与以下内容相匹配:
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->text('details');
$table->string('client');
$table->boolean('is_fulfilled')->default(false);
$table->timestamps();
});
}
按照迁移文件中的规定,order 表将有以下列:
- 一个ID。这将是该表的主键。
- 订单的详细信息。
- 下订单的客户的名字。
- 订单是否已经完成。
- 当订单被创建和更新时,
created_at和updated_at,由timestamps函数提供。
接下来,让我们更新OrderFactory ,以便它能生成一个假的订单来作为数据库的种子。在database/factories/OrderFactory.php中,更新definition 函数,使之与以下内容相匹配:
public function definition()
{
return [
'details' => $this->faker->sentences(4, true),
'client' => $this->faker->name(),
'is_fulfilled' => $this->faker->boolean(),
];
}
接下来,打开数据库/seeders/OrderSeeder.php,更新run 函数,以匹配以下内容:
public function run()
{
Order::factory()->times(50)->create();
}
这使用OrderFactory ,在数据库中创建50个订单。
不要忘记添加这个导入。
use App\Models\Order;
在src/database/seeders/DatabaseSeeder.php 中,在run 函数中添加以下内容:
$this->call(
[
OrderSeeder::class
]
);
当Artisan的db:seed 命令被运行时,这将运行QuoteSeeder 。
最后,运行你的迁移,用下面的命令对数据库进行播种:
php artisan migrate --seed
如果你打开订单表,你会看到新播种的订单:

创建存储库
在我们为Order 模型创建存储库之前,让我们定义一个接口来指定存储库必须声明的所有方法。我们的控制器(以及我们将来可能建立的任何订单组件)将依赖于接口,而不是直接依赖存储库类。
这使得我们的代码很灵活,因为如果将来有必要做出改变,控制器仍然不受影响。例如,如果我们决定将订单管理外包给第三方应用程序,我们可以建立一个符合OrderRepositoryInterface's signature的新模块,并交换绑定声明,我们的控制器将完全按照预期工作--不需要触及控制器中的任何一行代码。
在应用程序目录下,创建一个名为Interfaces的新文件夹。然后,在Interfaces中,创建一个名为OrderRepositoryInterface.php的新文件,并在其中添加以下代码:
<?php
namespace App\Interfaces;
interface OrderRepositoryInterface
{
public function getAllOrders();
public function getOrderById($orderId);
public function deleteOrder($orderId);
public function createOrder(array $orderDetails);
public function updateOrder($orderId, array $newDetails);
public function getFulfilledOrders();
}
接下来,在app文件夹中,创建一个新的文件夹,称为Repositories。在这个文件夹中,创建一个名为OrderRepository.php的新文件并添加以下代码:
<?php
namespace App\Repositories;
use App\Interfaces\OrderRepositoryInterface;
use App\Models\Order;
class OrderRepository implements OrderRepositoryInterface
{
public function getAllOrders()
{
return Order::all();
}
public function getOrderById($orderId)
{
return Order::findOrFail($orderId);
}
public function deleteOrder($orderId)
{
Order::destroy($orderId);
}
public function createOrder(array $orderDetails)
{
return Order::create($orderDetails);
}
public function updateOrder($orderId, array $newDetails)
{
return Order::whereId($orderId)->update($newDetails);
}
public function getFulfilledOrders()
{
return Order::where('is_fulfilled', true);
}
}
除了接口提供的灵活性外,以这种方式封装查询还有一个好处,即我们不必在整个应用程序中重复查询。
如果将来我们决定在getAllOrders() 函数中只检索未完成的订单,我们只需要在一个地方进行修改,而不是追踪所有声明Order::all() 的地方,同时冒着错过一些的风险。
创建控制器
有了我们的资源库,让我们为我们的控制器添加一些代码。打开app/Http/Controllers/OrderController.php,更新代码,使之与以下内容一致:
<?php
namespace App\Http\Controllers;
use App\Interfaces\OrderRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class OrderController extends Controller
{
private OrderRepositoryInterface $orderRepository;
public function __construct(OrderRepositoryInterface $orderRepository)
{
$this->orderRepository = $orderRepository;
}
public function index(): JsonResponse
{
return response()->json([
'data' => $this->orderRepository->getAllOrders()
]);
}
public function store(Request $request): JsonResponse
{
$orderDetails = $request->only([
'client',
'details'
]);
return response()->json(
[
'data' => $this->orderRepository->createOrder($orderDetails)
],
Response::HTTP_CREATED
);
}
public function show(Request $request): JsonResponse
{
$orderId = $request->route('id');
return response()->json([
'data' => $this->orderRepository->getOrderById($orderId)
]);
}
public function update(Request $request): JsonResponse
{
$orderId = $request->route('id');
$orderDetails = $request->only([
'client',
'details'
]);
return response()->json([
'data' => $this->orderRepository->updateOrder($orderId, $orderDetails)
]);
}
public function destroy(Request $request): JsonResponse
{
$orderId = $request->route('id');
$this->orderRepository->deleteOrder($orderId);
return response()->json(null, Response::HTTP_NO_CONTENT);
}
}
该代码通过构造函数注入一个OrderRepositoryInterface 实例,并在每个控制器方法中使用相关对象的方法。
首先,在index() 方法中,它调用orderRepository 中定义的getAllOrders() 方法来检索订单列表并返回JSON格式的响应。
接下来,store() 方法调用orderRepository 中的createOrder()方法来创建一个新订单。这将需要创建的订单的细节作为一个数组,之后返回一个成功的响应。
在控制器的show() 方法中,它从路由中检索唯一的订单Id ,并将其作为一个参数传递给getOrderById() 。这将从数据库中获取具有匹配Id的订单的详细信息,并返回JSON格式的响应。
然后,为了更新一个已经创建的订单的细节,它调用资源库中的updateOrder() 方法。这需要两个参数:订单的唯一ID和需要更新的细节。
最后,destroy() 方法从路由中检索一个特定订单的唯一ID,并从存储库中调用deleteOrder() 方法来删除它。
添加路由
为了将控制器中定义的每个方法映射到特定的路由,请在rouse/api.php中添加以下代码:
Route::get('orders', [OrderController::class, 'index']);
Route::get('orders/{id}', [OrderController::class, 'show']);
Route::post('orders', [OrderController::class, 'store']);
Route::put('orders/{id}', [OrderController::class, 'update']);
Route::delete('orders/{id}', [OrderController::class, 'delete']);
记住要包括import 语句的OrderController:
use App\Http\Controllers\OrderController;
绑定接口和实现
我们需要做的最后一件事是在Laravel的服务容器中把OrderRepository 和OrderRepositoryInterface 绑定起来; 我们通过一个服务提供者来实现。使用以下命令创建一个:
php artisan make:provider RepositoryServiceProvider
打开app/Providers/RepositoryServiceProvider.php,并更新register ,使其与以下内容一致:
public function register()
{
$this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
}
记住要为OrderRepository 和OrderRepositoryInterface 包括import 语句:
use App\Interfaces\OrderRepositoryInterface;
use App\Repositories\OrderRepository;
最后,在config/app.php中把新的服务提供者添加到providers 数组中:
'providers' => [
// ...other declared providers
App\Providers\RepositoryServiceProvider::class,
];
测试应用程序
使用下面的命令运行该应用程序:
php artisan serve
默认情况下,被服务的应用程序将在http://127.0.0.1:8000/。使用Postman或cURL,我们可以向我们新创建的API发出请求。
运行下面的命令,使用cURL测试/api/orders 端点:
curl --silent http://localhost:8000/api/orders | jq
使用jq将响应格式化为JSON。
你会在终端看到类似于下面这个例子的JSON输出,为了帮助阅读,这个输出被截断了:
{
"data": [
{
"id": 1,
"details": "Sit ullam cupiditate dolorem in. Magnam suscipit eaque occaecati facilis amet illum. Dolor perspiciatis velit laboriosam. Enim fugiat excepturi qui natus incidunt dolorem debitis ut.",
"client": "Cydney Conn V",
"is_fulfilled": 0,
"created_at": "2021-09-09T09:18:28.000000Z",
"updated_at": "2021-09-09T09:18:28.000000Z"
},
{
"id": 2,
"details": "Eum iste eum molestiae est. Voluptatibus veritatis earum commodi. Quod et laboriosam ratione dolor adipisci. Nam et debitis nobis ea sit.",
"client": "Willow Herzog",
"is_fulfilled": 1,
"created_at": "2021-09-09T09:18:28.000000Z",
"updated_at": "2021-09-09T09:18:28.000000Z"
},
{
"id": 3,
"details": "At maxime architecto repellat quidem id. Saepe provident quo eos officiis et tenetur. Et expedita maxime atque. Et consequuntur sequi aperiam possimus odio est ab.",
"client": "Mr. Peyton Nolan DVM",
"is_fulfilled": 1,
"created_at": "2021-09-09T09:18:28.000000Z",
"updated_at": "2021-09-09T09:18:28.000000Z"
}
]
}
这就是如何在Laravel应用程序中使用存储库模式的方法
在这篇文章中, 我们了解了Repository模式以及如何在Laravel应用程序中使用它.我们也看到了它为大型项目提供的一些好处 - 其中之一是松散耦合的代码,我们是在为抽象的代码编码,而不是具体实现。
不过,我最后还是要提醒一下。对于小项目来说,这种方法会让人感觉到大量的工作和模板化的代码,而这些回报可能不会立即显现。因此,在采用这种方法之前,你必须适当考虑项目的规模。
本教程的整个代码库可在GitHub上找到。请自由地进一步探索。编码愉快!