ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
_一个真正的强者,不是摆平了多少人,而是他能帮助到多少人。 --开源中国源创会分享广州站 @海洋之心-悟空_ *** ##1.11.1 模拟开发:获取开源中国用户信息接口 首次使用此接口开发框架时,可以先查看此开发示例。 假设,我们需要为开源中国打造一个平放平台,其中有一个接口是可以根据用户ID来获取用户的基本信息。 本文,就以模拟获取开源中国用户信息接口开发(即:从数据库获取用户的基本信息并以JSON格式返回给客户端)为例,简明的接口开发中的流程,以及用到的基本功能和操作,主要包括有:统一入口文件、参数规则配置、接口层/领域层/模型持久层、日志纪录、数据库操作、配置读取、国际化翻译等。 以给大家一个感观的认识。 最终接口的调用与返回结果如下: ```javascript //接口请求格式 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下。 参数入口文件的写法,我们可以快速得到基本的入口文件如下: ```javascript // $ 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(); ``` 此外,我们还需要一个公共的初始化文件: ```javascript //$ 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测试驱动开发 遵循最佳实践,我们在编写代码前先编写单元测试。但同时为了减少编写测试代码的痛苦,我们可以先定义接口函数签名,再通过脚本自动生成测试代码骨架来提高我们的开发效率。 ```javascript //$vim ./Demo/Api/User.php <?php class Api_User extends PhalApi_Api { public function getBaseInfo() { } } ``` 通过脚本生成测试骨架: ```javascript $ 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入口文件,也搭建一个测试环境的入口文件: ```javascript vim ./Demo/Tests/test_env.php ``` 修正一下Api_Examples_User_Test.php里,测试环境test_env.php的包含路径: ```javascript //手动调用test_env.php的路径 require_once dirname(__FILE__) . '/../test_env.php'; ``` 并且,修改测试,以符合我们通过userId=1获取基本信息(名字为dogstar,来源为oschina): ```javascript //$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']); } ``` 此时,单元测试是预期失败的: ```javascript $ 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,这样更显专业。 ```javascript //$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,必须。 ####接口实现 ```javascript //$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的合法性判断。 ```javascript //$ 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持久层 此一层主要关注数据从持久存储的获取,特别是针对数据库的操作,但不排除其他媒介,如文件、缓存等。 首先,先准备一下我们需要的表和数据: ```javascript 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实现对数据的操作: ```javascript //$ vim ./Demo/Model/User.php <?php class Model_User { public function getByUserId($userId) { return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch(); } } ``` ###(6)单元测试通过啦! 在完成上面简单的开发后,我们即可以实现接口的开发,运行一下刚才的单元测试,完美通过! ```javascript $ 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)数据库配置 因为是单元测试,所以我们配置搭建了新的一个测试环境,特别对于数据库的配置,如下: ```javascript //$ 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) + 参数(可从单元测试那直接获取) 如本示例的是: ```javascript http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1 ``` 返回的结果是: ```javascript { "ret": 200, "data": { "code": 0, "msg": "", "info": { "id": "1", "name": "dogstar", "note": "oschina" } }, "msg": "" } ``` 截图效果: ![show](http://webtools.qiniudn.com/20150411005257_a5d0a984e4dd7fcdd1c95026e337003d) > 温馨提示: > 如果提示日志写入失败,请确保./Runtime目录具有写入权限,即0777。 ##1.11.3 更多简明的使用 ###(1)日志纪录 当我们访问一个不存在的用户时,将会触发日志纪录: ```javascript DI()->logger->debug('user not found', $this->userId); ``` 如访问: ``` http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=2 ``` 然后,可以在Runtime下看到按天分目录的日志: ```javascript $ tailf ./Runtime/log/201501/20150128.log 2015-01-28 00:37:34|DEBUG|user not found|2 ``` > 温馨提示: > 外网环境上,请把Runtime目录软链到磁盘空间很大的路径。 ###(2)国际化翻译 当需要翻译时,可以使用人性化的函数T(),如: ```javascript $rs['msg'] = T('user not exists'); ``` 对应地需要补充翻译的内容: ```javascript //$ vim ./Language/zh_cn/common.php 'user not exists' => '用户不存在', ``` 还是以上面的用户不存在为例,看下运行的截图效果: ![show](http://webtools.qiniudn.com/20150411005257_d8fc7091e4a2717fa9e1b4f9891117ff) ###(3)配置读取 配置的读取,使用方便,直接通过以下方式便可以获取,以点号分割: ```javascript DI()->config->get('dbs') ``` 第一段,必须为文件名,后面的为用点号相连的数组下标,不限级。 ###(4)[酷!]接口参数在线查询 为了方便客户端实时查看最新的接口参数,这里提供了一个快速的在线工具: ```javascript http://dev.phalapi.com/demo/checkApiParams.php?service=User.GetBaseInfo ``` 用浏览器打开后,即可以看到最新的接口参数说明, 不需要后台接口开发人员编写文档维护,直接从代码中生成参数报表: ![show](http://webtools.qiniudn.com/20150411005257_af3f58d9bc050b4906a57fea3d04e9b2) ##1.11.4 Demo相关代码文件 从上面可以得到,此示例相关的代码如下: ```javascript 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 ```