使用repository模式改进orm体验。
备注:本文针对tp5.0,由于tp不同版本的兼容性问题很多,以下这些内容仅供参考。
上一篇我们讲到了tp的数据库模型组件层的产品意识问题。那是对出错与异常管理的问题。本质上,函数使用体验也是有相当大的问题。首先,文档极其精简,像说天书。这可能是它要培训挣钱有关。但培训机构出来的,一样也不会。这就怪了。
模型Model类代码超长。有大量超过80行,甚致有上百行的函数。不仅如此,If嵌套5到7层都相当常见。代码跟天书一样。可是,Laravel框架的Eloquent,连注解加代码接近80行的也都没几个。真是没有比较就没有伤害。好的代码像首诗,烂的代码像坨屎。
最变态的,就是很多参数都叫data,你根本弄不清楚它倒底是什么。tp orm现在好像叫think orm了。但是,可惜的是,这一块,5.0中不是独立的,是非组件化的。
为什么要用repository呢?这是因为,传统的orm、Java是hibernate,php则是doctrine。而现在的orm基本都是源于Ruby的ActiveRecord进化而来。但tp的改进则毁坏了用户体验。证明开发者缺少产品意识。而ActiveRecord只有模型层,本质上就是数据库查询的构造器。这就是说,经常是你一个函数不能获取你要的数据或操作。
那么,repository怎么做呢?tp文档这一部分是要花钱买的。虽然现在有一些针对tp5的开源的repository,但实在是太弱了。但是,Laravel有一个极好的开源,叫prettus/l5-repository。你可以照抄一个。在这种情况下,你不必直接访问模型了,只要访问你自己写的函数就行了。所以,模型的使用,你只痛一次,后续只用你自己的repository了。比如,在repository中可以实现以下find函数:
/**
* @param array $where
* @param array|string $columns
*
* @return array
* @throws \app\common\exception\BaseException
*/
public function find($where, $columns = '')
{
$model = $this->makeModel();
try{
if(is_array($where)){
$model = $model->where($where);
}
if(!empty($columns)){
$model = $model->field($columns);
}
$result = $model->find();
if(is_object($result)){
return $result->toArray();
}
return $result;
}catch(\Exception $e){
throw new BaseException(['msg' => $e->getMessage()]);
}
}
为什么用:if(is_object($result)){ 来处理呢?我们可以看看tp的源码:
/**
* 查找单条记录
* @access public
* @param array|string|Query|\Closure $data
* @return array|false|\PDOStatement|string|Model
* @throws DbException
* @throws ModelNotFoundException
* @throws DataNotFoundException
*/
public function find($data = null)
{
//作者注:内部代码太烂了,不忍心贴出来
}
相信每一个看到这个注解的程序都会问,tp的函数有输入与输出规范吗? 这里传入的是Where条件,但它叫data太强大了,可以是id,还可是...,反正它是把一切搅在一起,命名为$data。可我作为用户,我根本不清楚它是如何搅的。单一责职原则,无论是类,还是方法函数,在tp中根本不起作用。 返回值,对于非强类型语言,最多是三态返回,即:正确数值,空值,错误(一般为false) 但是,你看看tp: return array|false|\PDOStatement|string|Model 共有5种类型。让你想跳楼!!!!
当然,完整的orm分层模式,一般是用于大项目的。所以,如果是大项目,你还是找个合适的框架。
另外,由于tp的依赖注入功能还不全面,所以,每一个repository对应哪个model,只能手工写死。千万不要用构造函数传参注入,否则,很多地方,这些类你就用不了了。想不通,php-di组件那么好,tp作者为什么要自己写一个这么弱的依赖注入。
为此,一个继承的repository可能像是这样:
/**
* Class UserRepository
*
* @package app\repositories
*/
final class UserRepository extends BaseRepository
{
/**
*
*/
public function __construct(){
parent::__construct();
$this->init();
}
/**
* @return void
* @throws \app\common\exception\BaseException
*/
public function init(){
$this->model_class = User::class;
$this->makeModel();
}
上面代码中,构造函数调用init,不再使用依赖注入。因为,tp没有手工注入的途径。
init中第一行,是写入类。为什么呢,其实主要是因为tp的Model中有很多静态方法。所以,方便静态调用。第二行则是用类创建Model的实例。
实现repository也有很多要注意的地方。因为,tp的数据库操作,一不小心就是坑。比如,模型有update方法,代码如下:
/**
* 更新数据
* @access public
* @param array $data 数据数组
* @param array $where 更新条件
* @param array|true $field 允许字段
* @return $this
*/
public static function update($data = [], $where = [], $field = null)
{
$model = new static();
if (!empty($field)) {
$model->allowField($field);
}
$result = $model->isUpdate(true)->save($data, $where);
return $model;
}
对于上面这个代码,有很多小伙伴说过,我是真的醉了。为什么呢?除非真的没有编程规范的公司,并且是极度初级的程序员,才敢用这个函数。试想想,有谁对数据库操作完成后不想要操作结果,但上面这个代码,结果存在model,你在$model中根本拿不到结果。它为什么要这么做呢?是为了方便链式调用。如果程序员都这样链式调用,那么,这对项目来说是毁灭性的。
另一方面,还有一个好的方法,如果你没有复杂的orm数据,那么,你的repository就不要做成abstract的,有个好办法,那就是,在你继承的Model中增加一个方法,用当前模型$this作为构造函数的参数返回repository对象。这样,只要一个repository类就够了。
到目前为止,你又让tp向Laravel进了一步。虽然,你未使用任何Laravel组件,但你抄了Laravel的 prettus/l5-repository,使用了完整的ORM分层架构。所以,为你你未来迁移到Laravel又打下了一个基础。加油!