改进thinkPHP 5 开发体验(3)

469

使用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.其实不是,data. 其实不是,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;
    }
  

对于上面这个代码,有很多小伙伴说过,我是真的醉了。为什么呢?除非真的没有编程规范的公司,并且是极度初级的程序员,才敢用这个函数。试想想,有谁对数据库操作完成后不想要操作结果,但上面这个代码,结果存在result中,返回的是result中,返回的是model,你在$model中根本拿不到结果。它为什么要这么做呢?是为了方便链式调用。如果程序员都这样链式调用,那么,这对项目来说是毁灭性的。

另一方面,还有一个好的方法,如果你没有复杂的orm数据,那么,你的repository就不要做成abstract的,有个好办法,那就是,在你继承的Model中增加一个方法,用当前模型$this作为构造函数的参数返回repository对象。这样,只要一个repository类就够了。

到目前为止,你又让tp向Laravel进了一步。虽然,你未使用任何Laravel组件,但你抄了Laravel的 prettus/l5-repository,使用了完整的ORM分层架构。所以,为你你未来迁移到Laravel又打下了一个基础。加油!