Laravel 第六章 帖子数据

记录网址:laravel controller_那扎的博客-CSDN博客    

laravel controller介绍

记录语句:清空并重新自动加载

$ php artisan clear-compiled

$composer dump-autoload

一、分类列表

分类列表

在这个章节中,我们将开发帖子分类的获取接口,方便我们在下一章节『发布话题』中使用。

由于直接看当前第六章节,之前的内容中包含配置composer ,按照下方执行后一直报错 class ... does not exit

所以按照一下步骤进行配置

1)首先来搭建一下基础环境,创建一个基础 Controller,此类作为所有 API 请求控制器的【基类】。

$ php artisan make:controller Api/Controller

注意我们增加了一个命名空间 Api,以后接口相关的控制器,统一会放在 Api 目录中,会让代码结构更清晰。前面提到过接口版本控制的重要性,我们还可以在 Api 目录中增加 V1,V2 等目录,进一步区分不同版本的接口,为了教学方便,我们暂时不做进一步区分。 将 Controller.php 文件替换为以下的内容。

app/Http/Controllers/Api/Controller.php

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Dingo\Api\Routing\Helpers;
use App\Http\Controllers\Controller as BaseController;

class Controller extends BaseController
{
    use Helpers;
}

注意上面的基础代码中,我们增加了 DingoApi 的 helper,这个 trait 可以帮助我们处理接口响应,后面我们在使用到的时候,我们会对其提供的功能做一一讲解。我们新建的 Api 控制器都会继承这个基础控制器。

下面我们开始写第一个接口 发送短信验证码,先添加路由

routes/api.php

<?php

use Illuminate\Http\Request;

$api = app('Dingo\Api\Routing\Router');

$api->version('v1', [
    'namespace' => 'App\Http\Controllers\Api'
], function($api) {
    // 短信验证码
    $api->post('verificationCodes', 'VerificationCodesController@store')
        ->name('api.verificationCodes.store');
});

上面的代码中,我们增加了一个参数 namespace,使 v1 版本的路由都会指向 App\Http\Controllers\Api,方便我们书写路由。

接下来创建 VerificationCodes 的控制器

修改文件如以下:

app/Http/Controllers/Api/VerificationCodesController.php

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;

class VerificationCodesController extends Controller
{
    public function store()
    {
        return $this->response->array(['test_message' => 'store verification code']);
    }
}

通过 artisan 创建出来的控制器,默认会继承 App\Http\Controllers\Controller,我们只需要删除 use App\Http\Controllers\Controller 这一行即可,这样会继承相同命名空间下的 Controller,也就是我们上一步中添加的那个控制器。

增加了 store 方法,利用 DingoApi 的 Helpers trait,我们可以使用 $this->response->array 返回一个测试用的响应。接下来使用 PostMan 访问 发送短信验证码 接口,注意使用 POST 方式提交。

2)添加路由——routes/api.php

其中->get为调用get方法,get(param1, param2) param1:新建的Controller类  param2:调用的方法

->name  name则是对你的路由别名,与dingo/api 没有关系,是laravel 自带的功能

可以参考网址:路由 | 基础功能 |《Laravel 5.5 中文文档 5.5》| Laravel China 社区

$api->version('v1', [
    'namespace'=>'App\Http\Controllers\Api'
],function($api){
    $api->get('categories', 'CategoriesController@index')->name('api.categories.index');
});

3)创建Transformer  用于显示哪些数据(类似于创建类,类中有变量用于显示)

$ touch app/Transformers/CategoryTransformer.php

修改如下

app/Transformers/CategoryTransformer.php

<?php

namespace App\Transformers;

use App\Models\Category;
use League\Fractal\TransformerAbstract;

class CategoryTransformer extends TransformerAbstract
{
    public function transform(Category $category)
    {
        return [
            'id' => $category->id,
            'name' => $category->name,
            'description' => $category->description,
        ];
    }
}

4)、创建controller  由于需要大量的处理代码处理返回信息和操作,所以将有共同特征的路由处理函数放到一个共同的控制器中

php artisan make:controller Api/CategoriesController

修改如下

app/Http/Controllers/Api/CategoriesController.php

<?php

namespace App\Http\Controllers\Api;

use App\Models\Category;
use Illuminate\Http\Request;
use App\Transformers\CategoryTransformer;

class CategoriesController extends Controller
{
    public function index()
    {
        return $this->response->collection(Category::all(), new CategoryTransformer());
    }
}

分类数据是集合,所以我们使用 $this->response->collection 返回数据。

1. 添加路由

回忆一下 Larabbs 的模型关系,每一个话题一定会属于某个分类,所以发布话题的时候,我们会从列表中选择一个分类,APP 首页也会显示出来不同分类便签,便于切换。

新增 分类列表 接口,分类列表是游客可访问的接口,不需要 token 验证。

routes/api.php

$api->version('v1', [
    'namespace' => 'App\Http\Controllers\Api',
    'middleware' => 'serializer:array'
], function($api) {

    $api->group([
        'middleware' => 'api.throttle',
        'limit' => config('api.rate_limits.sign.limit'),
        'expires' => config('api.rate_limits.sign.expires'),
    ], function($api){
        // 短信验证码
        $api->post('verificationCodes', 'VerificationCodesController@store')
            ->name('api.verificationCodes.store');
        //用户注册
        $api->post('users', 'UsersController@store')
            ->name('api.users.store');
        //图片验证码
        $api->post('captchas', 'CaptchasController@store')
            ->name('api.captchas.store');
    });


    $api->group([
        'middleware' => 'api.throttle',
        'limit' => config('api.rate_limits.access.limit'),
        'expires' => config('api.rate_limits.access.expires'),
    ], function($api){
        //游客可以访问的接口

        // 需要 token 验证的接口
        $api->group(['middleware' => 'api.auth'], function($api) {
            // 当前登录用户信息
            $api->get('user', 'UsersController@me')
                ->name('api.user.show');
            //图片资源
            $api->post('images', 'ImagesController@store')
                ->name('api.images.store');
        });
    });

    //游客可以访问的接口
    $api->get('categories', 'CategoriesController@index')
        ->name('api.categories.index');
});

2. 创建 Transformer

$ touch app/Transformers/CategoryTransformer.php

修改如下

app/Transformers/CategoryTransformer.php

<?php

namespace App\Transformers;

use App\Models\Category;
use League\Fractal\TransformerAbstract;

class CategoryTransformer extends TransformerAbstract
{
    public function transform(Category $category)
    {
        return [
            'id' => $category->id,
            'name' => $category->name,
            'description' => $category->description,
        ];
    }
}

3. 创建 controller

php artisan make:controller Api/CategoriesController

修改如下

app/Http/Controllers/Api/CategoriesController.php

<?php

namespace App\Http\Controllers\Api;

use App\Models\Category;
use Illuminate\Http\Request;
use App\Transformers\CategoryTransformer;

class CategoriesController extends Controller
{
    public function index()
    {
        return $this->response->collection(Category::all(), new CategoryTransformer());
    }
}

分类数据是集合,所以我们使用 $this->response->collection 返回数据。

4. 测试一下

使用 PostMan 调试一下接口并保存接口,实现数据读取的接口遵循以下流程:

  1. 增加路由
  2. 创建 transformer
  3. controller 处理数据,使用 transformer 转换后返回

提交代码

$ git add -A
$ git commit -m 'categories index'


二、发布话题

需要填写话题标题,话题内容,选择话题分类。如果这个话题内容,可以上传图片,插入 markdown 图片链接,则可以调用我们上一节写的 添加图片 接口,type 为 topic,将返回的图片 path 插入到话题内容中。

1. 增加路由

只有登录用户才可以发布话题,添加路由。

routes/api.php

$api->version('v1', [
    'namespace' => 'App\Http\Controllers\Api',
    'middleware' => 'serializer:array'
], function($api) {

    $api->group([
        'middleware' => 'api.throttle',
        'limit' => config('api.rate_limits.sign.limit'),
        'expires' => config('api.rate_limits.sign.expires'),
    ], function($api){
        // 短信验证码
        $api->post('verificationCodes', 'VerificationCodesController@store')
            ->name('api.verificationCodes.store');
        //用户注册
        $api->post('users', 'UsersController@store')
            ->name('api.users.store');
        //图片验证码
        $api->post('captchas', 'CaptchasController@store')
            ->name('api.captchas.store');
    });


    $api->group([
        'middleware' => 'api.throttle',
        'limit' => config('api.rate_limits.access.limit'),
        'expires' => config('api.rate_limits.access.expires'),
    ], function($api){
        //游客可以访问的接口

        // 需要 token 验证的接口
        $api->group(['middleware' => 'api.auth'], function($api) {
            // 当前登录用户信息
            $api->get('user', 'UsersController@me')
                ->name('api.user.show');
            //图片资源下方加入
            $api->post('images', 'ImagesController@store')
                ->name('api.images.store');

            //发布话题
            $api->post('topics', 'TopicsController@store')
                ->name('api.topics.store');
        });
    });

    //游客可以访问的接口
    $api->get('categories', 'CategoriesController@index')
        ->name('api.categories.index');
});

2. 增加 Request

创建 TopicRequest:

$ php artisan make:request Api/TopicRequest

如下修改:

app/Http/Requests/Api/TopicRequest.php

<?php

namespace App\Http\Requests\Api;

use Dingo\Api\Http\FormRequest;

class TopicRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'title' => 'required|string',
            'body' => 'required|string',
            'category_id' => 'required|exists:categories,id',
        ];
    }

    public function attributes()
    {
        return [
            'title' => '标题',
            'body' => '话题内容',
            'category_id' => '分类',
        ];
    }
}

3. 增加 Transformer

$ touch app/Transformers/TopicTransformer.php

修改如下

app/Transformers/TopicTransformer.php

<?php

namespace App\Transformers;

use App\Models\Topic;
use League\Fractal\TransformerAbstract;

class TopicTransformer extends TransformerAbstract
{
    public function transform(Topic $topic)
    {
        return [
            'id' => $topic->id,
            'title' => $topic->title,
            'body' => $topic->body,
            'user_id' => (int) $topic->user_id,
            'category_id' => (int) $topic->category_id,
            'reply_count' => (int) $topic->reply_count,
            'view_count' => (int) $topic->view_count,
            'last_reply_user_id' => (int) $topic->last_reply_user_id,
            'excerpt' => $topic->excerpt,
            'slug' => $topic->slug,
            'created_at' => $topic->created_at->toDateTimeString(),
            'updated_at' => $topic->updated_at->toDateTimeString(),
        ];
    }
}

4. 增加 Controller

$ php artisan make:controller Api/TopicsController

修改文件

app/Http/Controllers/Api/TopicsController.php

<?php

namespace App\Http\Controllers\Api;

use App\Models\Topic;
use Illuminate\Http\Request;
use App\Transformers\TopicTransformer;
use App\Http\Requests\Api\TopicRequest;

class TopicsController extends Controller
{
    public function store(TopicRequest $request, Topic $topic)
    {
        $topic->fill($request->all());
        $topic->user_id = $this->user()->id;
        $topic->save();

        return $this->response->item($topic, new TopicTransformer())
            ->setStatusCode(201);
    }
}

5. PostMan 调试

代码版本控制

$ git add -A
$ git commit -m 'topics store'


三、修改话题

本章节我们将一起开发修改话题信息的 API。

1. 添加路由

routes/api.php

.
.
.
// 发布话题下方加入
$api->post('topics', 'TopicsController@store')
    ->name('api.topics.store');

//修改话题
$api->patch('topics/{topic}', 'TopicsController@update')
    ->name('api.topics.update');
.
.
.

2. 修改 Request

app/Http/Requests/Api/TopicRequest.php

.
.
.
public function rules()
{
    switch($this->method()) {
        case 'POST':
            return [
                'title' => 'required|string',
                'body' => 'required|string',
                'category_id' => 'required|exists:categories,id',
            ];
            break;
        case 'PATCH':
            return [
                'title' => 'string',
                'body' => 'string',
                'category_id' => 'exists:categories,id',
            ];
            break;
    }
}
.
.
.

3. 修改 Controller

app/Http/Controllers/Api/TopicsController.php

.
.
.
public function update(TopicRequest $request, Topic $topic)
{
    $this->authorize('update', $topic);

    $topic->update($request->all());
    return $this->response->item($topic, new TopicTransformer());
}
.
.
.

4. PostMan 调试

首先我们输入一个不存在的话题 id,想象中应该会返回 404

但是结果却是 TopicTransformer 报错了,好像是路由模型绑定出了问题。没错,因为路由交给 DingoApi 来处理了,所以模型绑定的中间件并没有注册上。手动增加 bindings 中间件。

routes/api.php

.
.
.
$api->version('v1', [
    'namespace' => 'App\Http\Controllers\Api',
    'middleware' => ['serializer:array', 'bindings']
], function ($api) {
.
.
.

5. API 的异常处理

状态码正确,但是 message 不是太好,暴露了一些代码细节,关于异常处理,DingoApi 提供了方法,让我们手动处理异常。

app/Providers/AppServiceProvider.php

.
.
.
public function register()
{
    if (app()->isLocal()) {
        $this->app->register(\VIACreative\SudoSu\ServiceProvider::class);
    }

    \API::error(function  (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException  $exception)  {
        throw  new  \Symfony\Component\HttpKernel\Exception\HttpException(404,  '404 Not Found');  
    });
}
.
.
.

再次调用,查看结果

如果你使用的是 dingo 2.2.3 以下的版本,状态码是会是 500。

可以使用如下代码 :

  \API::error(function (\Illuminate\Database\Eloquent\ModelNotFoundException $exception)   {
        abort(404);
 });

6. 用户权限控制

但是我们尝试修改别人的话题,这里我们使用 id 为 14 的用户,修改 id 为 1 的话题。

注意不要使用有 manage_contents 权限的用户,也就是 id 为 1 ,2 的用户,他们有管理内容的权限,所以可以修改任何人的话题。

同样的,状态码不太符合预期,403 可能更加合适,依然可以在 AppServiceProvider 中手动处理。

app/Providers/AppServiceProvider.php

.
.
.
public function register()
{
    if (app()->isLocal()) {
        $this->app->register(\VIACreative\SudoSu\ServiceProvider::class);
    }

    \API::error(function (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
        abort(404);
    });

    \API::error(function (\Illuminate\Auth\Access\AuthorizationException $exception) {
        abort(403, $exception->getMessage());
    });
}
.
.
.

再次调用,符合预期,最后尝试修改自己的话题

修改成功,注意 patch 需要使用 x-www-form-urlencoded

代码版本控制

$ git add -A
$ git commit -m 'topic update api'


四、删除话题

本章节我们将开发删除话题的 API 功能。

1. 添加路由

routes/api.php

.
.
.
// 发布话题
$api->post('topics', 'TopicsController@store')
    ->name('api.topics.store');

//修改话题
$api->patch('topics/{topic}', 'TopicsController@update')
    ->name('api.topics.update');

//删除话题
$api->delete('topics/{topic}', 'TopicsController@destroy')
    ->name('api.topics.destroy');
.
.
.

2. 修改 Controller

.
.
.
public function destroy(Topic $topic)
{
    $this->authorize('destroy', $topic);

    $topic->delete();
    return $this->response->noContent();
}
.
.
.

注意这里我们使用的是 destroy 的权限控制。

3. PostMan 调试

删除成功

提交代码

$ git add -A
$ git commit -m 'topic delete api'


五、话题列表

可以通过分类搜索,根据创建时间及最新回复排序,列表显示了用户信息,话题信息,分类信息。

1. 添加路由

话题列表游客可以访问

routes/api.php

    //游客可以访问的接口
    //分类列表
    $api->get('categories', 'CategoriesController@index')
        ->name('api.categories.index');

    //话题列表
    $api->get('topics', 'TopicsController@index')
        ->name('api.topics.index');

2. 修改 Controller

app/Http/Controllers/Api/TopicsController.php

.
.
.
    public function index(Request $request, Topic $topic)
    {
        $query = $topic->query();

        if ($categoryId = $request->category_id) {
            $query->where('category_id', $categoryId);
        }

        // 为了说明 N+1问题,不使用 scopeWithOrder
        switch ($request->order) {
            case 'recent':
                $query->recent();
                break;

            default:
                $query->recentReplied();
                break;
        }

        $topics = $query->paginate(20);

        return $this->response->paginator($topics, new TopicTransformer());
    }
.
.
.

PostMan 调用,返回了话题列表,并且有分页数据

Include 机制  获取额外的资源数据

话题数据我们有了,但是发布话题的 用户数据,以及话题的 分类数据 还没有,那么

  • 如何获取额外的资源数据?
  • 资源数据该以什么样的结构返回?

其实 DingoApi 已经很好的解决了这两个问题。
首先修改 TopicTransformer 如下

app/Transformers/TopicTransformer.php

<?php

namespace App\Transformers;

use App\Models\Topic;
use League\Fractal\TransformerAbstract;

class TopicTransformer extends TransformerAbstract
{
    protected $availableIncludes = ['user', 'category'];//为可以嵌套的额外资源有 user 和 category。

    public function transform(Topic $topic)
    {
        return [
            'id' => $topic->id,
            'title' => $topic->title,
            'body' => $topic->body,
            'user_id' => $topic->user_id,
            'category_id' => $topic->category_id,
            'reply_count' => $topic->reply_count,
            'view_count' => $topic->view_count,
            'last_reply_user_id' => $topic->last_reply_user_id,
            'excerpt' => $topic->excerpt,
            'slug' => $topic->slug,
            'created_at' => $topic->created_at->toDateTimeString(),
            'updated_at' => $topic->updated_at->toDateTimeString(),
        ];
    }

    public function includeUser(Topic $topic)
    {
        return $this->item($topic->user, new UserTransformer());
    }

    public function includeCategory(Topic $topic)
    {
        return $this->item($topic->category, new CategoryTransformer());
    }
}

上面的代码中,我们首先设置了 protected $availableIncludes = ['user', 'category'],可以理解为可以嵌套的额外资源有 user 和 category。那么额外的资源如何获取,如何转换,则通过 includeUser 和 includeCategory 确定,availableIncludes 中的每一个参数都对应一个具体的方法,方法命名规则为 include + user 、 include + category 驼峰命名。

那么什么时候才会引入额外的资源呢,由客户端提交的 include 参数指定,多个参数通过逗号分隔。

url 中增加 include=user 参数,调用 话题列表 接口,可以看到用户数据,并且是通过 UserTransformer 转换过后的数据,url 中增加 include=user,category 参数,调用 话题列表 接口,可以看到数据中增加了 user 和 category

UserTransformer 和 CategoryTransformer 在这里被复用,通常情况,每个资源只需要写好一个对应的 Transformer 即可。

整理一下完整的流程

  • 访问 api/topics?include=user,category
  • TopicsController 中查询出来话题,最终使用 return $this->response->paginator($topics, new TopicTransformer()); 返回响应。
  • TopicTransformer 处理每一个 话题模型,使用 transform 方法转化模型数据
  • 因为你请求中指定了 include=user,所以 TopicTransformer 自动调用 includeUser 方法。
  • includeUser 方法中使用,查询到用户数据 $topic->user ,通过 UserTransformer 格式化用户数据
  • 重复上面两步,处理 category 分类相关的数据
  • 最终 Fractal 帮我们整理好资源之间的嵌套关系,返回响应。

在 Transformer 中,我们可以使用:

  • $this->item () 返回单个资源
  • $this->collection () 返回集合资源

Include 让资源与资源之间以一种合理的嵌套返回,同时什么时候返回完全由请求参数决定,这让资源数据更加灵活。

N+1 问题以及查询日志

你可能会发现,我们并没有手动通过 with 或者 load 预加载模型关系,那么会不会带来 N+1 问题呢。首先我们需要输出 sql 查询日志,laravel-query-logger 是安正超写的一个查询日志组件,先来安装它

$ composer require overtrue/laravel-query-logger --dev

打开日志,再次调用接口 http://larabbs.test/api/topics?include=user,category

$ tail -f ./storage/logs/laravel.log

并没有产生 N+1 问题,因为当我们返回的是集合的时候,DingoApi 根据 include 参数以及 defaultInclude 帮助我们进行预加载,所以大部分情况我们不需要手动处理。如果遇到复杂的嵌套及关系加载,可以 app(\Dingo\Api\Transformer\Factory::class)->disableEagerLoading(); 临时关闭 DingoApi 的预加载,手动处理。

某个用户发布的话题列表

除了首页不同分类的话题列表,我们还可能查看某个用户发布的所有话题

1. 添加路由

某个用户的发布的话题,同样是游客可访问的

routes/api.php

.
.
.
//话题列表下方添加
    $api->get('topics', 'TopicsController@index')
        ->name('api.topics.index');

    //某个用户发布的话题
    $api->get('users/{user}/topics', 'TopicsController@userIndex')
        ->name('api.users.topics.index');
.
.
.

2. 修改 Controller

按创建时间倒叙返回该用户所有的话题。

app/Http/Controllers/Api/TopicsController.php

.
.
.
use App\Models\User;//需要添加 Models\User
.
.
.
//添加指定某个用户方法
public function userIndex(User $user, Request $request)
{
    $topics = $user->topics()->recent()
        ->paginate(20);

    return $this->response->paginator($topics, new TopicTransformer());
}
.
.
.

PostMan 调用接口

代码版本控制

$ git add -A
$ git commit -m 'topic index'


六、话题详情——根据话题ID获取话题详情及用户和种类详情

1. 添加路由

routes/api.php

.
.
.
$api->get('topics', 'TopicsController@index')
    ->name('api.topics.index');

//话题详情
$api->get('topics/{topic}', 'TopicsController@show')
    ->name('api.topics.show');
.
.
.

2. 修改 controller

app/Http/Controllers/Api/TopicsController.php

.
.
.
public function show(Topic $topic)
{
    return $this->response->item($topic, new TopicTransformer());
}
.
.
.

PostMan 调用接口,可以看到,当 Transformer 准备好了之后,开发相关的接口是很方便的。详情接口依然可以使用 include 参数。

代码版本控制

$ git add -A
$ git commit -m 'topics show'
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值