codecamp

快速入门: 接口开发示例(源码+图文)

一个真正的强者,不是摆平了多少人,而是他能帮助到多少人。 --开源中国源创会分享广州站 @海洋之心-悟空


1.11.1 模拟开发:获取开源中国用户信息接口

首次使用此接口开发框架时,可以先查看此开发示例。

假设,我们需要为开源中国打造一个平放平台,其中有一个接口是可以根据用户ID来获取用户的基本信息。

本文,就以模拟获取开源中国用户信息接口开发(即:从数据库获取用户的基本信息并以JSON格式返回给客户端)为例,简明的接口开发中的流程,以及用到的基本功能和操作,主要包括有:统一入口文件、参数规则配置、接口层/领域层/模型持久层、日志纪录、数据库操作、配置读取、国际化翻译等。

以给大家一个感观的认识。

最终接口的调用与返回结果如下:

//接口请求格式
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=帐号ID

//返回结果格式
{
    "ret": 200,
    "data": {           
        "code": 0,    //状态码,0表示正常获取,1表示用户不存在
        "msg": "",
        "info": {      //用户信息
            "id": "1",    //用户ID
            "name": "dogstar",   //帐号
            "note": "oschina"   //来源
        }
    },
    "msg": ""
}

1.11.2 开发流程

(1) 统一入口文件

为了更好保护我们的项目代码,建议将接口的访问路径设置在:./PhalApi/Pubic目录,并且各套接口(如按版本分:v1/v2/v3等等;按不同终端分:ios/android/pc等)各自独立入口。所以本示例中将./PhalApi/Pubic/demo/index.php下。

参数入口文件的写法,我们可以快速得到基本的入口文件如下:

// $ vim ./Public/demo/index.php 

<?php
/**
 * Demo 统一入口
 */

require_once dirname(__FILE__) . '/../init.php';

//装载你的接口
DI()->loader->addDirs('Demo');

/** ---------------- 响应接口请求 ---------------- **/

$server = new PhalApi();
$rs = $server->response();
$rs->output();

此外,我们还需要一个公共的初始化文件:

//$ vim ./Public/init.php 

<?php
/**
 * 统一初始化
 */

/** ---------------- 根目录定义,自动加载 ---------------- **/

date_default_timezone_set('Asia/Shanghai');

defined('API_ROOT') || define('API_ROOT', dirname(__FILE__) . '/..');

require_once API_ROOT . '/PhalApi/PhalApi.php';
$loader = new PhalApi_Loader(API_ROOT);

/** ---------------- 注册&初始化服务组件 ---------------- **/

//自动加载
DI()->loader = $loader;

//配置
DI()->config = new PhalApi_Config_File(API_ROOT . '/Config');

//日志纪录
DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime', 
    PhalApi_Logger::LOG_LEVEL_DEBUG | PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);

//数据操作 - 基于NotORM,$_GET['__sql__']可自行改名
DI()->notorm = function() {
    $debug = !empty($_GET['__sql__']) ? true : false;
    return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
};

//调试模式,$_GET['__debug__']可自行改名
DI()->debug = !empty($_GET['__debug__']) ? true : DI()->config->get('sys.debug');

//翻译语言包设定
SL('zh_cn');

(2)TDD测试驱动开发

遵循最佳实践,我们在编写代码前先编写单元测试。但同时为了减少编写测试代码的痛苦,我们可以先定义接口函数签名,再通过脚本自动生成测试代码骨架来提高我们的开发效率。

//$vim ./Demo/Api/User.php

<?php

class Api_User extends PhalApi_Api
{   
    public function getBaseInfo()
    {
    }
}

通过脚本生成测试骨架:

$ mkdir -p ./Demo/Tests/Api/
$ cd ./Demo/Tests/Api
$ php ../../../PhalApi/build_phpunit_test_tpl.php ../../Api/User.php Api_User ./../test_env.php

然后,根据/Public/demo/index.php入口文件,也搭建一个测试环境的入口文件:

vim ./Demo/Tests/test_env.php

修正一下Api_Examples_User_Test.php里,测试环境test_env.php的包含路径:

//手动调用test_env.php的路径
require_once dirname(__FILE__) . '/../test_env.php';

并且,修改测试,以符合我们通过userId=1获取基本信息(名字为dogstar,来源为oschina):

//$vim ./Demo/Tests/Api/Api_User_Test.php

    /**
     * @group testGetBaseInfo
     */
    public function testGetBaseInfo()
    {
        $str = 'service=User.GetBaseInfo&user_id=1';
        parse_str($str, $params);

        DI()->request = new PhalApi_Request($params);

        $api = new Api_User(); 
        //自己进行初始化
        $api->init();
        $rs = $api->getBaseInfo();

        $this->assertNotEmpty($rs);
        $this->assertArrayHasKey('code', $rs);
        $this->assertArrayHasKey('msg', $rs);
        $this->assertArrayHasKey('info', $rs);

        $this->assertEquals(0, $rs['code']);

        $this->assertEquals('dogstar', $rs['info']['name']);
        $this->assertEquals('oschina', $rs['info']['note']);
    }

此时,单元测试是预期失败的:

$ phpunit ./Api_User_Test.php 
PHPUnit 4.3.4 by Sebastian Bergmann.

F

Time: 3 ms, Memory: 5.25Mb

There was 1 failure:

1) PhpUnderControl_ApiUser_Test::testGetBaseInfo
Failed asserting that a NULL is not empty.

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

(3)Api接口层

此接口层,主要是负责响应客户端的请求,调用需要的领域层进行必要的服务功能提供。

配置参数规则

为了获取到用户ID,我们可以在getRules()函数里面定义参数规则,以便框架自动帮我们过滤获取需要的参数。

温馨提示:
接口层的全部类成员函数都应以小写开头。
但对外,函数首字母不区分大小写,因为框架会将请求的函数强制转换成小写再执行。

原因在于:
1、我们坚持驼峰法的代码风格;
2、对外界我们可以统一使用大写来提供服务名称,如:User.Login,这样更显专业。

//$vim ./Demo/Api/User.php

<?php

class Api_User extends PhalApi_Api
{
    public function getRules()
    {
        return array(
            'getBaseInfo' => array(
                'userId' => array('name' => 'userId', 'type' => 'int', 'min' => 1, 'require' => true),
            ),
        );
    }

    //...

如上,我们就定义了getBaseInfo接口中的userId参数,名字也为userId,整型,最小值为1,必须。

接口实现

//$vim ./Demo/Api/User.php

    public function getBaseInfo()
    {
        $rs = array('code' => 0, 'msg' => '', 'info' => array());

        $domain = new Domain_User();
        $info = $domain->getBaseInfo($this->userId);

        if (empty($info)) {
            DI()->logger->debug('user not found', $this->userId);

            $rs['code'] = 1;
            $rs['msg'] = T('user not exists');
            return $rs;
        }

        $rs['info'] = $info;

        return $rs;
    }

(4)Domain领域层

领域层主要是关注复杂业务的处理,以及缓存的处理、耗时操作后台异步处理等,并调用Model持久层获取需要的数据。因此,是Api与Model层之间的桥梁。

在此示例中,我们只需要简单地调用Model层获取用户的信息即可,再加强一下用户ID的合法性判断。

//$ vim ./Demo/Domain/User.php 

<?php

class Domain_User
{
    public function getBaseInfo($userId)
    {
        $rs = array();

        $userId = intval($userId);
        if ($userId <= 0) {
            return $rs;
        }

        $model = new Model_User();
        $rs = $model->getByUserId($userId);

        return $rs;
    }
}

(5)Model持久层

此一层主要关注数据从持久存储的获取,特别是针对数据库的操作,但不排除其他媒介,如文件、缓存等。

首先,先准备一下我们需要的表和数据:

CREATE TABLE `phalapi_test`.`tbl_user` (
  `id` INT NOT NULL,
  `name` VARCHAR(45) NULL,
  `note` VARCHAR(45) NULL,
  PRIMARY KEY (`id`));

INSERT INTO `phalapi_test`.`tbl_user` (`id`, `name`, `note`) VALUES ('1', 'dogstar', 'oschina');

然后,编写需要的Model代码,即利用NotORm实现对数据的操作:

//$ vim ./Demo/Model/User.php 

<?php

class Model_User
{
    public function getByUserId($userId)
    {
        return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
    }
}

(6)单元测试通过啦!

在完成上面简单的开发后,我们即可以实现接口的开发,运行一下刚才的单元测试,完美通过!

$ phpunit ./Api_User_Test.php 
PHPUnit 4.3.4 by Sebastian Bergmann.

SELECT * FROM tbl_user WHERE (id = ?); -- 1

.SELECT * FROM tbl_user WHERE (id = ?); -- 1<br />

Time: 34 ms, Memory: 6.50Mb

OK (2 tests, 7 assertions)

在单元测试的保证下,我们便可以放心大胆地将我们的代码发布到外网,提供给更多的开发者,和终端用户使用。

(7)数据库配置

因为是单元测试,所以我们配置搭建了新的一个测试环境,特别对于数据库的配置,如下:

//$ vim ./Config/dbs.php

<?php
/**
 * examples配置
 */

return array(
    /**
     * DB数据库服务器集群
     */
    'servers' => array(
        'db_demo' => array(
            'host'      => '192.168.0.104',         //数据库域名
            'name'      => 'phalapi_test',          //数据库名字
            'user'      => 'root',                  //数据库用户名
            'password'  => '123456',                //数据库密码
            'port'      => '3306',                  //数据库端口
        ),
    ),

    /**
     * 自定义路由表
     */
    'tables' => array(
        '__default__' => array(
            'prefix' => 'tbl_',
            'key' => 'id',
            'map' => array(
                array('db' => 'db_demo'),
            ),
        ),
    ),
);

温馨提示:
为了方便在单元测试时进行调试,和查看日志,对于全部查询、执行的SQL语句都会显示出来,全部的日志改用控制台输出。

(8)最终接口调用

接口调用的链接,这时已经相当明了了,即:域名 + 路径(/demo) + 参数(可从单元测试那直接获取)

如本示例的是:

http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1

返回的结果是:

{
    "ret": 200,
    "data": {
        "code": 0,
        "msg": "",
        "info": {
            "id": "1",
            "name": "dogstar",
            "note": "oschina"
        }
    },
    "msg": ""
}

截图效果:
show

温馨提示:
如果提示日志写入失败,请确保./Runtime目录具有写入权限,即0777。

1.11.3 更多简明的使用

(1)日志纪录

当我们访问一个不存在的用户时,将会触发日志纪录:

DI()->logger->debug('user not found', $this->userId);

如访问:

http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=2

然后,可以在Runtime下看到按天分目录的日志:

$ tailf ./Runtime/log/201501/20150128.log 
2015-01-28 00:37:34|DEBUG|user not found|2

温馨提示:
外网环境上,请把Runtime目录软链到磁盘空间很大的路径。

(2)国际化翻译

当需要翻译时,可以使用人性化的函数T(),如:

$rs['msg'] = T('user not exists');

对应地需要补充翻译的内容:

//$ vim ./Language/zh_cn/common.php 

    'user not exists' => '用户不存在',

还是以上面的用户不存在为例,看下运行的截图效果:show

(3)配置读取

配置的读取,使用方便,直接通过以下方式便可以获取,以点号分割:

DI()->config->get('dbs')

第一段,必须为文件名,后面的为用点号相连的数组下标,不限级。

(4)[酷!]接口参数在线查询

为了方便客户端实时查看最新的接口参数,这里提供了一个快速的在线工具:

http://demo.phalapi.net/checkApiParams.php?service=User.GetBaseInfo

用浏览器打开后,即可以看到最新的接口参数说明, 不需要后台接口开发人员编写文档维护,直接从代码中生成参数报表:

show

1.11.4 Demo相关代码文件

从上面可以得到,此示例相关的代码如下:

Demo$ tree
.
├── Api
│   ├── Default.php
│   └── User.php
├── Domain
│   └── User.php
├── Model
│   └── User.php
└── Tests
    ├── Api
    │   ├── Api_Default_Test.php
    │   └── Api_User_Test.php
    └── test_env.php

对PhalApi框架的抉择
参数规则:接口参数规则配置
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

八、视频教程

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }