morningtrain/laravel-resources

Laravel 的资源系统

4.25.0 2023-05-08 05:13 UTC

README

操作是一组在服务器上执行的动作,从设置路由到处理客户端请求的所有逻辑。

一个简单的操作可以是获取所有用户并返回给客户端的索引操作。

另一个用例可能是触发 Eloquent 模型上的动作。在这种情况下,用户输入总是需要验证,获取模型,执行方法,然后可能返回一些对用户有意义的响应。

这类似于一个简单的操作,我们需要返回模型的单个实例给用户(传统的读取调用)——但它包含一些额外的逻辑来处理方法执行。

删除操作类似,它首先获取模型并触发删除方法。

安装

通过 Composer

$ composer require morningtrain/laravel-resources
$ php artisan vendor:publish --provider="MorningTrain\Laravel\Resources\LaravelResourcesServiceProvider"

配置

config/resources.php 中注册您的资源和操作

第一级键对应于命名空间,值是资源类的数组。您可以使用数组键为资源提供自定义名称,并嵌套资源数组。只需确保所有项目都具有唯一的键。

如果为资源未提供键,则使用 Str::snake(class_basename($resource))。这意味着如果您的类名为 User,并且同一命名空间中存在具有键 "user" 的另一个资源,它们将发生冲突。

示例

'api' => [
    \App\Operations\Api\User::class,
    'custom_user' => \App\Operations\Api\User::class,
    
    'nested' => [
        \App\Operations\Api\User::class,
        'custom_user' => \App\Operations\Api\User::class,
        
        'deep_nested' => [
            \App\Operations\Api\User::class,
            'custom_user' => \App\Operations\Api\User::class,
        ],
    
    /*
     * These two will not work together - non-unique resource name:
     *  \App\Operations\Api\MyResource::class,
     *  'my_resource' => \App\Operations\Api\User::class,
     */
],

示例

索引操作

索引操作的目的在于检索模型条目的列表并将其作为 JSON 返回。

以下代码示例来自包,将说明如何实现索引操作。

<?php

namespace MorningTrain\Laravel\Resources\Operations\Eloquent;

use MorningTrain\Laravel\Resources\Support\Contracts\EloquentOperation;
use MorningTrain\Laravel\Resources\Support\Pipes\Eloquent\QueryToCollection;
use MorningTrain\Laravel\Resources\Support\Pipes\Eloquent\QueryModel;
use MorningTrain\Laravel\Resources\Support\Pipes\TransformToView;

class Index extends EloquentOperation
{

    const ROUTE_METHOD = 'get';

    protected function beforePipes()
    {
        return [

            /**
             * Takes filters and model name as parameters and returns a new query object
             */
            QueryModel::create()->model($this->model)->filters($this->getCachedFilters()),

            /**
             * Trigger `get` on the query and returns the resulting collection
             */
            QueryToCollection::create(),

            /**
             * Transform the collection by applying any appends to each model in the entry
             */
            TransformToView::create()->appends($this->appends, $this->overwrite_appends),
        ];
    }

}

在项目层面,索引类可以是资源或单个操作实现的。

以下示例是一个实现单个索引操作的 EloquentResource。操作已在基类中添加,因此不需要静态 $operations 属性。

在我们的示例中,用户被返回,并应用了分页过滤器。

<?php

namespace App\Http\Operations\Api;

use MorningTrain\Laravel\Resources\Support\Contracts\EloquentResource;
use MorningTrain\Laravel\Resources\Operations\Eloquent\Index;
use MorningTrain\Laravel\Filters\Filter;
use App\Models\User as Model;

class Users extends EloquentResource
{

    protected $model = Model::class;
    
    public static $operations = [
        Index::class,
    ];

    public function getFilters(){
        return [
            Filter::paginate(),
        ];
    }

}

以下示例类似于上面的示例,但操作将作为一个单独的类实现。

<?php

namespace App\Http\Operations\Api\Users;

use MorningTrain\Laravel\Resources\Operations\Eloquent\Index as Operation;
use MorningTrain\Laravel\Filters\Filter;
use App\Models\User as Model;

class IndexUsers extends Operation
{

    protected $model = Model::class;

    public function getFilters(){
        return [
            Filter::paginate(),
        ];
    }

}

设计原则

我们用操作完成的大部分工作都可以使用传统的 MVC 方法实现。然而,控制器逻辑很容易退化成重复和结构不良的代码。

我们实现设置中的 操作 的目标是大大提高代码的重用性。当我们找到了解决特定任务的良好解决方案时,为什么不将其用于所有类似任务呢?

资源

在这个上下文中,我们所说的 资源 实质上是一组操作。每个操作都在资源内部初始化和配置。

配置

操作的可配置性意味着我们可以构建一个多用途的操作,它在类似情况下使用,但具有略微不同的行为。

一个常见的例子可能是典型的索引操作,根据一组过滤器查询 Eloquent 模型并返回一个集合给用户。

在这种情况下,我们的索引操作被配置为使用特定的模型类(所有 Eloquent 操作都是这样)和过滤器数组。

到目前为止,我们只提到了API操作,但一个操作也可以是一个正常的页面请求。在这种情况下,它会配置一个更美观的路径,例如返回一个特定的视图。在我们的大多数用例中,页面操作渲染的视图会输出一个期望的React组件。

所有操作都应该配置为通过权限层进行保护,以控制访问。使用Laravel,我们最终利用底层的策略和门控系统来控制访问。我们还使用额外的权限Laravel包来创建所需的模型和数据库结构,以支持角色和权限。

使用管道执行微任务

随着我们Laravel包2.x版本的发布,操作的主要逻辑被分割成多个更小的任务,以便允许进一步的代码重用。这是通过使用Laravel管道设置来实现的,这些设置也用于Laravel内部处理中间件。

有关Laravel 5.7中管道的文档,请参阅此处

这允许在类似操作之间实现更高的定制程度,同时保持基本逻辑解耦。

每个小任务都被称为管道,以反映它是操作管道的一部分。

管道的示例

  • 验证:使用Laravel兼容的验证规则验证传入的HTTP请求
  • 过滤查询:允许基于HTTP请求变量过滤数据库查询构建器实例。
  • 准备响应负载:将主操作逻辑的返回值统一转换为负载JSON对象。

将操作结构化为管道也使实际的代码/逻辑不包含在操作中。剩下的基本上是一个可配置的类。要更改和调整操作的工作方式,只需要复制/扩展操作和管道的更改。在大多数情况下,不需要添加额外的代码。

管道

TriggerOnModel

TriggerOnModel管道期望前一个管道返回的数据是一个Eloquent模型。它将在模型或提供的闭包上触发一个方法,并使用当前模型实例。

可以使用以下方式使用它

\MorningTrain\Laravel\Resources\Support\Pipes\Eloquent\TriggerOnModel::create()
    ->trigger('someModelMethod')

或者使用闭包的方式如下

\MorningTrain\Laravel\Resources\Support\Pipes\Eloquent\TriggerOnModel::create()
    ->trigger(
        fn(Model $model) => $model->someModelMethod()
    )

Action Eloquent操作基本上是一个带有TriggerOnModel管道的读取操作。

TriggerOnModelsInCollection

TriggerOnModelsInCollection管道类似于TriggerOnModel。它期望一个模型集合,并为集合中的每个模型实例执行触发器。

QueryToCollection

触发前一个管道返回的查询的get操作,并返回结果。

QueryToModel

触发前一个管道返回的查询的first操作,并返回结果。

QueryToCount

触发前一个管道返回的查询的count操作,并返回结果。

ToResourceView

它期望前一个管道返回一个模型或集合实例。数据(模型或集合)将被传递到一个Laravel资源,并返回结果数组。

\MorningTrain\Laravel\Resources\Support\Pipes\ToResourceView::create()
    ->view(MyResourceView::class)

大多数Eloquent操作已经配置为使用此管道,因此可以像这样配置资源

public function readOperation(Read $read)
{
    $read->model(MyModel::class)
         ->resourceView(MyModelResource::class);
}

SetEnv

此管道设置上下文环境变量。它可以通过environment方法进行配置。

\MorningTrain\Laravel\Resources\Support\Pipes\Context\SetEnv::create()
    ->environment(['env' => 'variable'])

environment方法是对Context::env的代理,这意味着它还可以接受一个在生成ENV时执行的闭包。

BladeView

BladeView管道返回与配置匹配的blade视图。

\MorningTrain\Laravel\Resources\Support\Pipes\Pages\BladeView::create()
    ->path('path.to.blade.view')
    ->parameters(['blade' => 'parameters'])

RespondWithPageEnv

RespondWithPageEnv 管道将当前 Context 中的 ENV 作为响应返回,如果是一个 AJAX 请求。可以通过调用其路由使用 Ajax 来获取页面的 ENV。

\MorningTrain\Laravel\Resources\Support\Pipes\Pages\RespondWithPageEnv::create()

致谢

此包由 Morningtrain 开发并积极维护。

 _- _ -__ - -- _ _ - --- __ ----- _ --_  
(         Morningtrain, Denmark         )
 `---__- --__ _ --- _ -- ___ - - _ --_ ´ 
     o                                   
    .  ____                              
  _||__|  |  ______   ______   ______ 
 (        | |      | |      | |      |
 /-()---() ~ ()--() ~ ()--() ~ ()--() 
--------------------------------------