サジャッズディー/laravel-repository

Laravel Eloquentモデルのためのリポジトリパターンを使用する高度なツール。

1.0.9 2024-08-24 12:59 UTC

This package is auto-updated.

Last update: 2024-09-24 13:09:47 UTC


README

Advanced Laravel Repository

高度なLaravelリポジトリ

高度なヘルパーがLaravelにリポジトリパターンを実装し、堅牢なツールセットを提供して、Eloquentモデルを強化します。データベースレイヤーの複雑さを抽象化し、データベースクエリロジックを標準化および再利用することを目的としています。

機能

  • モデルアノニミティ:どんなEloquentモデルにも実装できます。
  • メソッドフォワーディング:モデルやクエリビルダーに対して動的なメソッド呼び出しを可能にします。
  • 自動クエリスコープ:パラメータに依存しないスコープメソッドをリポジトリで定義できます。
  • 柔軟な検索:モデルの属性を簡単に検索できます。
  • 高度なフィルタリング:範囲やパターンマッチングを含む複雑なフィルタリングロジックを適用できます。
  • 動的ソート:複数の列に対して異なるソート戦略でソートを適用できます。
  • ページネーション:追加のクエリ機能とともにLaravelのページネーションを統合します。
  • 高度なジョイン:モデルの関係を利用せずに関係を定義しやすくなります。
  • CRUDツールCRUDメソッドを実装しやすくなります。

インストール

このパッケージを使用するには、Composerで要求します

composer require sajadsdi/laravel-repository

インストール後は、Eloquentモデル用のリポジトリを作成したいリポジトリごとに、主要なRepositoryクラスを拡張する必要があります。

使用法

リポジトリの拡張

Repositoryクラスを拡張して新しいリポジトリを作成します

use Sajadsdi\LaravelRepository\Repository;

class UserRepository extends Repository implements UserRepositoryInterface
{
    // Implement abstract methods
}

抽象メソッドの実装

各子リポジトリは、モデルとその検索可能、フィルタリング可能、ソート可能な属性を定義するために以下のメソッドを実装する必要があります

public function getModelName(): string;
public function getSearchable(): array;
public function getFilterable(): array;
public function getSortable(): array;

例えば

class UserRepository extends Repository implements UserRepositoryInterface
{
    public function getModelName(): string 
    {
        return User::class;
    }

    public function getSearchable(): array 
    {
        return ['name', 'email'];
    }
    
    public function getFilterable(): array 
    {
        return ['name', 'email', 'created_at'];
    }
    
    public function getSortable(): array 
    {
        return ['name', 'email', 'created_at'];
    }

    // other methods...
}

自動クエリスコープ

リポジトリ内で、どんなパラメータにも依存しないスコープメソッドを実装することが可能です。

例えば

class UserRepository extends Repository implements UserRepositoryInterface
{
    // abstract methods...
    
    public function getVerified()
    {
        return $this->whereNotNull('verified_at');
    }
    
    public function getByName(string $name)
    {
        return $this->where('name','LIKE' , '%'.$name.'%');
    }
    
    public function getVerifiedUsersByName(string $name)
    {
        return $this->getVerified()->getByName($name);
    }
    
    // other methods...
}

検索、ソート、フィルタリング

検索、ソート、フィルタリング操作を実行します

//That's right, you need to inject your repository with the
//interface in the controller or each class you need.

//In this example, we create an instance of the repository
//to convey the concept.
//But it is not the right thing for real projects!
$userRepo = new UserRepository();

// Search by name or email
$users = $userRepo->search('John')->get();

// Sort by name in descending order
$users = $userRepo->sort('name:desc')->get();

// Filter between IDs
$users = $userRepo->filter('id:between_1,10')->get();

フィルタリング文字列

特殊な構文を使用します、例えば

  • id:equal_1は等価条件。
  • name:like_johnはLIKE条件。
  • price:between_100,200は範囲フィルタリング。
  • id:in_2,3,4は列の値が2または3または4であるかを確認します。
  • price:upper_200は上範囲フィルタリング。
  • price:lower_200は下範囲フィルタリング。
  • status:is_nullは列がNULLであるかを確認します。
  • status:is_not-nullは列がNULLでないかを確認します。
  • id:not_in_2,3,4は列の値が2、3、4に等しくないかを確認します。
  • id:not_between_2,6は列が2から6の範囲に含まれないかを確認します。
  • name:not_like_johnはLIKEでない条件。
  • id:not_equal_2は等価でない条件。
  • price:not_upper_500は上範囲フィルタリングでない。
  • price:not_lower_200は下範囲フィルタリングでない。

複数のフィルタリングおよびソートの使用

@は複数のフィルタリングおよびソート条件を区切るために使用されます。

// multiple sort 
$users = $userRepo->sort('name:desc@id:asc')->get();

// multiple filter
$users = $userRepo->filter('id:in_1,10@status:is_null')->get();

ページネーション

Laravelのページネーションを利用して追加のクエリ機能を追加します

$users = $userRepo->search('John')->filter('status:is_null')->sort('id:desc')->paginate(10);

高度なジョイン

ジョイン機能は、正確かつ柔軟にテーブルを複雑に連鎖するために設計されています。以下に、joinableプロパティを使用してリポジトリ内の関係を定義する方法を示します

protected $joinable = [
    'relationName' => [
        'rel' => [
            'table1.field1' => 'table2.field2',
            'table2.field3' => 'table3.field4',
        ],
        'join_type' => 'inner',
        'select'     => ['table2.field_x as x', 'table3.field_y as y'],
        'filterable' => ['x', 'y', 'field_z'],
        'sortable'   => ['x', 'y', 'field_z'],
        'soft_delete'=> ['table2', 'table3']
    ],
];

您无需填写所有选项;只需根据需求进行配置。通过调整每个组件来自定义 joinable 属性以适应您的应用程序需求。

rel:

rel 数组中指定了您的主表与相关表之间的连接条件。它是设置连接的基石,决定了查询过程中表之间的相互关系。

  • 单连接:要关联两个表,请指定主表中的字段和要连接的表中相应的字段。
'rel' => [
    'table1.field1' => 'table2.field2',
],
  • 多连接:当您的查询涉及多个表时,通过连续列出字段关系来链式连接。键代表主表中的字段或最后一个连接的表中的字段,值代表下一个要连接的表中的字段。
'rel' => [
    'table1.field1' => 'table2.field2',
    'table2.field3' => 'table3.field4',
    // Extend the chain with additional table joins as necessary
],

这种模式便于创建一系列连接,其中 table1 是与您的存储库相关的主表,而 table2table3 等,是按顺序连接的表。每个连接都扩展了在多个表上进一步过滤、选择和排序的能力,使您能够对最终的查询输出有更大的控制。

join_type:

默认情况下是 inner,您可以将它设置为 leftright 或 ...

select:

确定要从连接的表中选择哪些列。别名有助于区分具有共享名称的列或更希望有描述性名称的情况。

'select' => [
    // All visible fields from the main table are automatically selected.
    
    'table2.field_x as x',  // Specific field with a clear alias
    'table3.field_y as y',  // Another field with its own alias
    // Add more fields and aliases accordingly
],

filterablesortable

这些数组指定了哪些字段或别名可以从 'select' 子句中用于过滤和排序操作。列出的字段应属于在 'select' 中定义的别名或属于连接序列中的最后一个表。

'filterable' => [
    'x',        // Alias defined in `select`
    'y',        // Another alias defined in `select`
    'field_z',  // Field from the final table in the join chain
    // Add more filterable fields as needed
],
'sortable' => [
    'x',        // Alias that's sortable
    'y',        // Another sortable alias
    'field_z',  // Field from the final table (`table3`) that's sortable
    // Add more sortable fields as required
],

softDeletes:

指定除了基本存储库模型表以外的表,在连接操作中应排除软删除的记录。模型自动认可基本表的软删除状态,无需列出。

'softDeletes' => [
    'table2',  // Related table with soft-delete enabled
    'table3',  // Another related table with soft-delete
    // List additional related tables with soft-delete enabled as necessary
],

这种方法确保了连贯的查询体验,同时尊重软删除状态,提供了强大的查询能力。

使用 joinable 关系进行过滤和排序

一旦在 joinable 配置中定义了您的关联,您就可以轻松地使用 filtersort 方法通过相关模型进行过滤和排序。以下是如何使用这些方法根据条件和排序查询用户数据的示例。

// Apply filters and sorting on the related models
$users = $userRepo->filter('relationName.x:is_null@relationName.y:lower_100')
                   ->sort('relationName.field_z:desc')
                   ->paginate(10);

// This will fetch users with the following conditions applied:
// - For the related model under 'relationName':
//   - Field 'x' should be `null` (is_null condition).
//   - Field 'y' should be less than or equal to 100 (lower_100 condition).
// - The resulting users will then be sorted in descending order
//   based on field 'field_z' from the related model.
// - The results will be paginated, returning 10 users per page.

请确保您的关联在 joinable 数组中定义良好,并且相关字段在 filterablesortable 配置中提及。这确保了过滤和排序逻辑在您的数据库查询中被正确应用。

CRUD 工具

用于 CRUD 操作的方法通常在存储库中重复使用!为了防止这种重复,您可以使用包中可用的接口和特性;如果需要,您还可以覆盖方法。

使用 CrudRepositoryInterface 接口和 Crud 特性

这些工具用于所有 ReadWrite 操作。您可以在存储库中实现 CRUD 接口。

class UserRepository extends Repository implements CrudRepositoryInterface,UserRepositoryInterface
{
    use Crud;
    
    // other methods...
}

您可以扩展 CRUD 接口

interface UserRepositoryInterface extends CrudRepositoryInterface
{
    //you methods...
}


class UserRepository extends Repository implements UserRepositoryInterface
{
    use Crud;
    
    // other methods...
}

或者,您可以为所有存储库创建一个基本存储库类。

当然,您可以使用针对写入或读取分别特殊的接口和特性。

interface UserReadRepositoryInterface extends ReadCrudRepositoryInterface
{
    //you methods...
}


class UserReadRepository extends Repository implements UserReadRepositoryInterface
{
    use ReadCrud;
    
    // other methods...
}

//OR

interface UserWriteRepositoryInterface extends WriteCrudRepositoryInterface
{
    //you methods...
}


class UserWriteRepository extends Repository implements UserWriteRepositoryInterface
{
    use WriteCrud;
    
    // other methods...
}

存储库类中的方法命名

当您需要在存储库中定义一个与 Eloquent 方法具有相同名称的方法时,使用 $this->query() 以避免冲突。这种方法允许您安全地利用 Eloquent 的功能。

对于创建方法

public function create(array $data)
{
    // Call the `create` method on the query builder provided by `$this->query()`
    return $this->query()->create($data);
}

这将直接使用查询构建器的创建方法。

Eloquent 的简单存储库模式实现

假设我们定义了一个存储库和接口如下

interface UserRepositoryInterface
{
    public function getAll(string $search = null, string $filter = null, string $sort = null, int $perPage = 15);
    
    public function getProfilePic(int $userId);
    
    public function getUserWithAllRelations(int $userId);
}



class UserRepository extends Repository implements UserRepositoryInterface
{

    protected $joinable = [
        'activities' => [
            'rel' => [
                'users.id' => 'user_activities.user_id',
            ],
            'select'     => ['user_activities.created_at as activity_time', 'user_activities.type as activity_name'],
            'filterable' => ['activity_time', 'activity_name'],
            'sortable'   => ['activity_time', 'activity_name'],
            'soft_delete'=> ['user_activities']
        ],
        
        'profile' => [
            'rel' => [
                'users.pic_id' => 'user_pictures.id',
            ],
            'select'     => ['user_pictures.path as photo'],
            'filterable' => ['photo'],
            'sortable'   => ['photo'],
            'soft_delete'=> ['user_pictures']
        ],
    ];
    
    public function getModelName(): string 
    {
        return User::class;
    }

    public function getSearchable(): array 
    {
        return ['name', 'email'];
    }
    
    public function getFilterable(): array 
    {
        return ['name', 'email', 'created_at'];
    }
    
    public function getSortable(): array 
    {
        return ['name', 'email', 'created_at'];
    }

    //you can use this method for index api on controller
    //if needed, filter and sort methods call automatically join method.
    
    public function getAll(string $search = null, string $filter = null, string $sort = null, int $perPage = 15)
    {
        return $this->search($search)->filter($filter)->sort($sort)->paginate($perPage);
        
        // if you need join in all results,you can use `join` or `joins` in begin of the query like:
        //return $this->joins(['activities','profile'])->search($search)->filter($filter)->... 
    }
    
    //you can use join method with relation name, without filter or sort method
    public function getProfilePic(int $userId)
    {
        $user = $this->join('profile')->where('users.id',$userId)->first();
        
        return $user?->photo ?? 'path/to/no-profile.png';
    }
    
    //You can use multiple join on relations defied on joinable, without filter or sort method
    public function getUserWithAllRelations(int $userId)
    {
        return $this->joins(['activities','profile'])->find($userId);
    }
}

在定义了仓库和接口之后,您需要在AppServiceProvider中将这些文件绑定。

    public function register():void
    {
        //other bindings ...
        //..
        //.
        $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }

现在您可以在控制器中使用这个仓库,如下所示:

class UserController extends Controller
{

    private UserRepositoryInterface $repo;
    
    public function __construct(UserRepositoryInterface $userRepo) 
    {
        $this->repo = $userRepo;
    }
    
    public function index(Request $request)
    {
        $users = $this->repo->getAll(
            $request?->search,
            $request?->filter,
            $request?->sort
        );
        
        return response($users);
    }

} 

在您的路由器上设置控制器和索引方法之后(例如,GET http://127.0.0.1/api/v1/admin/users

现在您的前端可以通过以下方式调用带有searchfiltersort查询参数的API

http://127.0.0.1/api/v1/admin/users/?search=john&filter=id:upper_5@activities.activity_name:equal_comment@profile.photo:is_not-null&sort=activities.activity_time:desc

这非常简单...

Eloquent的仓库模式的高级实现

有些人认为在Eloquent Laravel中使用仓库模式是多余的,甚至是错误的。他们认为这种模式破坏了SOLID原则,确实如此。

为了实现Eloquent的这种模式,我们必须摒弃“ORM可能以后会改变”的观念。下一步是分离读写操作!我们通过创建一个用于读取方法的仓库和一个用于写入方法的另一个仓库来实现。这样,每个仓库都是为特定目的而创建的,这也带来了好处,其中之一是:想象一个大型项目,我们需要为读取和写入操作分别设置数据库连接。使用这种模式,在仓库内部定义这些连接非常简单,这样每个仓库都有自己的对应连接!

贡献

我们欢迎社区贡献以改进和扩展这个库。如果您想贡献,请按照以下步骤操作:

  1. 在GitHub上复制仓库。
  2. 在本地克隆您的副本。
  3. 为您的功能或错误修复创建一个新的分支。
  4. 进行更改并使用清晰、简洁的提交信息提交。
  5. 将您的更改推送到GitHub上的您的副本。
  6. 向主仓库提交拉取请求。

报告错误和安全问题

如果您发现此项目中的任何安全漏洞或错误,请通过以下渠道通知我们:

  • GitHub Issues:您可以在我们的GitHub仓库上打开一个问题来报告错误或安全担忧。请尽可能提供详细信息,包括重现问题的步骤。

  • 联系:对于敏感的安全相关问题,您可以通过以下联系渠道直接联系我们:

联系

如果您有任何问题、建议、财务问题或想为此项目做出贡献,请随时联系维护者

我们感谢您的反馈、支持和任何帮助我们维护和改进此项目的财务贡献。

许可证

高级Laravel仓库包是开源软件,根据MIT许可证授权。