okaybueno/laravel-repositories

一个提供优雅实现以在 Laravel 中集成 Repository 模式的 Eloquent 的包。

v2.0.1 2020-12-29 12:15 UTC

This package is auto-updated.

Last update: 2024-08-29 04:33:28 UTC


README

本包已停止更新,它将不再接收任何其他更新。如果您正在使用它,请考虑迁移到其他解决方案——或者将其分支并依赖您自己的包版本。

在 Laravel 上使用 Repository 模式不再令人愉悦。Laravel 在很多方面都有很大的进步,使我能够充分利用 Eloquent。比较 Eloquent 提供的默认功能和通过 Repository 模式实现的实现,我发现添加这个中间层最终只会带来更多需要维护的层,而这对最终产品或开发过程没有任何价值。我错了,我很高兴能回到完全使用 Eloquent。

Laravel Repositories

一个提供将 Repository 模式与 Laravel & Eloquent 集成的优雅实现的包。

Latest Version on Packagist Software License Quality Score Total Downloads

免责声明

此包最初发布于 此处,但由于该包的未来尚不明确,它已被分支并在此存储库中重新工作。

目标

与仓库一起工作可以提供一种很好的方法来解耦代码,同时分离关注点,隔离事物,以及分离和组合职责。大多数时候,我们将在数据库表上执行一些非常通用的操作,如创建、更新、过滤或删除。

然而,使用仓库并不总是好主意,尤其是与 Laravel 和其 ORM,Eloquent 一起使用,因为它有时会迫使你放弃一些很好的功能,以换取更好的架构(这取决于情况)。对于某些项目来说,这可能是一种过度设计,因此是否需要这种复杂程度取决于开发者。

本包旨在在 Laravel 的 Eloquent ORM 上实现 Repository 模式时提供一个模板。它提供的方式是使用 RepositoryInterface 和一个基本的 Repository 实现,该实现能够与 Eloquent 模型一起工作,提供覆盖 85% 的数据库操作的基本方法,这些操作你可能会在应用程序中执行。

安装

  1. 通过将其添加到 composer.json 或在项目文件夹中运行 composer require okaybueno/laravel-repositories 来安装此包。
  2. 对于 Laravel 5.5,服务提供程序会自动注册,但如果您使用 Laravel 5.4,则必须将提供程序添加到您的 config/app.php 文件中:OkayBueno\Repositories\RepositoryServiceProvider::class
  3. 通过运行 php artisan vendor:publish --provider="OkayBueno\Repositories\RepositoryServiceProvider" 发布配置文件。
  4. 打开配置文件(config/repositories.php),并根据您的需求配置路径。
  5. 准备就绪!

用法

要开始使用此包,您只需要创建一个文件夹,其中将放置所有您的仓库接口和仓库实现,并从包提供的 EloquentRepository 类扩展每个仓库。

您创建的仓库要处理的 Eloquent 模型也必须通过 Repo 构造函数注入。

然后,该包将尝试加载所有仓库接口,并根据 config/repositories.php 文件中指定的参数将它们绑定到仓库实现。

示例

注意:尽管该包包含一个生成器,但请仔细阅读所有文档,因为有些东西可能看起来就像“魔法”。

让我们假设我们的所有仓库都将位于“app”文件夹下的一个名为“MyWebbApp”的文件夹中,该文件夹位于“Repositories”文件夹中:app/MyWebApp/Repositories。

在这个文件夹的根目录下,我们将拥有所有遵循以下命名约定的接口:[RepositoryName]Interface.php

注意:只要我们使用“Interface”作为后缀,我们使用的名称并不重要。这是很重要的,因为自动绑定器将尝试找到所有匹配此模式的文件。

在这个Repositories文件夹内部,我们必须还有一个名为Eloquent的文件夹,它将包含所有仓库的实现,遵循以下命名约定:[RepositoryName].php

我们的结构应该是这样的

+-- app
|   +-- MyApp
|       +-- Repositories
|           +-- UserRepositoryInterface.php
|           +-- RoleRepositoryInterface.php
|           +-- CommentRepositoryInterface.php
|           +-- PostRepositoryInterface.php
|           +-- Eloquent
|               +-- UserRepository.php
|               +-- RoleRepository.php
|               +-- CommentRepository.php
|               +-- PostRepository.php  

让我们看看UserRepositoryInterface.phpUserRepository.php会有什么。

<?php

namespace MyApp\Repositories;

use OkayBueno\Repositories\RepositoryInterface;

interface UserRepositoryInterface extends RepositoryInterface {

    // here you would write the contract for methods that your repository will implement.
}
<?php

namespace MyApp\Repositories\Eloquent;

use OkayBueno\Repositories\src\EloquentRepository;
use MyApp\Repositories\UserRepositoryInterface;
use MyApp\Models\User;

class UserRepository extends EloquentRepository implements UserRepositoryInterface  {

    public function __construct( User $user ) {
        parent::__construct( $user );
    }

    // methods that your repository should implement...
}

现在我们需要配置config/repositories.php文件以匹配我们的路径和命名空间

    'repository_interfaces_namespace' => 'MyApp\Repositories',

    'criterias_namespace' => 'MyApp\Repositories\Criteria',

    'repositories_path' => app_path('MyApp/Repositories'),

    'criterias_path' => app_path('MyApp/Repositories/Criteria'),

文件中所有的参数都有适当的解释。

现在,仓库已经准备好被使用,并可以注入到其他服务或控制器中

<?php

namespace MyApp\Services\Users;

use MyApp\Repositories\UserRepositoryInterface;

class UsersService implements UserServicesInterface  {

    protected $usersRepository;

    public function __construct( UserRepositoryInterface $usersRepositoryInterface ) {
        $this->usersRepository = $usersRepositoryInterface;
    }

    // other methods in your service.
}

注意:此示例假定您已将composer.json配置为自动加载app/MyApp中的文件,并使用MyApp命名空间。

默认提供的方法。

仓库包默认提供一系列方法。这些是

    /**
     * Finds one item by the provided field.
     *
     * @param $value mixed Value used for the filter. If NULL passed then it will take ONLY the criteria.
     * @param string $field Field on the database that you will filter by. Default: id.
     * @param array $columns Columns to retrieve with the object.
     * @return mixed Model|NULL An Eloquent object when there is a result, NULL when there are no matches.
     */
    public function findOneBy( $value = NULL, $field = 'id', array $columns = ['*'] );

    /**
     * Finds ALL items the repository abstract without any kind of filter.
     *
     * @param array $columns Columns to retrieve with the objects.
     * @return mixed Collection Laravel Eloquent's Collection that may or may not be empty.
     */
    public function findAll( array $columns = ['*'] );

    /**
     * Finds ALL items by the provided field. If NULL specified for the first 2 parameters, then it will take ONLY the
     * criteria.
     *
     * @param $value mixed Value used for the filter.
     * @param string $field Field on the database that you will filter by. Default: id.
     * @param array $columns Columns to retrieve with the objects.
     * @return mixed Collection Laravel Eloquent's Collection that may or may not be empty.
     */
    public function findAllBy( $value = NULL, $field = NULL, array $columns = ['*'] );

    /**
     * Finds ALL the items in the repository where the given field is inside the given values.
     *
     * @param array $value mixed Array of values used for the filter.
     * @param string $field Field on the database that you will filter by.
     * @param array $columns Columns to retrieve with the objects.
     * @return mixed Collection Laravel Eloquent's Collection that may or may not be empty.
     */
    public function findAllWhereIn( array $value, $field,  array $columns = ['*'] );

    /**
     * Allows you to eager-load entity relationships when retrieving entities, either with or without criterias.
     *
     * @param array|string $relations Relations to eager-load along with the entities.
     * @return mixed The current repository object instance.
     */
    public function with( $relations );

    /**
     * Adds a criteria to the query.
     *
     * @param CriteriaInterface $criteria Object that declares and implements the criteria used.
     * @return mixed The current repository object instance.
     */
    public function addCriteria( CriteriaInterface $criteria );

    /**
     * Skips the current criteria (all of them). Useful when you don't want to reset the object but just not use the
     * filters applied so far.
     *
     * @param bool|TRUE $status If you want to skip the criteria or not.
     * @return mixed The current repository object instance.
     */
    public function skipCriteria( $status = TRUE );

    /**
     * Returns a Paginator that based on the criteria or filters given.
     *
     * @param int $perPage Number of results to return per page.
     * @param array $columns Columns to retrieve with the objects.
     * @return Paginator object with the results and the paginator.
     */
    public function paginate( $perPage, array $columns = ['*'] );

    /**
     * Allows you to set the current page with using the paginator. Useful when you want to overwrite the $_GET['page']
     * parameter and retrieve a specific page directly without using HTTP.
     *
     * @param int $page The page you want to retrieve.
     * @return mixed The current repository object instance.
     */
    public function setCurrentPage( $page );

    /**
     * Creates a new entity of the entity type the repository handles, given certain data.
     *
     * @param array $data Data the entity will have.
     * @return mixed Model|NULL An Eloquent object when the entity was created, NULL in case of error.
     */
    public function create( array $data );

    /**
     * Updates as many entities as the filter matches with the given $data.
     *
     * @param array $data Fields & new values to be updated on the entity/entities.
     * @param $value mixed Value used for the filter.
     * @param string $field Field on the database that you will filter by. Default: id.
     * @return mixed Model|NULL|integer An Eloquent object representing the updated entity, a number of entities updated if mass updating,
     * or NULL in case of error.
     */
    public function updateBy( array $data, $value = NULL, $field = 'id' );

    /**
     * Removes as many entities as the filter matches. If softdelete is applied, then they will be soft-deleted.
     * Criteria is applied as well, so please be careful with it.
     *
     * @param $value
     * @param $value mixed Value used for the filter.
     * @param string $field Field on the database that you will filter by. Default: id.
     * @return boolean TRUE It will always return TRUE.
     */
    public function delete( $value = NULL, $field = 'id' );

    /**
     * @return int number of records matching the criteria (or total amount of records).
     */
    public function count();

    /**
     * Resets the current scope of the repository. That is: clean the criteria, and all other properties that could have
     * been modified, like current page, etc.
     *
     * @return mixed The current repository object instance.
     */
    public function resetScope();

    /**
     * Permanently removes a record (or set of records) from the database.
     * Criteria is applied as well, so please be careful with it.
     *
     * @param $value mixed Value used for the filter.
     * @param string $field Field on the database that you will filter by.
     * @return mixed
     */
    public function destroy( $value = NULL, $field = 'id' );

标准

为了避免我们的仓库中充满了像findActiveUsers()findActiveUsersOlderThan($date)之类的查询方法,我们将使用标准来应用搜索或查询的过滤器。

标准只是一个实现该包提供的CriteriaInterface的PHP类,它通过Eloquent模型来应用我们想要为特定搜索应用的查询或查询集合。

要创建自己的标准,请将它们放置在您想要的任何位置,并使它们实现提供的CriteriaInterface。

例如:假设我们有一个应用程序,用户可以通过Facebook、Twitter或电子邮件进行注册。我们需要根据注册方法检索所有用户。我们会有一个这样的标准

<?php

namespace MyApp\Repositories\Criteria\Eloquent\Users;

use OkayBueno\Repositories\Criteria\CriteriaInterface;
use MyApp\Models\User;

class RegisteredVia implements CriteriaInterface  {

    protected $registeredVia;
    protected $onlyActive;

    public function __construct( $registeredVia, $onlyActive = TRUE ) {

        $this->registeredVia = $registeredVia;
        $this->onlyActive = $onlyActive;

    }


    public function apply( $queryBuilder ) {

        if ( $this->onlyActive ) $queryBuilder = $queryBuilder->where( 'active', TRUE );

        return $queryBuilder->where( 'registered_via', $this->registered_via );

    }

}

现在,在您的服务或控制器中,您可以使用这个标准如下

    $registeredViaFacebookCriteria = new RegisteredVia( 'facebook' );

    return $this->userRepository->addCriteria( $registeredViaFacebookCriteria )->findAllBy();

我们甚至可以连接不同的标准

    $registeredViaFacebookCriteria = new RegisteredVia( 'facebook' );
    $orderByCreationDate = new OrderBy( 'created_at', 'ASC' );

    return $this->userRepository
                ->addCriteria( $registeredViaFacebookCriteria )
                ->addCriteria( $orderByCreationDate )
                ->findAllBy();

默认提供的标准

  • OrderBy($orderBy, $direction = 'ASC'):根据给定的列和方向排序结果。
  • FilterByColumns(array $filter):通过多个列进行过滤(AND过滤)。应用过的过滤器可能很复杂。示例
    $maxDate = Carbon::now()->subDays( 7 );
    $filter = [
        [ 'balance', '>=', 10 ],
        [ 'created_at', '<=', $date ],
        [ 'is_premium_user', TRUE ]
    ];

    $filterCriteria = new FilterByColumns( $filter );

    // This will return all premium users created more than 1 week ago and that have more than 10 (€,$, whatever) of balance.
    $users = $this->userRepository->addCriteria( $filterCriteria )->findAllBy();

对于更复杂的查询,需要创建自定义标准。

现在,让我们直接跳到那些可以节省您时间的好东西...

生成器!

是的,创建所有这些文件,将它们链接起来,注入模型等等,真的很麻烦。对我也是如此。所以我创建了一个生成器,它可以创建模型接口和Eloquent实现(基本)文件。酷吧?

提供了两个生成器:用于仓库和用于标准。

仓库

只需执行php artisan make:repository Namespace\\ModelName --implementation=eloquent

其中ModelName是要为该仓库生成仓库的模型(类)的名称。这将生成一个用于此仓库的接口,一个实现该接口的Eloquent实现,并且还会将模型注入到仓库中。文件将放置在您在repositories.php配置文件中配置的目录中,并且还将有在那里指定的命名空间。您可以指定您想要使用的实现,尽管目前仅支持Eloquent。

标准

执行 php artisan make:criteria Namespace\\ClassName --implementation=eloquent

这将生成一个标准,使用你在 repositories.php 配置文件中配置的参数。另外,如果你想将标准放在特定的文件夹中(在已配置的文件夹内),你可以在命令中指定: php artisan make:criteria Accounts\\FilterByCreationDate

试一试,看看它们传递不同参数时的表现!

生成器做了很多魔法,但它们在错误描述上也很详细,所以如果失败了,请注意错误;D。

特质

一些函数包含在内,不一定需要在所有仓库中都包含,但它们依赖于注入的模型。这些是不同类型的实体可用的特质

仓库

  • 可恢复的:如果你在模型中使用软删除,并且想要通过仓库恢复它们,只需在你的仓库中包含这个特质。你还需要在仓库和仓库接口中实现 可恢复接口
  • 可计数的:如果你有任何充当计数器的列,你可以使用这个特质为你的仓库添加计数功能(在给定数量中增加/减少值)。你还需要在仓库和仓库接口中实现 可计数接口

扩展包

你可以创建自己的实现来支持,例如,SQLite或MongoDB。计划是添加更多引擎,但如果你需要自己的东西,你可以创建自己的引擎。

要添加一个新引擎,其类需要实现 RepositoryInterface 接口以及其中定义的所有方法。你可以将此类放在任何位置,因为绑定是在配置文件(config/repositories.php)中手动完成的

    'supported_implementations' =>  [
            'eloquent' => \OkayBueno\Repositories\src\EloquentRepository::class,
            'mongodb' => \MyApp\Repositories\Custom\MongoDbRepository::Class
        ]

你还可以手动指定应该由该引擎映射的仓库。所有未明确映射的仓库都将映射到默认驱动程序(eloquent)

    'bindings' => [
            'eloquent' => 'default',
            'mongodb' => [
                 'LogRepositoryInterface',
                 'EventTrackerRepositoryInterface'
            ]
        ],

当然,你可以使用生成器创建标准和仓库

    php artisan make:repository MyApp\\Models\\Log --implementation=mongodb

    php artisan make:criteria Events\\FilterLogsOlderThanAWeek --implementation=mongodb

变更日志

v1.0.7
  • 添加了新的基本标准(OrderByRandom,WhereIn,WhereNotIn,WhereRelationFieldIn,With)。重新设计了 with() 方法。
v1.0.6
  • EloquentRepository 中的私有方法现在变为受保护的,以允许覆盖它们。
v1.0.5
  • 修复了使用 with() 预加载关系时的错误。
v1.0.4
  • 修复了在 Laravel 5.5 上同时加载不同引擎时的错误。
v1.0.3
  • 修复了 Laravel 5.5 上的生成器错误。
v1.0.2
  • 修复了在 Laravel 5.5 上调用 updateBy() 时的错误。
v1.0.1
  • 修复了一个小错误,这使得自动加载变得很痛苦。
v1.0.0
  • 发布第一个公开官方版本。从主包继承了基本的仓库功能。

致谢

错误和贡献

  • 发现了错误?那很好(也很糟糕)。请使用 Github 上的问题告诉我。
  • 需要功能或有一些有趣的内容要贡献?太好了!打开一个 pull request。

待办事项

  • 自动化测试:尽管这个包已经过大量测试(甚至在生产环境中),但没有自动化测试。

许可证

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