adobrovolsky97/laravel-repository-service-pattern

Laravel 5|6|7|8|9|10 - Repository - Service Pattern

v2.1 2024-03-25 14:53 UTC

This package is auto-updated.

Last update: 2024-09-02 08:13:09 UTC


README

Laravel中的仓库服务模式是一种广泛使用的架构模式,它提供了一种结构化的方法来访问和管理应用程序中的数据。它充当应用程序的业务逻辑与底层数据存储机制(如数据库)之间的中介层。

在Laravel中使用仓库服务模式的目的是将应用程序的业务逻辑与数据存储的具体实现细节解耦。通过这样做,它促进了代码的可重用性、可维护性和可测试性。该模式通过定义一组表示数据访问操作和查询的接口或合约来实现这一点。

以下是使用Laravel中仓库服务模式的一些关键目的和好处

  • 抽象和封装:该模式抽象掉了底层的数据存储技术,允许应用程序在不影响业务逻辑的情况下在不同的存储机制(例如,数据库、API)之间切换。这种封装确保了应用程序不会紧密耦合到特定的数据源,增加了其灵活性。

  • 关注点分离:仓库服务模式将数据访问和操作的责任从应用程序的其他代码中分离出来。这种分离通过明确定义每个组件的边界和责任,增强了代码库的可维护性。

  • 可测试性:通过使用仓库作为抽象层,可以更容易地编写针对应用程序业务逻辑的单元测试。在测试期间可以使用模拟实现,允许业务逻辑独立于实际数据存储进行测试。

  • 代码组织和可重用性:该模式促进了与数据访问相关的代码的结构化方法。它为数据操作提供了清晰且一致的API,使开发人员更容易理解和处理数据层。此外,仓库可以在应用程序的不同部分中重用,避免代码重复。

  • 缓存和性能优化:使用仓库服务模式,您可以在仓库级别实现缓存策略。这允许您通过缓存频繁访问的数据来优化性能,减少对底层数据存储的查询次数。

总的来说,Laravel中的仓库服务模式为管理应用程序中的数据访问提供了一种结构化和灵活的方法,有助于更好的代码组织、可维护性和可测试性。

支持软删除功能(通过特质或自定义列)。

支持组合模型键。

安装

Composer

执行以下命令以获取软件包的最新版本

composer require adobrovolsky97/laravel-repository-service-pattern

方法

Adobrovolsky97\LaravelRepositoryServicePattern\Repositories\Contracts\BaseRepositoryInterface

  • with(array $with): self - 定义要附加到查询的预加载关系
  • withCount(array $withCount): self - 定义要附加到查询的预加载关系计数
  • withTrashed(): self - 将已删除的模型添加到查询结果中(如果模型支持软删除)
  • onlyTrashed(): self - 仅返回作为查询结果已删除的模型(如果模型支持软删除)
  • withoutTrashed(): self - 从查询结果中删除已删除的模型(如果模型支持软删除)
  • find(mixed $key): ?Model - 通过主键查找模型
  • findOrFail(mixed $key, ?string $column = null): ?Model - 通过PK或另一个字段查找或失败模型
  • findFirst(array $attributes): ?Model - 根据给定的属性查找第一个模型,例如 [['email', 'test@email.com'], ['anotherProperty', '>=', 'val']]
  • findMany(array $attributes): ?Model - 根据给定的属性查找多个模型,例如 [['email', 'test@email.com'], ['anotherProperty', '>=', 'val']]
  • getAll(array $search = []): Collection - 获取模型集合(并应用过滤器)
  • getAllCursor(array $search = []): LazyCollection - 以游标形式获取模型集合(并应用过滤器)
  • getAllPaginated(array $search = [], int $pageSize): LengthAwarePaginator - 获取带分页的模型集合(并应用过滤器),pageSize 可以通过传递查询参数 page_size 动态更改
  • count(array $search = []): int - 获取符合搜索条件的模型数量
  • create(array $data): ?Model - 创建模型实体
  • insert(array $data): bool - 批量数据插入
  • update(mixed $keyOrModel, array $data): Model - 更新模型实体
  • updateOrCreate(array $attributes, array $data): ?Model - 如果不存在则更新或创建模型
  • delete(mixed $keyOrModel): bool - 删除模型(如果模型支持软删除,则使用 forceDelete)
  • softDelete(mixed $keyOrModel): void - 软删除模型(如果模型支持软删除)
  • restore(mixed $keyOrModel): void - 恢复模型(如果模型支持软删除)

Adobrovolsky97\LaravelRepositoryServicePattern\Repositories\Contracts\BaseCachableRepositoryInterface

此接口支持相同的方法,唯一的区别是它支持缓存模型和集合

Adobrovolsky97\LaravelRepositoryServicePattern\Services\Contracts\BaseCrudServiceInterface

  • with(array $with): self - 定义要附加到查询的预加载关系
  • withCount(array $withCount): self - 定义要附加到查询的预加载关系计数
  • withTrashed(): self - 将已删除的模型添加到查询结果中(如果模型支持软删除)
  • onlyTrashed(): self - 仅返回作为查询结果已删除的模型(如果模型支持软删除)
  • withoutTrashed(): self - 从查询结果中删除已删除的模型(如果模型支持软删除)
  • getAll(array $search = []): Collection - 获取模型集合(并应用过滤器)
  • getAllCursor(array $search = []): LazyCollection - 以游标形式获取模型集合(并应用过滤器)
  • getAllPaginated(array $search = [], int $pageSize): LengthAwarePaginator - 获取带分页的模型集合(并应用过滤器),pageSize 可以通过传递查询参数 page_size 动态更改
  • count(array $search = []): int - 获取符合搜索条件的模型数量
  • find(mixed $key): ?Model - 通过主键查找模型
  • findOrFail(mixed $key, ?string $column = null): ?Model - 通过PK或另一个字段查找或失败模型
  • create(array $data): ?Model - 创建模型实体
  • createMany(array $data): Collection - 创建多个模型
  • insert(array $data): bool - 批量数据插入
  • update(mixed $keyOrModel, array $data): Model - 更新模型实体
  • updateOrCreate(array $attributes, array $data): ?Model - 如果不存在则更新或创建模型
  • delete(mixed $keyOrModel): bool - 删除模型(如果模型支持软删除,则使用 forceDelete)
  • deleteMany(array $keysOrModels): void - 删除模型(如果模型支持软删除,则使用 forceDelete)
  • softDelete(mixed $keyOrModel): void - 软删除模型(如果模型支持软删除)
  • restore(mixed $keyOrModel): void - 恢复模型(如果模型支持软删除)

用法

创建模型

创建你的模型,例如 Post

namespace App;

class Post extends Model {

    protected $fillable = [
        'title',
        'author',
        ...
     ];

     ...
}

创建仓库

namespace App;

use Adobrovolsky97\LaravelRepositoryServicePattern\Repositories\BaseRepository;

class PostRepository extends BaseRepository implements PostRepositoryInterface {

    /**
     * Specify Model class name
     *
     * @return string
     */
    protected function getModelClass(): string
    {
        return Post::class;
    }
}

创建服务

namespace App;

use Adobrovolsky97\LaravelRepositoryServicePattern\Repositories\BaseRepository;

class PostService extends BaseCrudService implements PostServiceInerface {

    /**
     * Specify Repository class name
     *
     * @return string
     */
    protected function getRepositoryClass(): string
    {
        return PostRepositoryInteface::class;
    }
}

在 ServiceProvider 中将服务链接到其合约

class AppServiceProvider extends ServiceProvider {

    /**
     * Specify Repository class name
     *
     * @return string
     */
    public function register(): void
    {
        $this->app->singleton(PostRepositoryInterface::class, PostRepository::class);
        $this->app->singleton(PostServiceInterface::class, PostService::class);
    }
}

现在服务已准备好工作。

使用方法

namespace App\Http\Controllers;

use App\PostServiceInterface;

class PostsController extends Controller {

    /**
     * @var PostServiceInterface
     */
    protected PostServiceInterface $service;

    public function __construct(PostServiceInterface $service) 
    {
        $this->service = $service;
    }
    ....
}

CRUD 控制器动作示例

索引

public function index(SearchRequest $request): AnonymousResourceCollection
{
    return PostResource::collection($this->service->withTrashed()->getAllPaginated($request->validated(), 25));
}

显示

public function show(int $postId): PostResource
{
    return PostResource::make($this->service->findOrFail($postId));
}

存储

public function store(StoreRequest $request): PostResource
{
    return PostResource::make($this->service->create($request->validated()));
}

更新

public function update(Post $post, UpdateRequest $request): PostResource
{
    return PostResource::make($this->service->update($post, $request->validated()));
}

销毁

public function destroy(Post $post): JsonResponse
{
    $this->service->delete($post);
    // Or  
    $this->service->softDelete($post); 
       
    return Response::json(null, 204);
}

恢复

public function restore(Post $deletedPost): PostResource
{
    $this->service->restore($deletedPost);
       
    return PostResource::make($deletedPost->refresh());
}

软删除

要开始使用服务中的软删除,您至少需要在表中添加一个软删除列(deleted_at

此外,您还可以与 SoftDeletes 特性一起使用

默认情况下,软删除列的名称是 deleted_at,您可以通过在仓库中定义变量来覆盖它

protected $deletedAtColumnName = 'custom_deleted_at';

默认情况下,软删除的记录将不包括在查询结果数据中

$posts = $this->service->getAll();
// Those are equivalent
$posts = $this->service->withoutTrashed()->getAll();

仅显示软删除的记录

$posts = $this->service->onlyTrashed()->getAll();

仅显示未软删除的记录

$posts = $this->service->withoutTrashed()->getAll();

加载模型的关联关系

$post = $this->service->with(['author'])->withCount(['readers'])->getAll();

查询结果过滤

默认情况下,过滤将由 applyFilterConditions() 处理,但您可能需要自定义过滤,因此如果您需要自定义过滤,请在您的仓库中重写 applyFilters 方法

class PostRepository extends BaseRepository implements PostRepositoryInterface {
   
   /**
    * Override this method in your repository if you need custom filtering
    * 
    * @param array $searchParams
    * @return Builder
    */
    protected function applyFilters(array $searchParams = []): Builder 
    {
        return $this
            ->getQuery()
            ->when(isset($searchParams['title']), function (Builder $query) use ($searchParams) {
                $query->where('title', 'like', "%{$searchParams['title']}%");
            })
            ->orderBy('id');
    }
}

通过多个字段查找多个模型

$posts = $this->repository->findMany([
    'field' => 'val' // where('field', '=', 'val')
    ['field', 'val'], // where('field', '=', 'val')
    ['field' => 'val'], // where('field', '=', 'val')
    ['field', '=', 'val'], // where('field', '=', 'val')
    ['field', '>', 'val'], // where('field', '>', 'val')
    ['field', 'like', '%val%'], // where('field', 'like', '%val%')
    ['field', 'in', [1,2,3]], // whereIn('field', [1,2,3])
    ['field', 'not_in', [1,2,3]], // whereNotIn('field', [1,2,3])
    ['field', 'null'], // whereNull($field)
    ['field', 'not_null'], // whereNotNull($field)
    ['field', 'date', '2022-01-01'], // whereDate($field, '=', '2022-01-01')
    ['field', 'date <=', '2022-01-01'], // whereDate($field, '<=', '2022-01-01')
    ['field', 'date >=', '2022-01-01'], // whereDate($field, '>=', '2022-01-01')
    ['field', 'day >=', '01'], // whereDay($field, '>=', '01')
    ['field', 'day', '01'], // whereDay($field, '=', '01')
    ['field', 'month', '01'], // whereMonth($field, '=', '01')
    ['field', 'month <', '01'], // whereMonth($field, '<', '01')
    ['field', 'year <', '2022'], // whereYear($field, '<', '2022')
    ['field', 'year', '2022'], // whereYear($field, '=', '2022')
    ['relation', 'has', function($query) {// your query}], // whereHas('relation', function($query) { // your query}})
    ['relation', 'DOESNT_HAVE', function($query) {// your query}], // whereDoesntHave('relation', function($query) { // your query}})
    ['relation', 'HAS_MORPH', function($query) {// your query}], // whereHasMorph('relation', function($query) { // your query}})
    ['relation', 'DOESNT_HAVE_MORPH', function($query) {// your query}], // whereDoesntHaveMorph('relation', function($query) { // your query}})
    ['field', 'between', [1,5]], // whereBetween('field', [1,5])
    ['field', 'NOT_BETWEEN', [1,5]], // whereNotBetween('field', [1,5])
]);

缓存

如果您想对模型应用缓存,则可以将您的实体仓库扩展到 BaseCacheableRepository.php

代码生成器

允许生成类/接口/特性

$template = new ClassTemplate();

$template->setType('class')->setName('ClassName')->setNamespace('Path\\To\\Class');

您可以定义属性、方法、常量、扩展、实现、文档块注释、创建抽象/最终类、设置方法体等...

Eloquent 模型生成功能

命令 php artisan generate:model {table} - 为表生成模型,定义所有关系、属性,定义带有 props 注释的文档块。(如果模型已存在,则不会覆盖模型)

仓库-服务模式文件生成

命令 php artisan generate:repository-service {表名} 生成模型对应的仓库和服务(如果模型已存在则不会覆盖)

请求生成

命令 php artisan generate:request {表名和命名空间} {模型命名空间?} 将生成一个请求实例,

  • {表名和命名空间} 将指定新请求的文件夹(例如,User\\StoreRequest 将创建 App\\Http\\Requests\\User\\StoreReqeust);
  • {模型命名空间?} - 是可选参数。例如,App\\Models\\ModelName 将为模型 fillable 属性生成请求规则;

资源生成

命令 php artisan generate:resource {表名} 根据表名生成特定模型的资源(如果模型已存在则不会覆盖)

API资源控制器生成

命令 php artisan generate:resource-controller {表名} 将根据表名生成特定模型实体的资源控制器(如果模型已存在则不会覆盖)

控制器操作将基于 Repository-Service Laravel 模式包;

CRUD生成

命令 php artisan generate:crud {表名} 将生成上述所有实体(如果模型已存在则不会覆盖)

  • 模型(如果不存在)
  • 仓库接口(如果不存在)
  • 仓库(如果不存在)
  • 服务接口(如果不存在)
  • 服务(如果不存在)
  • 资源(如果不存在)
  • StoreRequest 和 UpdateRequest(如果不存在)
  • API资源控制器(如果不存在)