royvoetman/laravel-repository-pattern

Eloquent模型中间件

v2.0.4 2023-06-17 07:35 UTC

This package is auto-updated.

Last update: 2024-09-07 18:34:58 UTC


README

Eloquent模型中间件

Latest Version MIT Licensed Total Downloads

目录

介绍

此包提供了一个方便的机制来分组数据操作逻辑,这与Laravel原生HTTP中间件等效。然而,为了避免与HTTP中间件混淆,此处之后的机制将以其更通用的形式称为“管道”。实际上,Laravel提供了自己的管道实现,这是底层中间件使用的。

管道是一种设计模式,它组合了几个不同的类(管道)并按顺序应用它们。所有管道都接收一个所谓的可传递对象,并产生一个所谓的可返回对象。在HTTP中间件的上下文中,可传递对象是HTTP请求对象,可返回对象是HTTP响应对象。相反,在仓库的上下文中,模型数据数组被归类为可传递对象,而最终的Eloquent模型对象是可返回对象。通常,每个管道将过滤或更改通过管道发送的可传递对象。因此,创建一个易于扩展的架构,其中每个管道只关注一项任务。例如,此包提供了一个在将密码保存到数据库之前自动对密码进行散列的管道。这样,集中管理密码散列逻辑,从而减轻其他类的责任。

可以编写额外的管道来执行除修改列值以外的各种任务。一个翻译管道可能会将某些列的所有翻译保存到一个单独的翻译表中。一个事务管道可能会在数据库事务中运行特定的查询组。此包已包含一些管道,包括密码散列和数据库事务的管道。所有这些默认管道都将详细说明,以及如何定义自己的管道。

安装

composer require royvoetman/laravel-repository-pattern

仓库

定义仓库

首先,创建一个继承自RoyVoetman\Repositories\Repository类的类。其次,仓库应该通过将$model字段设置为模型的完全限定类名来了解它关联的模型。最后,可以通过在$pipes字段中声明来定义管道。

class BooksRepository extends Repository
{
    protected string $model = Book::class;

    protected array $pipes = [
        'save' => [Translate::class],
        'delete' => [DeleteTranslations::class]
    ];
}

生成命令

php artisan make:repository BooksRepository

插入和更新模型

save(array $data, Model $model = null): ?Model

插入

要创建新的数据库记录,实例化关联的仓库,传递模型属性作为关联数组,然后调用save方法。

$book = (new BooksRepository())->save([
  'name' => 'Laravel',
  'author' => 'Taylor Otwell'
]);

更新

save方法也可以用于更新数据库中已存在的模型。要更新模型,应检索它,将您想要更新的任何模型属性与检索到的模型结合,然后调用save方法。

$book = Book::find(1);

$updatedBook = (new BooksRepository())->save([
  'name' => 'Laravel!',
  'author' => 'Taylor Otwell'
], $book);

删除模型

delete(Model $model): bool

要删除模型,检索模型,将模型传递给仓库,然后调用delete方法。

$book = Book::find(1);

(new BooksRepository())->delete($book);

管道

定义管道

要创建新的管道,使用make:pipe生成命令

php artisan make:pipe HashPassword

此命令将在您的app/Repositories/Pipes目录中放置一个新的HashPassword类。在这个管道中,我们将检查是否定义了密码键。如果是,将散列密码并用纯文本密码替换它。

class HashPassword
{
    public function handle($data, Closure $next): Model
    {
        if(Arr::has($data, 'password')) {
            $data['password'] = bcrypt($data['password']);
        }

        return $next($data);
    }
}

然而,应用于删除操作的管道将接受一个Eloquent模型作为第一个参数,而不是包含模型数据的$data数组。要创建一个删除管道,请将--delete选项添加到生成器命令中。

php artisan make:pipe RemoveBookRelations --delete
class RemoveBookRelations
{
    public function handle(Model $book, Closure $next)
    {
        $book->author()->delete();
        $book->reviews()->delete();

        return $next($book);
    }
}

管道前后

管道是在模型的插入/更新/删除之前还是之后运行取决于管道本身。例如,以下管道将在任何数据操作被持久化之前执行某些任务

class BeforePipe
{
    public function handle($data, Closure $next)
    {
        // Perform actions on the model-data
        // e.g. hashing passwords

        return $next($data);
    }
}

然而,这个管道将在数据操作被持久化之后执行其任务

class AfterPipe
{
    public function handle($data, Closure $next): Model
    {
        $model = $next($data);

        // Perform actions on Eloquent model
        // e.g. saving relationships

        return $model;
    }
}

使用管道

该包定义了以下操作,这些操作可以在仓库的$pipes数组中使用。当发生相应的操作时,这些数组将自动应用

class BooksRepository extends Repository
{
    protected array $pipes = [
        'create' => [...],
        'update' => [...],
        'save' => [...],
        'delete' => [...]
    ];
    
    ...
}

或者可以通过使用with方法在运行时定义管道。

(new BooksRepository())->with(Translate::class)->create([
    ...
]);

管道组

有时您可能想要将几个管道分组在单个键下,以便更容易应用。您可以使用仓库中的$pipeGroups字段来实现这一点。例如,当保存VIP用户而不是普通用户时,您可能想要应用特殊的逻辑

class UsersRepository extends Repository
{
    protected string $model = Book::class;

    protected array $pipeGroups = [
        'vip' => [
            AddVipPermissions::class,
            EnrollToVipChannel::class
        ]
    ];
}

然后可以通过调用withGroup方法来应用组。

$user = (new UsersRepository())->withGroup('vip')->save([
  'name' => 'Roy Voetman',
  'email' => 'info@example.com',
  ...
]);

事务

此包提供了一个事务管道,可用于在数据库事务中运行特定管道。例如,仓库可以实现UsesTransaction接口,以指示每个管道都应该在事务中运行。

class BooksRepository extends Repository implements UsesTransaction
{
    protected string $model = Book::class;
}

通过实现UsesTransaction,插入记录或保存翻译时引发的异常不会导致数据不一致。事实上,当抛出异常时,事务将被回滚。

此外,可以使用transaction()方法指定仅当前管道应在事务内运行。

$book = $books->transaction()->save([
  'name' => 'Laravel',
  'author' => 'Taylor Otwell'
]);

注意:可以通过将其添加到$pipes字段来使用Transaction管道。然而,由于管道是连续运行的,它应该是数组的第一个管道。上述技术会自动将管道预置于管道堆栈的开头。

管道参数

管道还可以接收额外的参数。例如,如果您想使用特定重试次数的事务管道,可以将表示重试次数的整数作为附加参数传递。

为了澄清,事务管道的定义如下

class Transaction
{
    public function handle($passable, \Closure $next, int $attempts)
    {
        return DB::transaction(fn () => $next($passable), $attempts);
    }
}

在定义$pipes数组时,可以通过在类名和参数之间使用冒号来指定管道参数。多个参数应由逗号分隔

class BooksRepository extends Repository
{
    protected array $pipes = [
        'save' => [
            '\RoyVoetman\Repositories\Pipes\Transaction:3'
        ],
    ];
    
    ...
}

然而,也可以通过在仓库上使用with方法在运行时定义管道参数。

(new BooksRepository())
    ->with('\RoyVoetman\Repositories\Pipes\Transaction:3')
    ->create([
        ...
    ]
);

变更日志

有关最近更改的更多信息,请参阅变更日志

贡献

欢迎贡献,并将得到完全的认可。我们接受通过Github的拉取请求进行的贡献。

拉取请求

  • PSR-2编码标准 - 应用约定最简单的方法是安装PHP Code Sniffer
  • 记录任何行为变更 - 确保将README.md和任何其他相关文档保持最新。
  • 创建功能分支 - 不要要求我们从您的master分支拉取。
  • 每个功能一个拉取请求 - 如果您想做更多的事情,请发送多个拉取请求。

许可

MIT许可(MIT)。有关更多信息,请参阅许可文件