开放与封闭:多入口和统一初始化
美国著名棒球运动员约格.贝拉说过:“通过仔细地观察,你可以了解很多事情。”
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']);
}
更多建议: