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 以及该类工作所需的任何私有/受保护方法。这将允许您在不同项目和框架之间使用它们。您也可以将它们视为服务类。
鸣谢
许可证
有关更多信息,请参阅许可证文件
