如上图,本文讲述
Laravel5.5 LTS
为例子,怎么合理的开发RESTful API
接口(真正意义上的前后端分离),因为博客正在开发,所以文章是持续更新的....
*注: 本文全部是自己手打的原创,基本上是文字比较多,所以大多都是要理解,有看不懂留言详解(另外大神们可以随意指点,我都会虚心接受)
1.Laravel底层核心架构Contracts契约的理解和使用
理解契约
契约用在哪里?契约哪里好?为什么要契约?这也是当初我带着这些疑问去尝试使用的问题
契约用官方解释是:Laravel 的契约是一组定义框架提供的核心服务的接口其实就是:
通过服务提供者去绑定接口类,然后在Services层实现方法,用Trait多继承特性,注入到控制器继承方法里使用
关键字服务提供者
、延迟提供器
、Trait
、依赖注入(DI)
、Io容器
,请读者先了解一下这几个关键字再使用契约,已知的大神们请忽略这句话。
使用契约
-
由于框架没有对契约的
php artisan
命令的支持,我们需要手动生成Contracts文件夹并新建.php
文件
App\Contracts\Response.php
<?php
namespace App\Contracts;
interface Response
{
/**
* SUCCESS - 操作成功
*/
public function success(string $message = '', array $data = []);
/**
* Error - 失败
*/
public function error(string $message = '', array $data = []);
}
- 聪明的人到这里大概知道我们契约用在了什么地方了吧~
创建完文件夹和定义完方法后,我们要对此接口进行实现类方法
就需要手动创建一个Services
层
Services文件目录:app\Services\ResponseService.php (这里有个坑要注意类名和方法名一定要一致)
<?php
namespace App\Services;
use App\Contracts\Response;
class ResponseService implements Response
{
/**
* SUCCESS - 操作成功
* @param $message string
* @return json
*/
public function success(string $message = '', array $data = [])
{
return response()->json([
'status' => true,
'code' => 200,
'message' => empty($message) ? config('response.success') : $message,
'data' => $data,
]);
}
/**
* Error - 失败
* @param $message string
* @return json
*/
public function error(string $message = '', array $data = [])
{
return response()->json([
'status' => false,
'code' => 404,
'message' => empty($message) ? config('response.error') : $message,
'data' => $data,
]);
}
}
- 然后用Laravel的核心架构
服务提供者
,去绑定这两者的关系,并且在config\app
中providers
数组中注册
服务提供者支持artisan命令:php artisan make:provider ResponseServiceProvider
'providers' => [
...
//注册Contracts契约接口
App\Providers\ResponseServiceProvider::class,
],
- 进入到刚才创建的
ResponseServiceProvider.php
文件中写入以下内容:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\Response as ResponseContracts; // 引入契约
use App\Services\ResponseService; // 引入实现接口类
/**
* Response - 延迟提供器
* (推迟加载这种提供器会提高应用程序的性能,因为它不会在每次请求时都从文件系统中加载)
*
* Class ResponseServiceProvider
* @package App\Providers
* @author SuperHao - 619596123@qq.com
*/
class ResponseServiceProvider extends ServiceProvider
{
/**
* 延迟提供器 - 是否延时加载提供器。
*
* @var bool
*/
protected $defer = true;
/**
* 引导任何应用程序服务
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* 在容器中注册绑定
* Register the application services.
*
* @return void
*/
public function register()
{
// ======================绑定方式①====================== //
// 给这个接口一个别名
// $this->app->bind('MResponse','App\Contracts\Response');
// 将Contracts接口和它的实现类绑定
// $this->app->bind('App\Contracts\Response','App\Services\ResponseService');
// ======================绑定方式②====================== //
/**
* singleton 单例绑定
*
* @param \App\Contracts\Response 契约接口类
* @param \App\Services\ResponseService 接口实现类
*/
$this->app->singleton(ResponseContracts::class, ResponseService::class);
/**
* 起别名
*
* @param \App\Contracts\Response 契约接口类
* @param string $name
*/
$this->app->alias(ResponseContracts::class, 'MResponse');
}
/**
* 延迟提供器 - 获取提供器提供的服务。
*
* @return array
*/
public function provides()
{
return ['MResponse'];
}
}
- 创建
Trait
继承类,实现接口类方法
/**
* Response继承类
*
* Trait ResponseTrait
* @package App\Traits
* @property \App\Contracts\Response $response
*/
trait ResponseTrait
{
/**
* @var array
*/
protected $method = ['response'];
/**
* 读取不可访问属性的值时
* (当访问私有的或者受保护的属性时触发魔术方法)
*
* @param string $name
* @return mixed
* @author SuperHao - 619596123@qq.com
*/
public function __get($name)
{
if (in_array($name, $this->method)) {
$name = "_" . $name;
return $this->$name();
}
abort(422, trans('response.contractsError'));
}
/**
* app获取可用的容器实例 - 响应
*
* @return \Illuminate\Foundation\Application|mixed
* @author SuperHao - 619596123@qq.com
*/
private function _response()
{
return app('MResponse');
}
}
- 到这里就差我们在控制器中,继承方法里
use
引入使用就可以了
namespace App\Http\Controllers;
use App\Traits\ResponseTrait;
/**
* Api有状态 - 基类
*
* Class ApiController
* @package App\Http\Controllers
*/
class ApiController extends Controller
{
/**
* 继承实现契约方法
*/
use ResponseTrait;
}
- 下面我们直接在有需要的时候调用自己定义的方法就阔以了
public function store(RegisterStoreRequest $request)
{
$data = $request->only([
'name',
'nickname',
'email',
'mobile',
'password',
]);
$data['password'] = bcrypt($data['password']);
User::create($data);
// 调用契约类给前端返回json格式数据
return $this->response->success(trans('response.success'));
}
2.Eloquent 【fill】方法
fill
方法,在Laravel5.5的官方文档里粗略的介绍了一丢丢,容易被人忽略使用的一个检测后批量赋值的方法
,大白话来讲这个方法就是在save
修改之前调用,来检测批量赋值Model
层的protected $fillable
白名单或protected $guarded
黑名单
深入fill
源码【思否】大牛讲解地址: segmentfault.com 下面这两句借鉴大神总结当你设置了
fillable
数组,没有设置guarded
数组时,那么此Model
会处于仅可批量赋值指定属性
的状态
当你没有设置fillable
数组,却设置了guarded
数组时,那么此Model
会处于可批量赋值任何属性
的状态
(注意:不要同时使用$fillable
和$guarded
亲测目前Laravel还不允许这种骚操作~)
Controller:
public function User()
{
...
$this->user->fill($this->user)->save();
}
********************************************************************
Model:
class User extends BaseModel
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'nickname',
'email',
'mobile',
'password',
'nickname_at',
];
}
3.Trait 特性『合理』灵活地『应用』(标题Trait的解释来自大神 ---- Summer)Summer 站长第一条评论
Trait 是用来实现多继承的关系,弥补
PHP
这个单继承语音的缺点,Trait
让PHP实现支持多继承,直接use
进来直接使用。
最大的作用就是提高代码的复用性,和Interface
继承类似但是区别在于,可以继承多个类,
Trait 更灵活方便了,可以用来写业务逻辑层
和封装公共方法
提升复用性,但是要注意耦合性
,下面是我博客
中实战运用其中之一代码:
真实代码结合容器
和契约
实现全局自定义Response
返回数据格式:
namespace App\Traits\Applets;
use Illuminate\Support\Facades\Cache;
/**
* 小程序 - 用户接口类
*
* Trait AppletsTrait
* @package App\Traits\Applets
*/
trait AppletsTrait
{
/**
* 获取当前微信登录的用户信息
*
* @param $user
* @return bool
* @author SuperHao - 619596123@qq.com
*/
public function getRedisUser(&$user)
{
/**
* 由于优先级问题:先走继承再走中间件原因
* 这里验证一下是否存在
*/
$result = array_key_exists('HTTP_AUTHORIZATION', $_SERVER);
if (!$result) return false;
$session3rd = session3rd($_SERVER['HTTP_AUTHORIZATION']);
if (!Cache::has($session3rd)) return false;
/**
* 获取用户 Redis Key值
*/
$user = Cache::get($session3rd);
if (empty($user)) return false;
return true;
}
}
4.Query Builder 叠加条件
Query Builder
我认为是一个积木,创建了积木地基,上面你可以拼接任何的条件,运用很广:全局作用域
DB闭包外加条件
闭包函数
前两个官方文档都有展示,当你解锁了Query
新姿势就可以各种变形的查询条件拼接叠加
- 两种“地基”方式:
DI注入形式:$query = $this->user->query()
直接实例形式:$query = User::query()
- 然后就可以随意的操作自己的条件代码Demo:
// 带name查询
if (request()->filled('type')) $query->where('type_id',$type);
// 闭包模糊查询
$query->where(function ($query) use ($value) {
$query->orWhere('name', 'like','%' . $value['name'] . '%');
$query->orWhere('keyword', 'like','%' . $value['keyword'] . '%');
$query->orWhere('is_desable',"$value['is_desable']");
});
// 拼接倒序
$query->orderBy('id', 'desc');
// 最后再拼接获取方式
$data = $query->get(['id', 'name', 'email', 'status', 'is_disable']);
- 实战打印出来看
$query = $this->hospital->query();
/**
* 首字母筛选
*/
if($request->filled('initials')) $query->where('initials',$request->input('initials'));
/**
* 地区筛选
*/
if($request->filled('area')) $query->where('area',$request->input('area'));
dd($query); 或者 dd($query->toSql());
5.Exceptions 全局捕获异常
前置条件:需要使用过表单验证 - 表单请求验证,为什么我说先需要使用这个,我们为了让代码更
清晰简约
剥离代替以前老的Illuminate\Support\Facades\Validator
类去验证,不再把验证写在控制器当中,而是依赖注入
到我们的控制当中,在这时候就特别...特别...特别...(重要的事说3遍)需要用到全局捕获异常(至于为什么,请认真看下面的解释)
当你在使用
php artisan make:request StoreBlogPost
去生成表单验证层
的时候,用postman
你会发现并不是抛出异常,而是跳转到上一个操作请求上了(超难受)。
跟进打开use Illuminate\Foundation\Http\FormRequest
验证的继承类源码,你会发现有一个failedValidation
方法,这个Laravel自带的源码中能看出方法是处理失败的验证
里面有个->redirectTo()
。找到祸源就解决它,不让它跳转,我尝试过重写父类这个方法,是可行的。但是太low
,我们用这个全局捕获异常去捕获返回json
更舒服一些,代码看下面:
- App\Exceptions\Handler的重写方法代码:
/**
* 重写 - 将异常呈现为HTTP响应 (Render an exception into an HTTP response)
*
* @param \Illuminate\Http\Request $request
* @param Exception $exception
* @return \Symfony\Component\HttpFoundation\Response
* @author SuperHao - 619596123@qq.com
*/
public function render($request, Exception $exception)
{
if ($exception instanceof ValidationException) {
// 捕获 request
return $this->response->error($exception->validator->errors()->first());
} elseif ($exception instanceof AuthenticationException) {
// 捕获 Auth
dd('AuthenticationException - 身份不正确');
} elseif ($exception instanceof ModelNotFoundException) {
// 捕获 模型 (只限:findOrFail 和 firstOrFail 方法会抛出异常)
return $this->response->error(trans('response.errorOrFail'));
} elseif ($exception instanceof NotFoundHttpException) {
// 捕获 404
return $this->response->wrong(404 ,'您的页面 - 走丢了');
} elseif ($exception instanceof MethodNotAllowedHttpException) {
// 捕获 Route
dd('MethodNotAllowedHttpException - 路由方法不对应');
} elseif ($exception instanceof BroadcastException) {
// 捕获 Route
dd('BroadcastException - 广播异常');
}
/**
* 没有捕获到走源码
*/
return parent::render($request, $exception);
}