Laravel 之道特别篇 3: 模型式 Web API 接口开发流程
导语
这篇文章是我使用 Laravel 开发 Web Api 总结的自己一套开发流程。
记录在此,方便回顾。
同时,将自己的经验分享给大家。
这套流程,可能稍微复杂,对软件设计方面支持还有待提高。
欢迎童鞋们,提出指正意见。
模型式 Web Api 接口开发。
先理一下概念,模型式的意思是指:将控制器逻辑做高度封装,放到模型父类中,通过控制器链式调用,简化代码。
-
关于 Api 接口的数据流程
调用我们自己开发的 Web Api。无非就两个概念:Request(请求) 和 Response(响应)
结合请求参数,请求类型(操作类--增、删、改,获取类--查)等问题进行详细分析,所有接口基本分为以下流程
- 第一步,对请求进行 身份验证,这步操作基本由 JWT 或其他身份验证方式负责,我们基本不用对其进行开发,尽管用就行
- 第二步,身份通过后,我们首先应该做的工作就是 表单验证
- 第三步,表单数据验证通过后,有些情况不能直接对表单数据进行使用,还需要对其 进行加工
- 第四步,拿到加工好的表单数据后,我们才能利用这些数据进行 数据库存储、数据库查询、第三方请求等操作
- 第五步,如果是获取类请求,我们还需要对数据库进行查询操作,数据库查询可能涉及模型关联、分页等复杂操作,有的还需要请求第三方
- 第六步,如果是操作类请求,我们要 生成数据模型,保存数据,更改数据,删除数据等
- 第七步,生成响应数据,在我们刚拿到响应数据时,可能数据不是前端需要的格式,我们还需要对其进行最后的 加工
- 第八步,发送响应数据给前端
-
承上启下
上面是我对一个请求周期的一些见解,不足之处,请多多指正。下面我们具体看一下如何进行 模型式开发
模型父类 Model.php
首先,我们需要一个模型父类,而这个父类继承 Laravel 的模型基类。具体模型子类则继承自此模型父类。说白一点,就是在模型继承中插入一个模型类,能够做到修改 Laravel 模型基类,而不影响其它开发者使用模型基类。
控制器大部分涉及到的代码逻辑,全部封装到这个模型父类中,通过 控制器链式调用,模型具体配置逻辑代码所需参数。从而简化了代码,规范了控制器代码逻辑流程,同时,实现逻辑方法的高度复用。
具体 模型父类 的代码内容如下(稍微有些复杂):
app/Model.php
<?php declare(strict_types=1);
namespace App;
use Closure;
use App\Utils\Auth;
use App\Utils\Helper;
use App\Utils\Response;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Eloquent\Model as BaseModel;
class Model extends BaseModel
{
// ------------------------ 定义基础属性 --------------------------
/**
* 加载辅助 Trait 类:可自行修改定义,根据项目情况可多加载几个
*/
use Auth,
Helper,
Response;
/**
* 数据容器:存放请求数据、模型生成临时数据、响应数据等
*
* @var array
*/
protected $data = [];
/**
* 应该被转换成原生类型的属性。
*
* @var array
*/
protected $casts = [
'created_at' => 'string',
'updated_at' => 'string'
];
/**
* 数据表前缀
*
* @var string
*/
protected $prefix = 'blogger_';
protected $_table = '';
/**
* Model constructor.
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
if ($this->_table) {
$this->table = $this->prefix . $this->_table;
}
parent::__construct($attributes);
}
// ------------------------ 定义基础数据容器操作方法 --------------------------
/**
* 存储数据:向数据容器中合并数据,一般在方法结尾处使用
*
* @param array $data 经过系统 compact 方法处理好的数组
* @return Model
*/
protected function compact(array $data): Model
{
$this->data = $data + $this->data;
return $this;
}
/**
* 获取数据:从数据容器中获取数据,一般在方法开始,或方法中使用
*
* @param string $keys
* @param null $default
* @param null $container
* @return mixed
*/
protected function extract($keys = '', $default = null, $container = null)
{
if (is_string($keys)) {
$keys = explode('.', $keys);
}
$key = array_shift($keys);
if ($keys) {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
return $this->extract($keys, $default, $this->data[$key]);
} else {
return $default;
}
} else {
if (array_key_exists($key, $container)) {
return $this->extract($keys, $default, $container[$key]);
} else {
return $default;
}
}
} else {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
if (is_array($this->data[$key])) {
return collect($this->data[$key]);
}
return $this->data[$key];
} else {
return $default;
}
} else {
if (array_key_exists($key, $container)) {
if (is_array($container[$key])) {
return collect($container[$key]);
}
return $container[$key];
} else {
return $default;
}
}
}
}
/**
* 数据容器对外接口
*
* @param string $keys
* @param null $default
* @return array|mixed|null
*/
public function data(string $keys = '', $default = null)
{
return $this->extract($keys, $default);
}
/**
* 追加数据
*
* @param $keys
* @param $data
* @param null $container
* @return $this|array|null
*/
protected function add($keys, $data, $container = null)
{
if (is_string($keys)) {
$keys = explode('.', $keys);
}
$key = array_shift($keys);
if ($keys) {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
$this->data = Collection::make($this->data)->map(function ($item, $originalKey) use ($key, $keys, $data) {
if ($originalKey == $key) {
return $this->add($keys, $data, $item);
}
return $item;
})->all();
} else {
$this->data[$key] = $this->add($keys, $data, []);
}
} else {
if (array_key_exists($key, $container)) {
return Collection::make($container)->map(function ($item, $originalKey) use ($key, $keys, $data) {
if ($originalKey == $key) {
return $this->add($keys, $data, $item);
}
return $item;
})->all();
} else {
$container[$key] = $this->add($keys, $data, []);
return $container;
}
}
} else {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
if ($data instanceof Closure) {
$this->data[$key] = array_merge($this->data[$key], $data());
} else {
$this->data[$key] = array_merge($this->data[$key], $data);
}
} else {
if ($data instanceof Closure) {
$this->data[$key] = $data();
} else {
$this->data[$key] = $data;
}
}
} else {
if (array_key_exists($key, $container)) {
if ($data instanceof Closure) {
$container[$key] = array_merge($container[$key], $data());
} else {
$container[$key] = array_merge($container[$key], $data);
}
} else {
if ($data instanceof Closure) {
$container[$key] = $data();
} else {
$container[$key] = $data;
}
}
return $container;
}
}
return $this;
}
/**
* 替换数据
*
* @param $keys
* @param $data
* @param null $container
* @return $this|array|null
*/
protected function replace($keys, $data, $container = null)
{
if (is_string($keys)) {
$keys = explode('.', $keys);
}
$key = array_shift($keys);
if ($keys) {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
$this->data = Collection::make($this->data)->map(function ($item, $originalKey) use ($key, $keys, $data) {
if ($originalKey == $key) {
return $this->replace($keys, $data, $item);
}
return $item;
})->all();
} else {
abort(422, '数据错误');
}
} else {
if (array_key_exists($key, $container)) {
return Collection::make($container)->map(function ($item, $originalKey) use ($key, $keys, $data) {
if ($originalKey == $key) {
return $this->replace($keys, $data, $item);
}
return $item;
})->all();
} else {
abort(422, '数据错误');
}
}
} else {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
if ($data instanceof Closure) {
$this->data[$key] = $data();
} else {
$this->data[$key] = $data;
}
} else {
abort(422, '数据错误');
}
} else {
if (array_key_exists($key, $container)) {
if ($data instanceof Closure) {
$container[$key] = $data();
} else {
$container[$key] = $data;
}
} else {
abort(422, '数据错误');
}
return $container;
}
}
return $this;
}
/**
* 销毁数据
*
* @param $keys
* @param null $container
* @return $this|array|null
*/
protected function unset($keys, $container = null)
{
if (is_string($keys)) {
$keys = explode('.', $keys);
}
$key = array_shift($keys);
if ($keys) {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
$this->data = Collection::make($this->data)->map(function ($item, $originalKey) use ($key, $keys) {
if ($originalKey == $key) {
return $this->unset($keys);
}
return $item;
})->all();
}
} else {
if (array_key_exists($key, $container)) {
return Collection::make($container)->map(function ($item, $originalKey) use ($key, $keys) {
if ($originalKey == $key) {
return $this->unset($keys);
}
return $item;
})->all();
} else {
return $container;
}
}
} else {
if ($container === null) {
if (array_key_exists($key, $this->data)) {
unset($this->data[$key]);
}
} else {
if (array_key_exists($key, $container)) {
unset($container[$key]);
}
return $container;
}
}
return $this;
}
// ------------------------ 请求开始:验证数据,加工数据 --------------------------
/**
* 请求:使用模型的入口
*
* @param string $method 控制器方法名称
* @param array $request 请求的原始数据
* @param BaseModel|null $origin fetch 起源模型。
* @return Model
*/
protected function request(string $method, array $request = [], BaseModel $origin = null): Model
{
return $this->compact(compact('method', 'request', 'origin'))
->validate()
->process()
->trim();
}
/**
* 验证数据:根据 rule 方法返回的规则,进行数据验证
*
* @return Model
*/
protected function validate(): Model
{
$rules = $this->rule();
if (!$rules) return $this;
$validator = Validator::make($this->extract('request')->all(), $rules['rules'], config('message'), $rules['attrs']);
if (isset($rules['sometimes']) && count($rules['sometimes'])) {
foreach ($rules['sometimes'] as $v) {
$validator->sometimes(...$v);
}
}
$validator->validate();
return $this;
}
/**
* 数据验证规则:此方法将在 validate 方法中被调用,用以获取验证规则
*
* 注:此方法需根据情况,在子模型中重写
*
* @return array
*/
protected function rule(): array
{
return [];
}
/**
* 加工请求:请求数据验证通过后,用此方法进行数据加工与方法派遣操作
*
* 注:此方法需根据情况,在子模型中重写
*
* @return Model
*/
protected function process(): Model
{
return $this;
}
/**
* 修剪参数
*
* @return Model
*/
protected function trim(): Model
{
return $this->replace('request', $this->extract('request')->map(function ($value) {
if (is_string($value)) {
return trim($value);
}
return $value;
})->all());
}
// ------------------------ 操作类请求:映射字段、生成模型、保存数据 --------------------------
/**
* 生成数据模型:此方法定义一个统一名为 model 的对外接口,建议在控制器中调用
*
* @return Model
*/
public function model(): Model
{
return $this->createDataModel();
}
/**
* 生成数据模型(内置方法)
*
* @return Model
*/
protected function createDataModel(): Model
{
$request = $this->extract('request');
$maps = $this->map();
$model = $this->extract('origin') ?: $this;
if (!$maps) return $this;
foreach ($request->keys() as $v) {
if (array_key_exists($v, $maps)) {
$k = $maps[$v];
$model->$k = $request[$v];
}
}
return $model;
}
/**
* 数据映射:请求字段 => 数据库字段 的映射,用以生成含有数据的数据表模型
*
* 注:此方法需根据情况,在子模型中重写
*
* @return array
*/
protected function map(): array
{
return [];
}
/**
* 保存模型:同 save 方法,可重写 save 逻辑,而不影响原 save,保证其它模块正常工作
*
* @param array $options
* @return Model
*/
public function reserve(array $options = []): Model
{
if ($this->save($options)) {
return $this;
} else {
abort(422, '保存失败');
}
}
// ------------------------ 获取类请求:根据规则和映射获取数据,加工数据,返回数据 --------------------------
/**
* 取出数据:从数据库获取数据,建议在控制器中调用
*
* @param Closure|null $callback
* @return Model
* @throws \ReflectionException
*/
public function fetch(Closure $callback = null): Model
{
$map = $this->fetchMap();
if (!$map) return $this;
$gathers = $this->isShow()->getCollection()->getChainRule($this->extract('origin') ?: $this, $map['chain']);
if ($callback instanceof Closure) {
$gathers = $callback($gathers);
}
$response = [];
foreach ($gathers as $index => $gather) {
$response[$index] = $this->relationRecursion($map['data'], $gather, []);
}
return $this->compact(compact('response'));
}
/**
* 关系递归取值
*
* @param array $data
* @param Model $model
* @param array $response
* @return array
* @throws \ReflectionException
*/
protected function relationRecursion (array $data, Model $model, array $response)
{
foreach ($data as $key => $value) {
if ($key == '__method__') {
foreach ($value as $method => $param) {
if ($method == '__model__') {
$param($model);
} else {
$model->$method(...$param);
}
}
} else if (is_array($value)) {
foreach ($model->$key as $_index => $relevancy) {
$response[$key][$_index] = $this->relationRecursion($value, $relevancy, []);
}
} else if ($value instanceof Closure) {
$params = (new \ReflectionObject($value))
->getMethod('__invoke')
->getParameters();
if (!count($params)) {
$response[$key] = $value();
continue;
}
$params = collect($params)->map(function ($param) use ($model) {
$field = $param->name;
if ($field == 'model') {
return $model;
}
return $model->$field;
})->all();
$response[$key] = $value(...$params);
} else {
$response[$key] = $this->getDataRule($model, explode('.', $value));
}
}
return $response;
}
/**
* 区分展示详情或展示列表
*
* @return Model
*/
protected function isShow(): Model
{
$__isShow__ = $this->id ? true : false;
return $this->compact(compact('__isShow__'));
}
/**
* 获取集合
*
* @return Model
*/
protected function getCollection(): Model
{
return $this;
}
/**
* 取数据的映射规则
*
* 注:此方法需根据情况,在子模型中重写
*
* @return array
*/
protected function fetchMap(): array
{
return [];
}
/**
* 递归链式操作:封装查询构造器,根据数组参数调用查询构造顺序。
*
* @param array $chains
* @return object
*/
protected function getChainRule($model, array $chains)
{
if (!$chains) {
if ($this->extract('__isShow__')) {
return Collection::make([$model]);
}
return $model->get();
}
$chain = array_shift($chains);
$k = '';
foreach ($chain as $k => $v) {
$model = $model->$k(...$v);
}
if ($k == 'paginate') {
$page = [
'total' => $model->total(),
'lastPage' => $model->lastPage(),
];
$this->compact(compact('page'));
return $model;
} else if ($chains) {
return $this->getChainRule($model, $chains);
} else if ($this->extract('__isShow__')) {
return Collection::make([$model]);
} else {
return $model->get();
}
}
/**
* 递归取值:取关联模型的数据
*
* @return mixed
*/
protected function getDataRule($gather, array $rules)
{
$rule = array_shift($rules);
$gather = $gather->$rule;
if ($rules) {
return $this->getDataRule($gather, $rules);
} else {
return $gather;
}
}
// ------------------------ 响应数据 --------------------------
/**
* 发送响应:请在控制器调用,操作类请求传 message,获取类请求不要传 message
*
* @param null $message
* @return JsonResponse
*/
public function response($message = null): JsonResponse
{
if ($message !== null) {
$this->setMessage($message);
} else {
$this->epilogue();
}
return $this->send();
}
/**
* 操作类请求设置操作成功的 message
*
* @param null $message
* @return Model
*/
protected function setMessage($message = null): Model
{
$response = [
'code' => 200,
'message' => $message !== null ? $message : 'success',
];
return $this->compact(compact('response'));
}
/**
* 收尾:对获取的数据进行最后加工
*
* @return Model
*/
protected function epilogue(): Model
{
return $this->replace('response', $this->success($this->extract('response')));
}
/**
* 发送数据
*
* @return JsonResponse
*/
protected function send(): JsonResponse
{
return response()->json($this->extract('response'));
}
/**
* Handle dynamic method calls into the model.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement', 'request'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
}
来张图,加深一下认识
需要的前期准备
-
增加
message.php
配置文件相信仔细看的童鞋,validate 方法需要一个中文配置文件 message.php
具体代码如下:
config/message.php
<?php return [ 'accepted' => ':attribute必须为yes、on、 1、或 true', 'after' => ':attribute必须为:date之后', 'alpha' => ':attribute必须完全是字母的字符', 'alpha_dash' => ':attribute应为字母、数字、破折号( - )以及下划线( _ )', 'alpha_num' => ':attribute必须完全是字母、数字', 'array' => ':attribute必须为数组', 'before' => ':attribute必须为:date之后', 'between' => ':attribute大小必须在:min与:max之间', 'confirmed' => '两次:attribute不一致', 'date' => ':attribute不是日期格式', 'date_format' => ':attribute必须是:format格式', 'different:field' => ':attribute不能与:field相等', 'email' => ':attribute不是邮箱格式', 'integer' => ':attribute必须为整数', 'max' => ':attribute最大为:max', 'min' => ':attribute最小为:min', 'numeric' => ':attribute必须为数字', 'regex' => ':attribute格式错误', 'required' => ':attribute不能为空', 'required_if' => ':attribute不能为空', 'required_with' => ':attribute不能为空', 'required_with_all' => ':attribute不能为空', 'required_without' => ':attribute不能为空', 'required_without_all' => ':attribute不能为空 ', 'size' => ':attribute必须等于:value', 'string' => ':attribute必须为字符串', 'unique' => ':attribute已存在', 'exists' => ':attribute不存在', 'json' => ':attribute必须是JSON字符串', 'image' => ':attribute必须是一个图像', 'url' => ':attribute必须是合法的URL', ];
如图:
-
修改
App\Exceptions\Handler
类。主要拦截 表单验证 异常,通过 Api 返回的方式,发送给前端
app/Exceptions/Handler.php
<?php namespace App\Exceptions; use Exception; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Http\JsonResponse; use Illuminate\Validation\ValidationException; class Handler extends ExceptionHandler { /** * A list of the exception types that are not reported. * * @var array */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed for validation exceptions. * * @var array */ protected $dontFlash = [ 'password', 'password_confirmation', ]; /** * Report or log an exception. * * @param \Exception $exception * @return void */ public function report(Exception $exception) { parent::report($exception); } /** * 公用返回格式 * @param string $message * @param int $code * @return JsonResponse */ protected function response(string $message, int $code) :JsonResponse { $response = [ 'code' => $code, 'message' => $message, ]; return response()->json($response, 200); } /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return Object */ public function render($request, Exception $exception) { // 拦截表单验证异常,修改返回方式 if ($exception instanceof ValidationException) { $message = array_values($exception->errors())[0][0]; return $this->response($message, 422); } return parent::render($request, $exception); } }
如图:
具体使用,我们来举个例子
-
数据表结构如下图
这是一个关于文章的管理数据结构,其中文章基本信息和文章内容是分离两个表,做一对一关系;而文章与评论是一对多关系。这样做的好处是,获取文章列表时,无需查文章内容,有助于提高查询速度,因为内容一般是很大的。
-
先看一下模型类和控制器的位置
-
Api 路由
routes/api.php
<?php use Illuminate\Http\Request; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ // Laravel 自带路由,不用管 Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); // 文章管理 REST API。 Route::apiResource('article', 'ArticleController');
-
控制器代码
app/Http/Controllers/ArticleController.php
<?php namespace App\Http\Controllers; use App\Models\Article; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class ArticleController extends Controller { /** * Display a listing of the resource. * * @return JsonResponse */ public function index(Request $request) :JsonResponse { return Article::request('index', $request->all()) ->fetch() ->response(); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return JsonResponse */ public function store(Request $request) :JsonResponse { return Article::request('store', $request->all()) ->model() ->reserve() ->response('保存成功'); } /** * Display the specified resource. * * @param Article $article * @return JsonResponse */ public function show(Request $request, Article $article) :JsonResponse { return $article->request('show', $request->all()) ->fetch() ->response(); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param Article $article * @return JsonResponse */ public function update(Request $request, Article $article) :JsonResponse { return $article->request('update', $request->all()) ->model() ->reserve() ->response('修改成功'); } /** * Remove the specified resource from storage. * * @param Article $article * @throws * @return JsonResponse */ public function destroy(Article $article) :JsonResponse { return $article->request('destroy', []) ->delete() ->response('删除成功'); } }
-
Article 模型代码
app/Models/Article.php
<?php namespace App\Models; use App\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Facades\DB; class Article extends Model { /** * 与 Content 一对一 * * @return Relation */ public function content() :Relation { return $this->hasOne(Content::class)->withDefault(); } /** * 与 Comment 一对多 * * @return Relation */ public function comments() :Relation { return $this->hasMany(Comment::class); } /** * 数据验证规则:此方法将在 validate 方法中被调用,用以获取验证规则 * * @return array */ protected function rule() :array { switch ($this->extract('method')) { case 'store': return [ 'rules' => [ 'title' => 'required|string|max:140', 'content' => 'required|string', ], 'attrs' => [ 'title' => '文章标题', 'content' => '文章内容', ] ]; break; case 'update': return [ 'rules' => [ 'title' => 'required_without:content|string|max:140', 'content' => 'required_without:title|string', ], 'attrs' => [ 'title' => '文章标题', 'content' => '文章内容', ] ]; break; case 'index': case 'show': return [ 'rules' => [ 'page' => 'required|integer|min:1', 'num' => 'sometimes|integer|min:1', ], 'attrs' => [ 'page' => '页码', 'num' => '每页数量', ] ]; break; } return []; } /** * 加工请求:请求数据验证通过后,用此方法进行数据加工与方法派遣操作 * * @return Model */ protected function process() :Model { switch ($this->extract('method')) { case 'store': case 'update': $request = array_map(function ($item) { return trim($item); }, $this->extract('request')); return $this->compact(compact('request')); break; } return $this; } /** * 数据映射:请求字段 => 数据库字段 的映射,用以生成含有数据的数据表模型 * * @return array */ protected function map() :array { return [ 'title' => 'title', ]; } /** * 保存模型:同 save 方法,可重写 save 逻辑,而不影响原 save,保证其它模块正常工作 * * @param array $options * @return Model */ public function reserve(array $options = []) :Model { DB::beginTransaction(); if ( $this->save($options) && $this->content->request('store', $this->extract('request')) ->model() ->save() ) { DB::commit(); return $this; } else { DB::rollBack(); abort(422, '保存失败'); } } /** * 删除 * * @return $this|bool|null * @throws \Exception */ public function delete() { DB::beginTransaction(); if ( $this->content->delete() && parent::delete() ) { DB::commit(); return $this; } else { DB::rollBack(); abort(422, '删除失败'); } } /** * 取数据的映射规则 * * @return array */ protected function fetchMap() :array { switch ($this->extract('method')) { case 'index': return [ 'chain' => [ ['paginate' => [$this->extract('request.num', 10)]] ], 'data' => [ 'id' => 'id', 'title' => 'title', 'c_time' => 'created_at', 'u_time' => 'updated_at', ] ]; break; case 'show': return [ 'chain' => [ ['load' => ['content']], ['load' => [['comments' => function ($query) { $paginate = $query->paginate($this->extract('request.num', 10)); $page = [ 'total' => $paginate->total(), ]; $this->compact(compact('page')); }]]], ], 'data' => [ 'id' => 'id', 'title' => 'title', 'content' => 'content.content', 'comments' => [ 'id' => 'id', 'comment' => 'comment', ], 'c_time' => 'created_at', 'u_time' => 'updated_at', ] ]; break; } return []; } /** * 收尾:对获取的数据进行最后加工 * * @return Model */ protected function epilogue() :Model { switch ($this->extract('method')) { case 'index': $response = [ 'code' => 200, 'message' => '获取成功', 'data' => $this->extract('response'), 'total' => $this->extract('page.total'), 'lastPage' => $this->extract('page.lastPage'), ]; return $this->compact(compact('response')); break; case 'show': $response = ['comments' => [ 'total' => $this->extract('page.total'), 'data' => $this->extract('response.0.comments') ]] + $this->extract('response.0'); return $this->compact(compact('response')); break; } return $this; } }
-
Content 模型
app/Models/Content.php
<?php namespace App\Models; use App\Model; use Illuminate\Database\Eloquent\Relations\Relation; class Content extends Model { /** * @return Relation */ public function article() :Relation { return $this->belongsTo(Article::class); } /** * 数据映射:请求字段 => 数据库字段 的映射,用以生成含有数据的数据表模型 * * @return array */ protected function map() :array { return [ 'content' => 'content', ]; } }
-
Comment 模型
app/Models/Comment.php
<?php namespace App\Models; use App\Model; use Illuminate\Database\Eloquent\Relations\Relation; class Comment extends Model { /** * 与 Article 多对一 * * @return Relation */ public function article() :Relation { return $this->belongsTo(Article::class); } }
说一点
上面代码,我没有对 评论 写控制器,测试时,是在数据库手动添加的评论
最后,我们看一下 postman 检测结果
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: