sarfraznawaz2005 / actions
Laravel 包,作为单动作控制器的替代品,支持单类中的 Web 和 API。
Requires
- php: >=7.0
- illuminate/support: ~5|~6|~7|~8|~9
README
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/浏览器请求的动作类
这是默认行为,如果您使用它,您可以从 __invoke
或 html
方法返回您的 HTML/blade 视图。
验证
您可以为您的 store
和 update
方法执行输入验证,只需在您的动作类中使用 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
方法返回某些内容,它可以从html
和json
方法作为第一个参数读取。在这种情况下,创建待办事项的布尔结果(return $this->create($todo)
)在html
和json
方法中通过$result
变量(其名称可以是任何名称)使用。 -
任何验证错误都保存在
$this->errors
变量中,可以根据需要使用。 -
在
html()
方法中,我们使用了来自父操作类的self::MESSAGE_CREATE
。类似地,也可以使用self::MESSAGE_UPDATE
、self::MESSAGE_DELETE
和self::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
,它之前不存在。
幕后,我们只是将此方法返回的任何内容合并到原始请求数据中。
创建动作
- 创建操作
php artisan make:action ShowPost
ShowPost
操作将被创建
- 为所有资源操作(
index
、show
、create
、store
、edit
、update
、destroy
)创建操作
php artisan make:action Post --resource
IndexPost
、ShowPost
、CreatePost
、StorePost
、EditPost
、UpdatePost
、DestroyPost
操作将被创建
- 为所有API操作(不包括
create
、edit
)创建操作
php artisan make:action Post --api
IndexPost
、ShowPost
、StorePost
、UpdatePost
、DestroyPost
操作将被创建
- 通过指定操作创建操作
php artisan make:action Post --actions=show,destroy,approve
ShowPost
、DestroyPost
、ApprovePost
操作将被创建
- 排除指定操作
php artisan make:action Post --resource --except=index,show,edit
CreatePost
、StorePost
、UpdatePost
、DestroyPost
操作将被创建
- 指定创建操作时的命名空间(相对路径)
php artisan make:action Post --resource --namespace=Post
IndexPost
、ShowPost
、CreatePost
、StorePost
、EditPost
、UpdatePost
、DestroyPost
操作将在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
以及该类工作所需的任何私有/受保护方法。这将允许您在不同项目和框架之间使用它们。您也可以将它们视为服务类。
鸣谢
许可证
有关更多信息,请参阅许可证文件