codecamp

开放与封闭:多入口和统一初始化

美国著名棒球运动员约格.贝拉说过:“通过仔细地观察,你可以了解很多事情。”

1.29.1 绝对的开放,还是绝对的固化?

框架应该都会考虑这样一个问题:到底是应该给应用项目提供统一固定的入口和初始化流程呢,还是应该给他们完全自由的空间?

但我发现,很多PHP的框架都提供了一个绝对的固化流程。也就是你能作出改动的地方很少,虽然这样应用不需要过多地去考虑。

然而我觉得,这样做,弊大于利。特别在现在项目需求背景各有各的不同时。所以,我为PhalApi框架选择了开放式的初始化做法。考虑到若开放的度太大,项目可能会迷茫,所以又结合了统一的初始化。

下面分别说明这两点:开放式入口和封闭式的初始化。

1.29.2 开放式的入口

作为一个接口系统,是需要为不同的终端、不同的开放人群,甚至不同的版本提供不同的服务的。

如:

  • 按不同的终端:iOS设备、Android设备、PC版、网页版
  • 按不同的开放人群:手机客户端、内部管理后台、内部其他系统、公共开放平台
  • 按不同的版本:版本从V1、V2到VN
  • 按不同的项目:同一个应用下可能会存在多个项目,当然更推荐每个项目单独一个系统

基于此,为不同的维度提供不同的入口就很有现实实用场景了。

更为重要的是,这些不同的入口都应该尽可能简单,并能统一共享公共的组件资源、数据库、日志系统这些。一个可能的入口示例如下:

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

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

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

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

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

简单解读一下上面的代码,首先要加载统一初始化文件,其实装载挂靠的接口项目(对应你的项目目录的名称),最后创建一个PhalApi接口实例进行响应、输出结果。

下面来看下统一初始化文件需要做的事情。

1.29.3 封闭式的初始化

不言而喻,尽管我们有按不同维度划分入口的需要,但统一初始化的过程更是必不可少的。

不同的入口提供了各维度定制的机会,统一的初始化则为应用提供了统一定制的机会。

(1)常规入口代码

常规的入口,需要以下初始化操作:

<?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)常规代码解读

上面是框架执行所需的基础服务注册和配置,一般直接可用,但也可以根据需要作些细微的调整。如日志的级别设定、调试的参数修改(改成一个只有自己知道的参数名字,别让外界知道!)等。

出于让大家对初始化过程有一个更理性的认识,这里补充一下各代码的作用。

最开始是利用了PHP原生态的时区设置和宏定义:

date_default_timezone_set('Asia/Shanghai');

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

接着,便开始引入PhalApi框架的类自动加载器:

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

这样,我们就可以从原生态的PHP开发,切入到了PhalApi接口开发模式。但在捲起袖口准备大干一场前,我们还需要注册一些必备的服务:

//自动加载
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);
};

上面的自动加载、配置、日志和数据库操作通常而言,对于一个项目都是必须的。
但配置文件的路径可自行指定,日志的存储类型也可以自由组合(多个类型采用或运算),还可以选择你合适的数据库配置。注意到,PhalApi_DB_NotORM初始化时,除了配置文件外,还有一个debug参数,此参数的作用是用于控制是否打印显示执行的SQL语句及对应消耗的时间。

至此,我们已经为项目完成了绝大部分的基础服务注册,且上面的初始化顺序建议保留不变。因为,前后有依赖关系。

但为了让我们的项目更有活力、更具生气、更国际化,我们还可以多加这么两行代码:

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

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

这里,也有一个debug参数,之所以和数据库的分开,是因为如果混在一起会导致返回结果解析失败(如不再是JSON格式)。
此debug的来源,默认来自环境的系统配置文件(如区分生产环境和测试环境);也可以来自某个请求的手动设置,这样,开发同学便可以快速进行在线调试了。而这个参数,则是框架代码、项目代码以及扩展类库所共用的调试开关,至于各个场景使用的效果,视各环节而定。

SL()是一个快速函数,作用是设定翻译语言包。如果觉得中文下的UTF-8查看不直观,可以自行加个参数修改,如:

SL(isset($_GET['__lan__']) ? $_GET['__lan__'] : 'zh_cn');

(3)更多定制注册代码

上面的操作,涵盖了大部分项目的需要。除此之外,还有一些额外的服务,可根据自身的情况,定制处理:

/** ---------------- 以下服务组件就根据需要定制注册 ---------------- **/

//缓存 - MC
/**
DI()->cache = function() {
    $mc = new PhalApi_Cache_Memcached(DI()->config->get('sys.mc'));
    return $mc;
};
 */

//签名验证服务
//DI()->filter = 'Common_SignFilter';

//支持JsonP的返回
if (!empty($_GET['callback'])) {
    DI()->response = new PhalApi_Response_JsonP($_GET['callback']);
}

(4)更多定制解读

此部分的注册,非项目必须部分。可根据需要,自行定制。

如上面出现的第一个,即缓存服务,使用的是Memcached:

DI()->cache = function() {
    $mc = new PhalApi_Cache_Memcached(DI()->config->get('sys.mc'));
    return $mc;
};

接下来的,是重要的接口签名验证服务。之所以没有提供这个服务的实现,是出于更高安全性考虑而建议项目各自制定签名规则并实现。然后这样简单注册即可被框架自动调用:

//签名验证服务
//DI()->filter = 'Common_SignFilter';

在有些需要支持JsonP返回的场景,可以使用PhalApi_Response_JsonP返回格式开放callback操作:

//支持JsonP的返回
if (!empty($_GET['callback'])) {
    DI()->response = new PhalApi_Response_JsonP($_GET['callback']);
}

1.29.4 用一张图来表示

多级缓存静态结构图-多入口


COOKIE:对COOKIE原生态的支持及记忆加密升级版
保持的力量:接口开发最佳实践
温馨提示
下载编程狮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; }