如何组织REST关系的控制器? [Laravel路线]

时间:2017-03-04 11:39:49

标签: php laravel rest laravel-5 dingo-api

想象一下实体GenreBook

每个都有API资源端点/genre/book。在Laravel路线中可能是:

 $app->resource('/genre', GenreController::class);

我想要一个关系的端点。 GET /genre/1/book,获取Genre#1下的图书。

这里的最佳做法是什么?将处理程序放在GenreControllerBookController或者可能是一个全新的控制器中?

在旁注中,我使用的是dingo-api包,但我认为这没有任何区别。

3 个答案:

答案 0 :(得分:2)

一种选择就是使用您当前的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的范围,并将其作为路线参数删除。

然后你的路线就是:

$api->resource('genre.book');

请注意,使用此方法,您仍然可以以相同的方式使用storeupdate方法,即在请求中传递genre_id

希望这有帮助!

答案 1 :(得分:1)

虽然这里没有100%的具体答案 - 每个资源都有一个控制器几乎总是更容易,然后如果你想直接点击它就会有一个关系,这听起来就是你想要的做。

如果您(通常)能够(通常)坚持主要操作(索引,创建,存储,显示,编辑,更新,删除),那么它将更容易。它将保持组织有序,未来开发项目的开发人员将能够轻松地遵循该结构。

好读: DHH对于大本营的方法:http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/

Whitehouse API指南:https://github.com/WhiteHouse/api-standards#white-house-web-api-standards

答案 2 :(得分:0)

所以@Chris建议为这种关系建立一个专用的控制器,而@RossWilson有一种天才的方式来重用关系控制器(至少对于加载Book的动作)。

不幸的是,流明的RouteProvider会返回一个简单的数组,因此不方便$request->route($param),更重要的是$request->route()->forgetParameter($param)

===新解决方案===

我最终做的基本上与@RossWilson建议完全一样,只是以Lumen支持的方式。我没有在Controller的__construct中获取 Route参数,而是创建了一个中间件,将 Route参数移动到Request的输入和查询数组上。

中间件看起来像这样:

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);
}

在我的实现中,我只为GETDELETE请求设置查询参数,并为POSTPUT输入参数。

然后,您可以重复使用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可以从route参数设置genre_id输入变量。

我认为你有两种选择:

  1. 使用中间件将路由参数移动到请求输入上。
  2. 把它放在你authorize的{​​{1}}方法上(我没有测试过这个,因为我不喜欢在这里加上这样的逻辑)。
  3. Laravel:如果你没有使用流明,我建议你看一下@ RossWilson的回答,我觉得它有点清洁。