了解底层源码Laravel框架 RESTful API 风格进行

2018-04-13  本文已影响0人  Super三脚猫
timg (3).jpg

如上图,本文讲述Laravel5.5 LTS为例子,怎么合理的开发 RESTful API 接口(真正意义上的前后端分离),因为博客正在开发,所以文章是持续更新的....
*注: 本文全部是自己手打的原创,基本上是文字比较多,所以大多都是要理解,有看不懂留言详解(另外大神们可以随意指点,我都会虚心接受)

1.Laravel底层核心架构Contracts契约的理解和使用

理解契约

契约用在哪里?契约哪里好?为什么要契约?这也是当初我带着这些疑问去尝试使用的问题
契约用官方解释是:Laravel 的契约是一组定义框架提供的核心服务的接口

其实就是:通过服务提供者去绑定接口类,然后在Services层实现方法,用Trait多继承特性,注入到控制器继承方法里使用
关键字服务提供者延迟提供器Trait依赖注入(DI)Io容器,请读者先了解一下这几个关键字再使用契约,已知的大神们请忽略这句话。

使用契约
<?php
namespace App\Contracts;

interface Response
{
    /**
     * SUCCESS - 操作成功
     */
    public function success(string $message = '', array $data = []);

    /**
     * Error - 失败
     */
    public function error(string $message = '', array $data = []);
}
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,
        ]);
    }
}
'providers' => [
...
        //注册Contracts契约接口
        App\Providers\ResponseServiceProvider::class,
],
<?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'];
    }
}
/**
 * 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');
    }
}
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'));
 }
create.png

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 新姿势就可以各种变形的查询条件拼接叠加

// 带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());
bindings.png

5.Exceptions 全局捕获异常

前置条件:需要使用过表单验证 - 表单请求验证,为什么我说先需要使用这个,我们为了让代码更清晰简约剥离代替以前老的Illuminate\Support\Facades\Validator类去验证,不再把验证写在控制器当中,而是依赖注入到我们的控制当中,在这时候就特别...特别...特别...(重要的事说3遍)需要用到全局捕获异常(至于为什么,请认真看下面的解释)

当你在使用php artisan make:request StoreBlogPost去生成表单验证层的时候,用postman你会发现并不是抛出异常,而是跳转到上一个操作请求上了(超难受)。
跟进打开use Illuminate\Foundation\Http\FormRequest验证的继承类源码,你会发现有一个failedValidation方法,这个Laravel自带的源码中能看出方法是处理失败的验证里面有个->redirectTo()。找到祸源就解决它,不让它跳转,我尝试过重写父类这个方法,是可行的。但是太low,我们用这个全局捕获异常去捕获返回json更舒服一些,代码看下面:

/**
 * 重写 - 将异常呈现为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);
}
上一篇 下一篇

猜你喜欢

热点阅读