Yii2 之 RESTful API 实践之一:构建应用主体

本文介绍了如何在Yii2框架中构建RESTful API接口,包括创建API应用主体、设置请求错误提示为JSON格式、美化URL路由等步骤。教程详细讲解了从新建数据表、配置Apache/Nginx、建立API模块到前端Ajax跨域请求的全过程,适合开发者参考学习。
摘要由CSDN通过智能技术生成

▪ 前言

API 接口时,最土的办法就是按常规 PHP HTML 的写法直接输出 JSON 数据,规则规律完全自己把握。虽然它能很好运行,但是总觉得其不太规范。

好在 Yii2 框架中集成了 Restful API,利用其写 API 接口就像当初学习使用 MVC 写代码一样,还是非常值得一试的。

▪ 概述

在正式开始之前,我们先大概了解下完成一个 RESTful API 需要有哪些工作:

  1. ApacheNginx 中开启 Rewrite 伪静态模式。
  2. Yii2 中新建 api 应用主体,专门用来存放 api 相关文件,并配置响应格式为 JSON
  3. 改造请求错误提示为 JSON 格式,而非 HTML
  4. 测试调试请求。
  5. 前端进行接收(这部分我们说下前端 Ajax 如何跨域做出请求)。

▪ 准备

1. 新建数据表

新建 admin 表用来开发(也可以用命令行 migrate 去建表):

DROP TABLE IF EXISTS `admin`;  
CREATE TABLE IF NOT EXISTS `admin` (  
    `id` int(11) NOT NULL AUTO_INCREMENT,  
    `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `admin` VALUES ('1', '我是一');
INSERT INTO `admin` VALUES ('2', '我是二');
INSERT INTO `admin` VALUES ('3', '我是三');
INSERT INTO `admin` VALUES ('4', '我是四');
2. API 的请求工具

Chrome 中建议安装 postmanFirefox中安装 HttpRequest,其他浏览器自行解决。

由于浏览器被墙,所以推荐使用命令模式下 curl 命令,使用方式参考《Windows 下 curl 的安装和简单使用》

3. JSON 的查看工具

chrome 中建议安装 JSONViewFirefox 中安装 jsonformater,其他浏览器自行解决;

4. 伪静态

开启伪静态 Rewrite、并且配置 api 的二级域名为 api.domain.com 格式,绑定在 api/web 下面。

▪ api 应用主体的建立

  1. 在项目的根目录下新建名为 api 的文件夹,并在 common/config/bootstrap.php 文件中添加 api 模块的别名:
Yii::setAlias('@api', dirname(dirname(__DIR__)).'/api');
  1. 拷贝 backend/config 文件夹到 api 的根目录下,并修改配置文件 api/config/main.php,内容如下:
<?php
$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);

// 初始
$configs                                                        = array();
$configs['id']                                                  = 'APP-API';

$configs['params']                                              = $params;
$configs['basePath']                                            = dirname(__DIR__);
$configs['bootstrap']                                           = array('log');

$configs['modules']                                             = array();
$configs['modules']['v1']                                       = array('class'=>'api\modules\v1\Module');
$configs['modules']['v2']                                       = array('class'=>'api\modules\v2\Module');

$configs['components']                                          = array();
$configs['components']['errorHandler']                          = array('errorAction'=>'v1/index/error');

// 用户
$configs['components']['user']                                  = array();
$configs['components']['user']['loginUrl']                      = null;
$configs['components']['user']['identityClass']                 = 'common\models\User';
$configs['components']['user']['identityCookie']                = array('name'=>'_identity-api', 'httpOnly'=>true);
$configs['components']['user']['enableSession']                 = false;
$configs['components']['user']['enableAutoLogin']               = false;

// 返回
return $configs;
  1. 新建 api/modules/v1api/modules/v2 文件夹作为我们不同版本的内容,并在各自的文件夹下新建 controllersmodelsviews 目录。

  2. 新建 api/modules/v1/Config.php 文件(以 v1 为例),内容如下:

<?php
$configs['components']                                            = array();
$configs['components']['request']                                 = array('enableCsrfValidation'=>false);
$configs['components']['errorHandler']                            = array('errorAction'=>'v1/index/error');

// 返回
return $configs;
  1. 新建 api/modules/v1/Module.php 文件(以 v1 为例),内容如下:
<?php  
namespace api\modules\v1;  

use Yii;

/**
 * 子模块类
 */
class Module extends \yii\base\Module  
{ 
   public function init()  
   {  
        // 初始化
        parent::init();
        $module = $this->id;

        // 初始模块配置
        $config = require(__DIR__.'/Config.php');
        $components = Yii::$app->getComponents();

        foreach( $config['components'] AS $k=>$component ){
            if( isset($component['class']) && isset($components[$k]) == false ) continue;
            $config['components'][$k] = array_merge($components[$k], $component);
        }

        Yii::configure(Yii::$app, $config);

        // 组件重先注册
        // 系统核心的几个组件在初始化应用时就创建了对象,而子模块的配置在这之后才被应用
        // 所以就导致了这几个核心组件无法根据子模块的配置进行构建,此处将这几个核心组件进行重构
        Yii::$app->getErrorHandler()->unregister();
        Yii::$app->getErrorHandler()->register();
   }  
}  
  1. 新建 common/models/Admin.php 模型文件,内容如下:
<?php
namespace common\models;

use Yii;
use yii\db\ActiveRecord;
use yii\data\Pagination;

/**
 * 管理员模型
 *
 * @author DaiYangFan <dmlk31@163.com>
 */
class Admin extends ActiveRecord
{
    /**
     * Define rules for validation
     */
    public function rules()
    {
        return array(
            array(['name'], 'required')
        );
    }
    
    // ...
}

Admin 模型类中必须重载 rules() 来指明必填的字段名称,否则 RESTful API 的内置接口 admin/createadmin/update 将无法使用

  1. 新建 api/modules/v1/controllers/AdminController.php 控制器用来响应,并重写控制器的行为函数 behaviors() 响应为 JSON
<?php  
namespace api\modules\v1\controllers;  
 
use Yii;
use yii\rest\ActiveController;
  
class AdminController extends ActiveController  
{  
    // 控制器对应的模型类
    // 指定模型类后 Yii2 能自动为该控制器实现 admin/index, admin/view, admin/create, admin/update, admin/delete 等 ActiveController::actions() 里预设的方法
    public $modelClass = 'common\models\Admin';

    /**
     * 绑定控制器行为
     *
     * @return array
     */
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['contentNegotiator']['formats']['text/html']        = Response::FORMAT_JSON;
        $behaviors['contentNegotiator']['formats']['application/json'] = Response::FORMAT_JSON;

        return $behaviors;
    }
}

common\models\Admin 为管理员模型类,如有问题请百度一下。

  1. 拷贝 backend/web 文件夹到 api 的根目录下。

  2. 拷贝 enviroments/dev/backend 并重命名为 enviroments/dev/api,拷贝 enviroments/prod/backend 并重命名为 enviroments/prod/api

enviroments 里面存放的主要就是一些基础配置的模板文件,供 Yii2 初始化环境变量使用(执行 ./init 用),具体参考《Yii2 初始化安装》

  1. 以上我们并快速的完成了一个 api 应用的建立,可以在浏览器中通过一下链接进行访问测试:
# admin list
http://api.domain.com/index.php?r=v1/admin
http://api.domain.com/index.php?r=v1/admin/index

# admin view
http://api.lengdo.com/index.php?r=v1/admin/view&id=2

当然,在浏览器链接的这种方式下(GET 方式),我们只能访问 admin/indexadmin/view,如果想操作其他接口,那么就需要使用 API请求工具 了,这里我们以 curl 方式为例:

# 删除 ID 为 2 的管理员
curl -X DELETE "http://api.domain.com/index.php?r=v1/admin/delete&id=2"

# 新增管理员
curl --data "name=xiaosa" "http://api.domain.com/index.php?r=v1/admin/create"

# 更新管理员
curl --data "name=xiaow" "http://api.domain.com/index.php?r=v1/admin/update&id=1" -X PUT

▪ 设置请求错误提示为 JSON

在这个 api 应用中,当出现内部错误时(比如访问的URL没有对应控制器等),系统默认是返回 HTML 错误内容,很明显这是不合适的。所以我们需要将错误内容格式改为 JSON

在上面的配置中,我们曾在 api/modules/v1/Config.php 中这样设置过:

<?php
// ...

$configs['components']['errorHandler'] = array('errorAction'=>'v1/index/error');

// ...

如果 URL 访问的不是 v1v2
子模块时,其下面的 Config.php 将不起作用,错误处理将使用 Yii2 内置逻辑并返回 HTML 错误内容。所以在 api/config/main.php 中也有这样一个错误处理配置,这个配置的作用是当你的 URL 访问的不是 v1v2
子模块时,将默认使用 v1 子模块的错误处理函数返回 JSON 错误内容。

这个配置就是告诉 Yii2 当出现内部错误时那么从 v1 模块->index 控制器->error方法 获取错误内容并返回到客户端。

所以很简单,我们只需要在 IndexController 控制器的 actionError() 里将错误数据格式化为 JSON 即可:

<?php
namespace api\modules\v1\controllers;

use Yii;
use yii\web\HttpException;
use yii\base\Controller;

/**
 * 控制器
 *
 * @author DaiYangFan <dmlk31@163.com>
 */
class IndexController extends Controller
{
    // ...

    /**
     * 错误处理
     * 模块所有的错误都将传递到该方法进行处理,可以在每个模块的配置文件 Config.php 中修改
     *
     * 更多详细的 Yii2 的错误处理请参考文章 《Yii2 之错误处理深入分析》
     *
     * @return string
     */
    public function actionError(){
        // 初始化
        $exception = Yii::$app->getErrorHandler()->exception;
        if( $exception === null ) $exception = new HttpException(404, Yii::t('yii', 'Page not found.'));

        // 获取错误码
        $error = ($exception instanceof HttpException) ? $exception->statusCode : $exception->getCode();
        $error = $error ? $error : 1;

        // 获取错误消息
        $message = ($exception instanceof Exception) ? $exception->getName() : '';
        $message = $message ? $message : (($exception instanceof UserException)?$exception->getMessage():'');
        $message = $message ? $message : Yii::t('yii', 'An internal server error occurred.');

        // 返回错误数据
        echo json_encode(['error'=>$error,'message'=>trim('v1 - '.$message)]);
    }
    
    // ...
}

以上代码只是抛砖引用,你可以直接采用,也可以按自己项目的需要重写一些返回的字段和内容。

▪ 测试调试请求

GET       http://api.domain.com/index.php?r=v1/admin           列出所有管理员
GET       http://api.domain.com/index.php?r=v1/admin/index     列出所有管理员
GET       http://api.domain.com/index.php?r=v1/admin/view&id=1 列出id为1的管理员
POST      http://api.domain.com/index.php?r=v1/admin           新建管理员
DELETE    http://api.domain.com/index.php?r=v1/admin?id=1      删除id为1的管理员
PUT/PATCH http://api.domain.com/index.php?r=/v1/admin?id=1     修改id为1的管理员

我们可以在 postmancurl 等工具中做以上 URL 的测试,这些软件的具体使用请自行百度。

▪ 美化 URL 路由

在上面的例程中,可以发现所有的 URL 都是最原始的方式:文件名 + 问号 + 参数;这种方式一般在 API 中不会使用的,所以为了符合主流的使用体验,我们得美化下 URL。

修改配置文件 api/config/main.php,添如下加内容:

// ...

// URL
$configs['components']['urlManager']                            = array();
$configs['components']['urlManager']['class']                   = 'yii\web\UrlManager';
$configs['components']['urlManager']['showScriptName']          = false;
$configs['components']['urlManager']['enablePrettyUrl']         = true;
$configs['components']['urlManager']['enableStrictParsing']     = false;

// ...

通过上面的配置后,我们可以重新测试调试请求,URL 例子如下:

GET       http://api.domain.com/v1/admin           列出所有管理员
GET       http://api.domain.com/v1/admin/index     列出所有管理员
GET       http://api.domain.com/v1/admin/view?id=1 列出id为1的管理员
POST      http://api.domain.com/v1/admin           新建管理员
DELETE    http://api.domain.com/v1/admin?id=1      删除id为1的管理员
PUT/PATCH http://api.domain.com/v1/admin?id=1      修改id为1的管理员

至此还没结束,因为上面的 URL 中的 ?id=1 还是没有完全美化,所以再修改文件 api/config/main.php,修改内容如下:

// ...

// URL
$configs['components']['urlManager']                            = array();
$configs['components']['urlManager']['class']                   = 'yii\web\UrlManager';
$configs['components']['urlManager']['rules']                   = ['<module>/<controller>/<action>/<id:\d+>'=>'<module>/<controller>/<action>'];
$configs['components']['urlManager']['showScriptName']          = false;
$configs['components']['urlManager']['enablePrettyUrl']         = true;
$configs['components']['urlManager']['enableStrictParsing']     = false;

// ...

通过上面的配置后,我们可以重新测试调试请求,URL 例子如下:

GET       http://api.domain.com/v1/admin          列出所有管理员
GET       http://api.domain.com/v1/admin/index    列出所有管理员
GET       http://api.domain.com/v1/admin/view/1   列出id为1的管理员
POST      http://api.domain.com/v1/admin/create   新建管理员
DELETE    http://api.domain.com/v1/admin/delete/1 删除id为1的管理员
PUT/PATCH http://api.domain.com/v1/admin/update/1 修改id为1的管理员

至此我们就搭建完成了 Restful api 的接口。

▪ 前端接收、并采取的跨域解决方案:

当我们在 www.domain.comajax 去请求我们 api.domain.comapi 接口时候,你会发现:请求失败,并报错,原因是跨域,浏览器做出了限制;

也就是说我们域名相同、端口相同、协议相同才不算做是跨域,那么该怎么办?不用担心,自有跨域的解决办法、这里介绍两种,其他可百度解决:

1. JsonP跨域解决

JQueryajax 中设定 datatype 的类型为 jsonp 就行了,很简单。

  • 优点是:对于一些古老的浏览器很高的支持;例如万恶的ie。。。
  • 缺点是:只能发出 GET 请求,其他 method 无法操作,例如 POSTDELETE
2. cros同源策略

设置方式,只要在 action 中设置 header 头部即可,也是最简单、最常用的一种(如果不考虑IE的话)。

header 中添加 Access-Control-Allow-Origin:* 或者 建立信任的 URL(例如:Access-control-Origin:http://www.domain.com;

  • 优点:操作方便,只需添加 header。支持新兴浏览器,所有方式都可采用:例如GET、POST、DELETE、PUT、PATCH、OPTIONS 等

Access-Control-Allow-Methods:* 允许操作的方法为所有;

  • 缺点:对IE的支持不好,兼容性 IE10 以前的都不能用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值