优雅的使用路由模型绑定

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 协议》,转载必须注明作者和本文链接
本帖由系统于 2年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 6
liyu001989

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

错误:

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

鲁迅:我没说过 [doge]

5年前 评论

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

5年前 评论

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

5年前 评论

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

{collections}?{collection}

5年前 评论

@bottleleong 笔误,应该是{collection}

5年前 评论

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