1.15.1 NotORM官网
这里使用了NotORM进行DB操作,具体的数据库操作使用文档请见NotORM官网:http://www.notorm.com
1.15.2 NotORM的优化
但为了更符合项目的开发,这里对NotORM的底层作了升级修改,以下为主要修改点和新的使用:
(1)将原来返回的结果全部从对象改成数组
对原来的大部分使用无特别影响,可按原来的方式开发。主要目的是为了更方面处理返回的数据,以及简化对结果的再解析,简单明了。
如:
DI()->notorm->user->where('username = ?', 'dogstar')->fetch();
返回的将是一个数组:
array(7) {
["id"]=>
string(3) "180"
["username"]=>
string(17) "dogstar"
["regtime"]=>
string(10) "1414811954"
//...
}
(2)提供获取全部结果的接口 - fetchAll()
如:
$rows = DI()->notorm->event_picurl->where('(eid)', $eids)->fetchAll();
即可获取全部的数据,不再受限于分页。
(3)提供更灵活的查询方式 -queryRows()
当需要进行复杂的SQL查询时,可以使用此接口,如:
$sql = 'select * from example AS ep LEFT JOIN user AS u ON ep.ui
d = u.id where ep.touid = :userId ORDER BY dateline desc LIMIT :start,:num';
$params = array('userId' => $userId, ':start' => $start, ':num' => $num);
$rs= DI()->notorm->example->queryRows($sql, $params);
(4)修正原来的BUG
进行LIMIT时 因加入了 OFFSET 关键字导致MySQL下无法获取数据
(5)禁止全表删除,防止误删
(6)开启HTTP调试模式
当处于debug模式时,可以输入执行的全部SQL语句,以便调试。
如:
SELECT times FROM tpl_user_session_10 WHERE (user_id = ?); -- '74110'
{"ret":0,"data":{"code":0},"msg":""}
1.15.3 可选的Model基类
(1)表数据入口模式
我们一直在考虑,是否应该提供数据库的基本操作支持,以减少开发人员重复手工编写基本的数据操作。
最后,我们认为是需要的。然后就引发了新的问题:是以继承还是以委托来支持?
委托有助于降低继承的层级,但仍然需要编写同类的操作然后再次委托。所以,这里提供了基于NotORM的Model基类:PhalApi_Model_NotORM。
然而提供这个基类还是会遇到一些问题,例如:如何界定基本操作?如何处理分表存储?如何支持定制化?
由于我们这里的Model使用了 “表数据入口” 模式,而不是“行数据入口”,也不是“活动纪录”,也不是复杂的“数据映射器”。所以在使用时可以考虑是否需要此基类。即使这样,你也可以很轻松转换到 “行数据入口”和“活动纪录”模式。这里,PhalApi中的Model是更广义上的数据源层(后面会有更多说明),因此对应地 PhalApi_Model_NotORM基类充当了数据库表访问入口的对象,处理表中所有的行。
(2)规约层的CURD
在明白了Model基类的背景后,再来了解其具体的操作和如何继承会更有意义。
而具体的操作则与数据表的结构相关,在“约定编程”下:即每一个表都有一个主键(通常为id,也可以自由配置)以及一个序列化LOB字段ext_data。我们很容易想到Model接口的定义(注释已移除,感兴趣的同学可查看源码):
interface PhalApi_Model {
public function get($id, $fields = '*');
public function insert($data, $id = NULL);
public function update($id, $data);
public function delete($id);
}
上面的接口在规约层上提供了基于表主键的CURD基本操作,在具体实现时,需要注意两点:一是分表的处理;另一点则是LOB字段的序列化。
(3)不使用Model基类的写法
由于我们使用了NotORM进行数据库的操作,所以这里也提供了基于NotORM的基类:PhalApi_Model_NotORM。下面以我们熟悉的获取用户的基本信息为例,说明此基类的使用。
为唤醒记忆,下面贴上Model_User类原来的代码:
// $ vim ./Demo/Model/User.php
class Model_User {
public function getByUserId($userId) {
return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
}
}
对应的调用:
$model = new Model_User();
$rs = $model->getByUserId($userId);
(4)继承Model基类的写法
若继承于PhalApi_Model_NotORM,则是:
// $ vim ./Demo/Model/User.php
class Model_User extends PhalApi_Model_NotORM {
protected function getTableName($id) {
return 'user';
}
}
从上面的代码可以看出,基类已经提供了基于主键的CURD操作,但我们需要钩子函数以返回对应的表名。相应地,外部调用则调整为:
$model = new Model_User();
$rs = $model->get($userId);
再进一步,我们可以得到其他的基本操作:
$model = new Model_User();
// 插入
$model->insert(array('name' => 'whatever', 'from' => 'somewhere'));
// 更新
$model->update(1, array('name' => 'dogstar huang'));
// 删除
$model->delete(1);
1.15.4 定制化你的Model基类
正如上面提及到的两个问题:LOB序列化和分表处理。所以,如果PhalApi现有就此两问题的解决方案不能满足项目的需求,可作定制化处理。
(1)LOB序列化
先是LOB序列化,考虑到有分表的存在,当发生数据库变更时(特别在线上环境)会有一定的难度和风险,因此引入了扩展字段ext_data。当然, 此字段也应对数据库变更的同时,也可以作为简单明了的值对象的大对象。序列化LOB首先要考虑的问题是使用二进制(BLOB)还是文本(CLOB),出于 通用性、易读性和测试性,我们目前使用了json格式的文本序列化。所以,如果考虑到空间或性能问题(在少量数据下我认为问题不大,如果数据量大,应该及 时重新调整数据库表结构),可以重写formatExtData() & parseExtData()。
如改成serialize序列化:
abstract class My_Model_NotORM extends PhalApi_Model_NotORM {
/**
* 对LOB的ext_data字段进行格式化(序列化)
*/
protected function formatExtData(&$data) {
if (isset($data['ext_data']) && !is_string($data['ext_data'])) {
$data['ext_data'] = serialize($data['ext_data']);
}
}
/**
* 对LOB的ext_data字段进行解析(反序列化)
*/
protected function parseExtData(&$data) {
if (isset($data['ext_data']) && !is_string($data['ext_data'])) {
$data['ext_data'] = unserialize($data['ext_data'], true);
}
}
// ...
}
(2)分表处理
其次是分表处理,同样考虑到分表的情况,以及不同的表可能配置不同的主键表,而基于主键的CURD又必须要先知道表的主键名才能进行SQL查询。所 以,问题就演变成了如何找到表的主键名。这里可以自动匹配,也可以手工指定。自动匹配是智能的,因为当我们更改表的主键时,可以自动同步更新而不需要担心 遗漏(虽然这种情况很少发生)。手工指定可以大大减少系统不必要的匹配操作,因为我们开发人员也知道数据库的主键名是什么,但需要手工编写一些代码。在这 里,提供了可选的手工指定,即可重写getTableKey($table)来指定你的主键名。
如,当我们的表的主键都固定为id时:
abstract class My_Model_NotORM extends PhalApi_Model_NotORM {
protected function getTableKey($table) {
return 'id';
}
}