想象一下实体Genre和Book。
每个都有API资源端点/genre和/book。在Laravel路线上,这可能是:
$app->resource('/genre', GenreController::class);我想要这段关系的终结点。GET /genre/1/book,把书放在Genre #1下面。
这里的最佳实践是什么?将处理程序放置在GenreController、BookController或一个全新的控制器中?
在sidenote上,我使用的是dingo-api包,但我不认为这有什么不同。
发布于 2017-03-09 09:37:54
因此,克里斯建议为这种关系设立一个专用控制器,@RossWilson有一种天才的方法,可以重用关系控制器(至少用于加载Book的操作)。
不幸的是,Lumen的RouteProvider返回一个简单的数组,因此不具备$request->route($param)和更重要的是$request->route()->forgetParameter($param)的方便性。
===新解决方案===
最后,我做的基本上和RossWilson建议的完全一样,就像Lumen所支持的那样。我没有在Controller的__construct中获取路由参数,而是制作了一个中间件,将路由参数移动到请求的输入和查询数组中。
中间件看起来如下所示:
public function handle($request, $next)
{
if ($genre_id = Arr::get($request->route()[2], 'genre')) {
// Add 'genre_id' to the input array (not replacing it if it already exists).
$request->merge(['genre_id' => $request->input('genre_id', $genre_id)]);
// Add 'genre_id' to the query array.
$request->query->add('genre_id', $genre_id);
// Forget the route parameter
// Has to be done manually, because Lumen...
$route = $request->route();
$request->setRouteProvider(function() use ($route) {
Arr::forget($route[2], 'genre');
return $route;
});
}
// Pass the updated $request to $next.
return $next($request);
}在我的实现中,我只设置了GET和DELETE请求的查询参数,以及POST和PUT的输入参数。
然后,您可以对BookController资源重用genre.book资源,从$request->query('genre_id')进行过滤,并将关系从$request->input('genre_id')关联起来。
===原始解===
相反,我最终得到了一个专用关系控制器GenreBookController,该控制器继承自非关系控制器BookController。由于需要匹配的方法声明(请参见下面的$book_id = null如何解决此问题),它并不像可能的那样优雅,但它非常苗条和枯燥。
GenreBookController extends BookController
protected function addGlobalScope($genre_id)
{
// Thanks Ross Wilson for the global scope suggestion.
Book::addGlobalScope('genreScope', function ($query) use ($genre_id) {
$query->where('genre_id', $genre_id);
});
}
public function show(Request $request, $genre_id, $book_id = null)
{
$this->addGlobalScope($genre_id);
return parent::show($request, $book_id);
}对于BookController,show方法和往常一样(它不需要知道show上的额外参数)。
我还想出了一种简单的方法,让GenreBookController可以将类型参数传递给store方法:
public function store(Request $request, $genre_id)
{
// This way lets an input genre_id override the Route parameter.
$request->merge(['genre_id' => $request->input('genre_id', $genre_id)]);
// This way forces the Route parameter to be used over input parameters.
$request->merge(['genre_id' => $genre_id]);
return parent::store($request);
}同样,对于BookController来说,它是一切照旧的,当然,它可能会对通过$request->input('genre_id')传递的类型进行任何验证/授权。这样就没有重复的验证和授权逻辑。
--关于FormRequests的一点注记
如果使用FormRequests验证genre_id,则在GenreBookController可以从路由参数设置genre_id输入变量之前进行验证。
在我看来,你有两个选择:
FormRequest的FormRequest方法上(我还没有测试这个,因为我不喜欢把这样的逻辑放在这里)。Laravel:,如果你不使用Lumen,我建议看一下@RossWilson的答案,在我看来,它有点干净。
发布于 2017-03-04 13:45:06
一种选择是只使用当前的BooksController。将下面的内容添加到BooksController中
public function __construct(Request $request)
{
if ($genreId = $request->route('genre')) {
$request->route()->forgetParameter('genre');
Book::addGlobalScope('genreScope', function ($query) use ($genreId) {
$query->whereGenreId($genreId);
});
}
}这将允许您的书籍成为Genre的作用域,并将其作为路由param删除。
那你的路线就是:
$api->resource('genre.book');请注意,使用此方法,您仍将以相同的方式使用store和update方法,即在请求中传递genre_id。
希望这能有所帮助!
发布于 2017-03-04 12:08:11
虽然这里没有100%的具体答案--每个资源都有一个控制器几乎总是比较容易,如果你想直接点击它,那么一个控制器就是你想要做的事情。
如果你可以(通常)坚持主要动作(索引,创建,存储,显示,编辑,更新,删除),这将使它更容易。它将保持有组织的事情,未来的开发人员在您的项目将能够很容易地遵循结构。
很好的阅读:基本放大器的DHH方法:http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/
白屋API指南:https://github.com/WhiteHouse/api-standards#white-house-web-api-standards
https://stackoverflow.com/questions/42595561
复制相似问题