sarfraznawaz2005 / actions

Laravel 包,作为单动作控制器的替代品,支持单类中的 Web 和 API。

1.6.2 2021-11-10 12:45 UTC

This package is auto-updated.

Last update: 2024-09-10 19:01:49 UTC


README

Software License Latest Version on Packagist Total Downloads

Laravel Actions

Laravel 包,作为单动作控制器的替代品,支持单类中的 Web/html 和 API。您可以使用名为 Action 的单个类来发送适当的 Web 或 API 响应 自动。它还提供了轻松验证请求数据的方法。

在底层,动作类是普通的 Laravel 控制器,但具有单个公共 __invoke 方法。这意味着您可以做您通常用控制器做的事情,例如调用 $this->middleware('foo') 或其他任何事情。

目录

为什么

  • 有助于遵循单一责任原则(SRP)
  • 有助于保持控制器和模型精简
  • 小型专用类使代码更容易测试
  • 有助于避免代码重复,例如 Web 和 API 的不同类
  • 动作类可以从您的应用程序的多个位置调用
  • 小型专用类在复杂应用程序中特别有用
  • 表达式的路由注册,如 Route::get('/', HomeAction::class)
  • 允许装饰器模式

要求

  • PHP >= 7
  • Laravel 5, 6

安装

通过 composer 安装

composer require sarfraznawaz2005/actions

就是这样。

示例动作类

class PublishPostAction extends Action
{
    /**
     * Define any validation rules.
     */
    protected $rules = [];

    /**
     * Perform the action.
     *
     * @return mixed
     */
    public function __invoke()
    {
        // code
    }
}

__invoke() 方法中,您编写动作的实际逻辑。动作是可调用的类,使用 __invoke 魔法函数将它们转换为 Callable,这使得它们可以作为函数调用。

用法

作为控制器动作

动作类的主要用途是将它们映射到路由,以便在访问这些路由时自动调用它们。

// routes/web.php

Route::get('post', '\App\Http\Actions\PublishPostAction');

// or

Route::get('post', '\\' . PublishPostAction::class);

请注意,这里的初始 \ 非常重要,以确保命名空间不会成为 \App\Http\Controller\App\Http\Actions\PublishPostAction

作为可调用类

$action = new PublishPostAction();
$action();

自动发送 Web 或 API 响应

如果您需要从同一个/单个动作类中提供 Web 和 API 响应,您需要在您的动作类中定义 html()json() 方法。

class TodosListAction extends Action
{
    protected $todos;

    public function __invoke(Todo $todos)
    {
        $this->todos = $todos->all();
    }

    protected function html()
    {
        return view('index')->with('todos', $this->todos);
    }

    protected function json()
    {
        return TodosResource::collection($this->todos);
    }
}

有了这两个方法,包将 自动 发送适当的响应。浏览器将接收来自 html() 方法的输出,而其他设备将接收来自 json() 方法的输出。

在底层,我们检查请求中是否存在 Accept: application/json 标头,如果存在,则从您的 json() 方法发送输出,否则从 html() 方法发送输出。

您可以通过实现 isApi() 方法来更改此 API/JSON 检测机制,它必须返回 boolean 值。

class TodosListAction extends Action
{
    protected $todos;

    public function __invoke(Todo $todos)
    {
        $this->todos = $todos->all();
    }

    protected function html()
    {
        return view('index')->with('todos', $this->todos);
    }

    protected function json()
    {
        return TodosResource::collection($this->todos);
    }
        
    public function isApi()
    {
        return request()->wantsJson() && !request()->acceptsHtml();
    }

}

仅用于 API 请求的动作类

只需从 isApi 方法返回 true 并使用 json 方法即可。

仅用于 Web/浏览器请求的动作类

这是默认行为,如果您使用它,您可以从 __invokehtml 方法返回您的 HTML/blade 视图。

验证

您可以为您的 storeupdate 方法执行输入验证,只需在您的动作类中使用 protected $rules = [] 属性即可。

class TodoStoreAction extends Action
{
    protected $rules = [
        'title' => 'required|min:5'
    ];
    
    public function __invoke(Todo $todo)
    {
        $todo->fill(request()->all());
    
        return $todo->save();
    }
}

在这种情况下,验证将在调用 __invoke 方法之前执行,如果失败,您将被自动重定向回上一个表单页面,其中 $errors 被填充了验证错误。

提示:由于验证在调用__invoke方法之前执行,使用request()->all()将始终在__invoke方法中提供有效数据,这就是为什么它在上面的示例中使用的原因。

自定义验证消息

要为您的规则实现自定义验证错误消息,只需使用protected $messages = []属性。

忽略/过滤请求数据

如果您想在验证/持久化之前移除一些请求数据,您可以使用protected $ignored = ['id'];。在这种情况下,id将从请求中删除,换句话说,它就像没有在请求中提交一样。

实用方法和属性

考虑以下操作,该操作应将待办事项/任务保存到数据库并向网页和API发送适当的响应

class TodoStoreAction extends Action
{
    protected $rules = [
        'title' => 'required|min:2'
    ];

    public function __invoke(Todo $todo)
    {
        return $this->create($todo);
    }

    protected function html($result)
    {
        if (!$result) {
            return back()->withInput()->withErrors($this->errors);
        }

        session()->flash('success', self::MESSAGE_CREATE);
        return back();
    }

    protected function json($result)
    {
        if (!$result) {
            return response()->json(['result' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        return response()->json(['result' => true], Response::HTTP_CREATED);
    }
}

以上是一些该软件包提供的开箱即用的功能

  • __invoke方法内部,我们使用了$this->create方法作为创建新待办事项记录的快捷方式。类似地,也可以使用$this->update$this->delete方法。它们都返回布尔值。它们也都接受可选的回调。
return $this->create($todo, function ($result) {
    if ($result) {
        flash(self::MESSAGE_CREATE, 'success');
    } else {
        flash(self::MESSAGE_FAIL, 'danger');
    }
});

尽管如此,使用这些实用方法并非必需。

  • 如果您从__invoke方法返回某些内容,它可以从htmljson方法作为第一个参数读取。在这种情况下,创建待办事项的布尔结果(return $this->create($todo))在htmljson方法中通过$result变量(其名称可以是任何名称)使用。

  • 任何验证错误都保存在$this->errors变量中,可以根据需要使用。

  • html()方法中,我们使用了来自父操作类的self::MESSAGE_CREATE。类似地,也可以使用self::MESSAGE_UPDATEself::MESSAGE_DELETEself::MESSAGE_FAIL

提示:您可以选择不使用此软件包提供的任何实用方法/属性/验证,这完全没问题。请记住,操作类是普通的Laravel控制器,您可以按自己的方式使用。

转换请求数据

如果您想在验证执行之前和调用__invoke()方法之前转换请求数据,您可以在您的操作类中定义一个名为transform的方法,它必须返回一个数组。

public function transform(Request $request): array
{
    return [
        'description' => trim(strip_tags($request->description)),
        'user_id' => auth()->user->id ?? 0,
    ];
}

转换方法可以用来修改现有的请求变量,也可以添加新的变量到请求数据中。在上面的示例中,我们修改了description以删除任何空白字符和HTML标签。我们还在请求数据中添加了user_id,它之前不存在。

幕后,我们只是将此方法返回的任何内容合并到原始请求数据中。

创建动作

Screen

  • 创建操作
php artisan make:action ShowPost

ShowPost操作将被创建

  • 为所有资源操作(indexshowcreatestoreeditupdatedestroy)创建操作
php artisan make:action Post --resource

IndexPostShowPostCreatePostStorePostEditPostUpdatePostDestroyPost操作将被创建

  • 为所有API操作(不包括createedit)创建操作
php artisan make:action Post --api

IndexPostShowPostStorePostUpdatePostDestroyPost操作将被创建

  • 通过指定操作创建操作
php artisan make:action Post --actions=show,destroy,approve

ShowPostDestroyPostApprovePost操作将被创建

  • 排除指定操作
php artisan make:action Post --resource --except=index,show,edit

CreatePostStorePostUpdatePostDestroyPost操作将被创建

  • 指定创建操作时的命名空间(相对路径)
php artisan make:action Post --resource --namespace=Post

IndexPostShowPostCreatePostStorePostEditPostUpdatePostDestroyPost操作将在App\Http\Actions\Post命名空间下创建,位于app/Http/Actions/Post目录中

  • 指定创建操作时的命名空间(绝对路径)
php artisan make:action ActivateUser --namespace=\\App\\Foo\\Bar

ActivateUser操作将在App\Foo\Bar命名空间下创建,位于app/Foo/Bar目录中

  • 强制创建
php artisan make:action EditPost --force

如果EditPost操作已经存在,它将被新的一个覆盖

注册路由

这里有几种在路由中注册操作的方法

在单独的actions.php路由文件中

  • 创建 routes/actions.php 文件(您可以选择任何名称,这里只是一个示例)
  • app/Providers/RouteServiceProvider.php 中定义 "action" 路由组
使用命名空间自动前缀
// app/Providers/RouteServiceProvider.php

protected function mapActionRoutes()
{
    Route::middleware('web')
         ->namespace('App\Http\Actions')
         ->group(base_path('routes/actions.php'));
}
// app/Providers/RouteServiceProvider.php

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();
    
    $this->mapActionRoutes();

    //
}
// routes/actions.php

Route::get('/post/{post}', 'ShowPost');
不使用命名空间自动前缀
// app/Providers/RouteServiceProvider.php

protected function mapActionRoutes()
{
    Route::middleware('web')
         ->group(base_path('routes/actions.php'));
}
// app/Providers/RouteServiceProvider.php

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();
    
    $this->mapActionRoutes();

    //
}
// routes/actions.php

use App\Actions\ShowPost;

Route::get('/post/{post}', ShowPost::class); // pretty sweet, isn't it? 😍

web.php 路由文件中

  • RouteServiceProvider.php 中更改 "web" 组的命名空间
// app/Providers/RouteServiceProvider.php

protected function mapWebRoutes()
{
    Route::middleware('web')
         ->namespace('App\Http') // pay attention here
         ->group(base_path('routes/web.php'));
}
  • routes/web.php 文件中将操作和控制器放在不同的路由组中,并为每个组添加适当的命名空间
// routes/web.php

Route::group(['namespace' => 'Actions'], function () {
    Route::get('/posts/{post}', 'ShowPost');
    Route::delete('/posts/{post}', 'DestroyPost');
});

Route::group(['namespace' => 'Controllers'], function () {
    Route::get('/users', 'UserController@index');
    Route::get('/users/{user}', 'UserController@show');
});

加分项:创建普通类

该包还提供了 make:class 控制台命令来创建普通类

php artisan make:class FooBar

FooBar 类将在 app/Actions 文件夹下创建

namespace App\Actions;

class FooBar
{
    /**
     * Perform the action.
     *
     * @return mixed
     */
    public function execute()
    {
        //
    }
}

请注意,这些是您可以用作任何目的的普通旧 PHP 类。理想情况下,它们不应依赖于 Laravel 框架或其他任何框架,并且应该有一个公开方法作为 API,例如 execute 以及该类工作所需的任何私有/受保护方法。这将允许您在不同项目和框架之间使用它们。您也可以将它们视为服务类。

鸣谢

许可证

有关更多信息,请参阅许可证文件