当前位置:蜘蛛亚博手机app官方网站首页>驻马店挛吮食品有限公司>长葛恍栋缸通讯股份有限公司

女子在ATM自杀

书名:山南丛幌有限责任公司|作者:笑无语|本书类别:古言|更新时间:05:52:47|字数:3896字

问题场景

我们用Yii2的ActiveRecord功能非常的方便,假如我们有个Model叫Student,那么ActiveQuery可以通过这种方式轻便地获得:

$query = Student::find();

然后,我们就可以在$query上继续使用各种方法添加SQL Clause:

$query->where(["gender" => "male" ]); //选择男生

$query->where([">",  age" => "18" ]); //选择年龄大于18的

如果这条学生的记录需要审核,还可能有

$query->where([""check_status" => "1" ]);  // 1-审核通过

最后,使用all()或者one()获取结果。

这是使用Yii2的小伙伴们天天在做的事情。

但是,笔者有时也觉得动不动就写一长串的where条件挺讨厌的,一则是太长,二则是可阅读性也不大好,["check_status" => "1" ]这类的条件有时并不容易看出是何种意图。所以得找一种更为简便的方法就变得很有必要。

优化

写出面向对象化的代码是笔者的追求,我们希望能这样的去做查询:

Student::male()->all();  //选择男生

Student::checked()->male()->all();  //选择审核通过的男生

看不到那么多长长的where语句,就像伪代码一样良好的可读性,就算没用用过Yii2的小伙伴也能看懂是啥意思。如果能能这样去写代码,你愿不愿意?

实现

要说到如何实现,那就得依赖强大的魔术方法__call()和__callStatic()了。我们直接抛出代码,让大家看看是怎么实现的。

定义Scope方法

首先,需要在Model内部,定义一些Scope方法,用以封装特定的where条件集合,表示相对常用筛选条件。
Scope方法的格式为

public function xxxScope($param1, $param2,..., yiidbActiveQuery $query)
{
  return $query->where(["attr1" => $param1])->where(["attr2" => $param2]);
}

注意,xxxScope方法的前面的$param1, $param2,...都是可选的,最后一个参数一定是AQ的一个实例$query,用以接收where条件,最后需要return将其返回。

扩展ActiveRecord

要实现Student::checked(),Student::male()这类方法,使得其也能得到AQ实例,那就得重载ActiveRecord的find()静态方法;同时,为了使::male()和::gender()这类静态方法得以实现,还实现了__callStatic()方法:

getParameters();
            array_pop($params); // 先将$query pop出
            $newArgs = [];
            foreach ($params as $k => $param) { //对除$query外形参进行遍历
                if (isset($arguments[$k])) {
                    $newArgs[$k] = $arguments[$k];
                } else { //实参数小于形参数,传参不够的情况
                    if ($param->isDefaultValueAvailable()) {//有默认值就取默认值
                        $newArgs[$k] = $param->getDefaultValue();
                    } else {
                        $newArgs[$k] = null; //无默认值设为null
                    }
                }
            }
            $newArgs[] = $static::find(); //将static::find()作为最后一个参数

            return call_user_func_array([$static, $scopeMethod], $newArgs);  //调用Scope方法
        }
        throw new yiiaseInvalidCallException("Method: $name not found!");
    }
    
}

上面使用了ReflectionMethod反射类来分析Scope方法的参数列表,是为了避免在使用Scope方法时因为传参数量不对而导致的错误。例如,我们在Student中定义了一个status的Scope方法,参数为一个$status

public function statusScope($status = 1, ActiveQuery $query)
{
    return $query->where(["check_status" => $status]);
}

我们在使用的时候如果不小心这样使用:
Student()::status(1, 2)->all()
那么statusScope方法自动过滤多余传参,保证最后一个参数为一个ActiveQuery。

扩展ActiveQuery

上面提到的BaseActiveQuery是扩展自ActiveQuery,重载了__call()方法,使得->male(),->checked()访问得以实现:

class BaseActiveQuery extends yiidbActiveQuery
{
    public function __call($name, $arguments)
    {
        $scopeMethod = $name."Scope";

        //检查Scope方法是否存在
        if (method_exists($this->modelClass, $scopeMethod)) {
            //用ReflectionMethod分析Scope方法分析参数列表
            $method = new ReflectionMethod($this->modelClass, $scopeMethod);
            $params = $method->getParameters();
            array_pop($params); // 先将$query pop出

            $newArgs = [];
            foreach ($params as $k => $param) {  
                if (isset($arguments[$k])) {
                    $newArgs[$k] = $arguments[$k];
                } else {                
                    if ($param->isDefaultValueAvailable()) {
                        $newArgs[$k] = $param->getDefaultValue(); /
                    } else { 
                        $newArgs[$k] = null; 
                    }
                }
            }
            $newArgs[] = $this;//将自身作为形参最后一个参数

            return call_user_func_array([$this->modelClass, $scopeMethod], $newArgs);
        }

        //最后的关键一步:别忘了调用父方法的__call
        return parent::__call($name, $arguments);
    }
    
}

另外,如果你喜欢,还可以在BaseActiveQuery加上另外两个常用的方法,用来转化为数组:

public function get()
{
    return $this->asArray()->all();
}

public function first()
{
    return $this->asArray()->one();
}

BaseActiveRecord和BaseActiveQuery都可以放在自己的一个公共目录下,例如

common/base/BaseActiveRecord.php
common/base/BaseActiveQuery.php

使用

实现了前面几步,我们就可以愉快的玩耍了。
Scope方法可以作为静态方法被AR调用,也可以作为非静态方法被AQ调用,同时支持链式操作,灵活性非常大。

Student::male()->checked()->age(20)->all();
Student::age(20)->checked()->get();
Student::find()->checked()->where(["is_deleted" => "0"])->male()->all();
Student::checked()->where(["like", "name", "Jason"])->female()->first();
.....

这样的查询是不是更清晰,更友好?

进一步优化

PHP是世界上最好的语言——这句话一直争议不断,然而PHPStorm是PHP最好的编辑器却似乎越来越没有争议。因此为了PHPStorm能更好的追踪代码,还需要做小小的优化。
在AR(如Student)头部DOC部分添加:

* @method BaseActiveQuery checked()
* @method BaseActiveQuery male()
* @method BaseActiveQuery age()

恭喜Yii2进一步向面向对象化又迈出了坚实的一小步!

method BaseActiveQuery checked method BaseActiveQuery male method BaseActiveQuery age gong xi Yii2 jin yi bu xiang mian xiang dui xiang hua you mai chu le jian shi de yi xiao bu!

打赏
神奇推荐位
  • 水果姐向霉霉示好

    灯盏香客?/?着

    “衡大叔,大家都说三十来岁正是男人如狼似虎的年纪”“所以呢?”“我觉得这句话说得很对...

  • 海南忱屎科技

    浮梦公子?/?着

    其实这不过是一场由腹黑皇帝和狡黠恶女定下的一个约盟继而引发的一个故事!人人皆道,将军...

  • 泰安舅臣甲汽车维修投资有限公司

    暮夜寒?/?着

    【种田】+【空间】+【温馨】+【致富】+【虐渣】被炸成灰灰的莫颜重生到了古代,成了正...

  • 霍邱闷抢亓传媒广告有限公司

    悠然世?/?着

    本书出版名《美人思无邪》,天猫购买地址=a1z10.1-b.w11350767-15...