优雅的使用路由模型绑定

Basic

laravel5.1/5.2发布的路由模型绑定是一个非常强大的功能,dingo/api中想要使用路由模型绑定需要引入bindings组件到dingo的路由组中

路由中的参数命名

如有需要,使用资源的单数形式为路由命名,而不是 {id}/{slug}/{code}等等

正确的示例

# api.php

Route::resource('posts', 'PostController'); // laravel会将参数命名为 post
Route::get('users/{user}/posts', 'PostController@index');
Route::get('posts/{post}/comments', 'CommentController@index');

显式绑定

简书的文章为例,使用RESTFul 风格可以得到以下几条路由

# api.php

Route::get('posts', 'PostController@index'); // 首页/基础帖子展示
Route::get('users/{user}/posts', 'PostController@index'); // 某个用户的帖子
Route::get('collections/{collection}/posts', 'PostController@index') // 某个专题下的帖子

可以看到,这里我使用了同一个方法来处理这三条路由.

接下来定义路由模型绑定,这里使用显式绑定,以获得较大的灵活性

# RouteServiceProvider.php

Route::model('user', User::class);
Route::model('collection', Collection::class)

然后看看控制器中的定义

# PostController.php

public function index($parent = null)
{
    $query = $parent ? $parent->posts() : Post::query();

    // e.g
    $posts = $query->latest()->paginate();

    // ...
}

当我们使用第一条路由访问我们的帖子时, $parent得到的是一个null, $query = Post::query.

访问后两条路由时,由于路由模型绑定,$parent 被赋值为具体的model. 此时可以通过model中定义的关联关系来获取query. 通过显示绑定和关联关系的定义,使得$parent->posts()足够抽象,不依赖于具体的model.具有强大的通用性.

p.s

# User.php

public function posts()
{
    return $this->hasMany(Post::class);
}
# Collection.php

public function posts()
{
    return $this->belongsToMany(Post::class, 'collection_post');
}

Advance

对于, 如 我的文章列表,我的订单等我们可能会这样定义我们的 RESTFul 路由

// 这里单数形式的user就代表着me的意思, 参考于github api
Route::get('user/posts', 'PostController@index');
Route::get('user/orders', 'OrderController@index');

甚至,依旧已简书为例,简书有两个板块30日热门7日热门, 我们可能会有这样两条路由

Route::get('hot-30/posts', 'PostController@index');
Route::get('hot-7/posts', 'PostController@index');

// 又或者根据推荐算法通过用户画像推荐不同的文章
Route::get('recommend/posts', 'PostController@index');

不要激动,接下来不是算法环节?

?现在的问题是,如何依旧使用同一个方法实现?的几条路由呢?

我们换一种思路.前面我们都是在控制器层面做抽象,然后把具体逻辑交给路由层.可面对上面的需求依旧有些力不从心,那我们不妨寻找一下上面需求的共同点,再提取一层抽象

当然更简单的思路是 拆开几个方法写就ok啦,搞这么麻烦是吧.见仁见智.瞎折腾就是了

我们可以这么做

# api.php

// 使用 {virtual} 来匹配上面的hot-30,hot-7,recommend 等等
Route::get('{virtual}/posts', 'PostController@index'); 
# RouteServiceProvider.php

Route::bind('virtual', function ($value) {
    $virtual = "App\\Virtual\\" . studly_case($value);
    return new $virtual($value);
});

上面的做法是, 路由模型绑定是基于model,或者说 entity 的(在symfony中model被称作entity).但是hot-30/hot-7/recommend 并不基于model.(当然也可以基于model,不过这不是我们本次讨论的重点),

那我们不妨使用一个virtual 来承载它们, virtual是一个和entity相近又相反的意思.在这里再适合不过了.来看看具体实现

# Hot30.php

namespace App\Virtual;

use App\Models\Post;

class Hot30 extends Virtual
{
    public function posts()
    {
        $ids = ...; // service
        return Post::whereIn('id', $ids);
    }
}

Hot7,Recommend同理. 这样我们又承接起了上面控制器的代码.这里的posts()的作用就相当于比如User.php中的posts()的作用,但是却更加的灵活.

鲁迅说过: 不要害怕在你的app下添加目录

我只是分享了一个简单的想法,更多的用法等着你来探索.

successful!

本作品采用《CC 协议》,转载必须注明作者和本文链接
我正在全力开发 nature 编程语言,如果我的文章对你有帮助,希望能获得一个 star,这对我的帮助非常大。
本帖由系统于 4年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 6

一直都在用 dingo/api,怪不得每次
Route::get('posts/{post}', 'PostController@show');
在show方法中模型绑定取到的值都是空的

7年前 评论
liyu001989

laravel5.5发布的路由模型绑定是一个非常强大的功能,然而dingo/api由于自定义了路由模块迟迟不支持该功能,直到摆脱了dingo/api后, 才能体验一把路由模型绑定.

错误:

  1. 5.1 已经有了路由模型绑定,5.2增加了隐式绑定;
  2. 路由模型绑定通过 bindings 中间件完成,dingo/api 中增加该中间件即可
6年前 评论

@liyu001989 哈哈,知道了. 我之前看到这个 https://github.com/dingo/api/issues/1506 当时还没有解决

6年前 评论

鲁迅:我没说过 [doge]

6年前 评论

Route::get('collections/{collections}/posts', 'PostController@index') // 某个专题下的帖子

{collections}?{collection}

6年前 评论

@bottleleong 笔误,应该是{collection}

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!