matiasmuller/yii-conditions

允许以模块化的方式重用预定义查询条件的条件,遵循DRY原则。

安装: 2

依赖: 0

建议者: 0

安全性: 0

星标: 0

关注者: 0

分支: 1

类型:yii2-extension

dev-master 2022-10-21 22:29 UTC

This package is not auto-updated.

Last update: 2024-09-20 09:21:36 UTC


README

Yii Conditions 是一个针对 Yii 2 的 ActiveQuery 类的扩展,允许模块化地重用预定义查询条件的条件,遵循DRY原则。

目录

安装

推荐的安装方式是通过 Composer

composer require matiasmuller/yii-conditions

使用

准备查询模型

开始使用 Yii Conditions 之前,首先需要一个扩展自 yii\db\ActiveRecord 类的模型,并且其finder配置为一个扩展自 yii\db\ActiveQuery 类的实例。该实例将实现此扩展。例如,假设我们有一个用户模型类

namespace app\models;

use Yii;
use yii\db\ActiveRecord;
use app\models\UsuarioQuery;

class Usuario extends ActiveRecord
{
    // ... 

    public static function find()
    {
        return Yii::createObject(UsuarioQuery::className(), [get_called_class()]);
    }

    // ... 
}
namespace app\models;

use Yii;
use yii\db\ActiveQuery;

class UsuarioQuery extends ActiveQuery
{
    // ... 
}

接下来将trait添加到查询类中

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class UsuarioQuery extends ActiveQuery
{
    use Conditions;

    // ... 
}

添加这个之后,类已经准备好使用 Yii Conditions

创建第一个条件。

假设在原始查询类中有一个用于查询(或修改查询)用户的方法。

namespace app\models;

use Yii;
use yii\db\ActiveQuery;

class UsuarioQuery extends ActiveQuery
{
    // ... 

    public function activos()
    {
        return $this->andWhere(['activo' => 1]);
    }
    
    // ... 
}

如果以以下方式定义方法

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class UsuarioQuery extends ActiveQuery
{
    use Conditions;
    
    // ... 

    protected function conditionActivos()
    {
        return ['activo' => 1];
    }
    
    // ... 
}

Conditions trait 的工作方式是,当查询 app\models\Usuario::find()->activos()->all() 时,此实现的结果与之前的实现等效。然而,这种第二种实现现在给我们带来了一系列重大优势,我们将在下面看到。

Yii Conditions 实现了一个 流畅的接口,因此可以链式地添加必要的条件,并相互组合。

逻辑修改器

从方法调用中修改逻辑

一旦定义了任何条件 conditionFoo(),我们可以调用以下方法

Model::find()->foo()->all();  // and where [condition]
Model::find()->andFoo()->all(); // and where [condition] , equivalente al llamado anterior
Model::find()->orFoo()->all(); // or where [condition]
Model::find()->notFoo()->all(); // and where not ([condition])
Model::find()->andNotFoo()->all(); // and where not ([condition]) , equivalente al llamado anterior
Model::find()->orNotFoo()->all(); // or where not ([condition])

这样我们就可以看到如何以更灵活的方式使用每个条件,同时流畅的接口允许我们从基本条件开始构建更复杂的组合,这些基本条件将不再被重新定义。

继续以用户为例,接下来我们可以看到如何通过组合两个基本条件,查询一个稍微复杂的结果

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class UsuarioQuery extends ActiveQuery
{
    use Conditions;
    
    protected function conditionTienenTelefono()
    {
        return ['not', ['telefono' => null]];
    }

    protected function conditionTienenEmail()
    {
        return ['not', ['email' => null]];
    }
}
namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Usuario;

class UsuarioController extends Controller
{
    public function actionFoo()
    {
        $usuariosConContacto = Usuario::find()
            ->tienenTelefono()
            ->orTienenEmail()
            ->all();

        $usuariosSinContacto = Usuario::find()
            ->notTienenTelefono()
            ->andNotTienenEmail()
            ->all();
    }
}

从条件定义中链式修改逻辑

从前面的例子中,我们可以将带有联系人的用户查询封装起来,并通过类中另一个方法使其可重用,具体方式如下:

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class UsuarioQuery extends ActiveQuery
{
    use Conditions;
    
    protected function conditionTienenTelefono()
    {
        return ['not', ['telefono' => null]];
    }

    protected function conditionTienenEmail()
    {
        return ['not', ['email' => null]];
    }

    // Combina las dos condiciones anteriores
    protected function conditionTienenContacto()
    {
        return ['or',
            'tienenTelefono',
            'tienenEmail',
        ];
    }
}

这样,我们不仅可以在前面的例子中查询带有联系人的用户,还可以自动获取没有联系人的用户,如下面的例子所示。

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Usuario;

class UsuarioController extends Controller
{
    public function actionFoo()
    {
        $usuariosConContacto = Usuario::find()->tienenContacto()->all();

        $usuariosSinContacto = Usuario::find()->notTienenContacto()->all();
    }
}

这种方式使我们免去了否定一组条件带来的复杂性,这可能导致错误。同时,我们也抽象了查询中涉及的逻辑。结果是,如果某个条件,例如“有电话”,需要以不同的方式实现,那么只需要更改这个基本条件的实现,所有的查询都会自动修正。

需要注意的是,在定义其他条件时包含条件,这些包含的字符串类型的条件最终会被解释为array类型的条件,这是Yii识别的方式。例如,以下是一个正确和一个错误的包含条件的方式。

protected function conditionActivos()
{
    return ['activo' => 1];
}

protected function conditionTienenTelefono()
{
    return ['not', ['telefono' => null]];
}

protected function conditionInactivos()
{
    // 'activos' es equivalente a ['activo' => 1]

    return ['not', ['activos']]; // Forma INCORRECTA, devuelve ERROR
                                 // Es equivalente a ['not', [['activo' => 1]]]

    return ['not', 'activos'];   // Forma CORRECTA
                                 // Es equivalente a ['not', ['activo' => 1]]
}

有时为了可读性或语义,甚至可以创建其他条件的别名。

protected function conditionTienenTelefono()
{
    return ['not', ['telefono' => null]];
}

protected function conditionConTelefono()
{
    return 'tienenTelefono';
}

// ->tienenTelefono() y ->conTelefono() 
// devolverán el mismo query

条件可以无限嵌套,只要逻辑上嵌套是连贯的,并且条件之间的依赖没有循环。

直接条件或关系条件

到目前为止,我们已经定义了一种条件类型,即直接条件,它直接应用于模型及其数据库中的对应实体(无论是否执行join、in()和其他操作,请参阅注意事项。还有可能构建基于ActiveRecord模型中定义的关系的查询,这大大扩展了查询的模块化和重用性。

关系条件

关系条件允许我们,给定模型ActiveQuery中定义的关系,可以使用它作为查询的一部分来根据通过该关系存在或不存在相关的实例来条件化查询结果。以下我们将继续使用Usuario类的例子来查看可能的不同类型的关系条件。

namespace app\models;

use Yii;
use yii\db\ActiveRecord;
use app\models\UsuarioQuery;

class Usuario extends ActiveRecord
{
    // ...

    public static function find()
    {
        return Yii::createObject(UsuarioQuery::className(), [get_called_class()]);
    }

    public function getCiudad()
    {
        return $this->hasOne(Ciudad::class, ['id' => 'ciudad_id']);
    }

    public function getPosts()
    {
        return $this->hasMany(Post::class, ['usuario_id' => 'id']);
    }

    // ...
}

纯关系

这包括查询该关系的存在性,正如它所定义的那样。

Foo::find()->withBar()->all();

在前面Usuario类的例子中,我们可以查询已分配地区的用户,或者拥有帖子的用户。

use app\models\Usuario;

// Devuelve los usuarios con localidad asignada
Usuario::find()->withLocalidad()->all(); 

// Devuelve los usuarios propietarios de posts
Usuario::find()->withPosts()->all(); 

正如我们所看到的,无论是hasOne还是hasMany关系都兼容关系条件(请参阅与关系条件兼容的关系

在方法中包含条件的关联

通过向纯关系条件调用添加一个关系或一系列纯关系,始终以camel_case格式,我们也可以条件化我们引用的查询中的关系,同时保持代码的清晰性和易于阅读。

Foo::find()->withOneConditionAnotherConditionBar()->all();

或者

Foo::find()->withBarOneConditionAnotherCondition()->all();

根据在定义这些条件的查询模型中配置的`conditionRelationNameFirst()`函数。

这种配置对于保留“人类”语法,无论语言如何都很有用。默认配置,即条件关系名称在前,与英语(例如:`->withActiveUsers()`)很好地匹配,而相反的选项更适合西班牙语(例如:`->withUsuariosActivos`)。

在先前的纯关系示例中,我们看到了如何查询用户是否有分配的地点。这种查询很可能是多余的,因为通常每个用户都必须分配一个地点。然而,我们可能会遇到以下场景

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class LocalidadQuery extends ActiveQuery
{
    use Conditions;

    protected function conditionActivas()
    {
        return ['activa' => 1];
    }

    // ...
}

这个条件使我们能够通过Localidad::find()->activas()->all()获取“活跃”的地点,正如我们已经看到的。但它也自动为我们提供了查询属于活跃地点的用户的机会,按照之前提到的通过关系名称、条件名称“activas”和以下方式的驼峰命名法来链接关系名称

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class UsuarioQuery extends ActiveQuery
{
    use Conditions;

    // Definido para que el orden de parseo sea compatible con el idioma español
    public function conditionRelationNameFirst() { return true; }

    // ...
}

// Devuelve los usuarios pertenecientes a localidades activas
Usuario::find()->withLocalidadActivas()->all(); 

Localidad是单数,因为它是关系名称;而Activas是复数,因为它对应于条件的声明方式(通常,条件名称按照惯例用复数形式声明)。

从调用方法中获取关系名称和后续条件,是按照首先找到的顺序。

在参数中包含条件的关联

如果我们需要自定义正在过滤的关系的筛选条件,可以将该条件作为调用关系条件的第一个参数,如下所示

Foo::find()->withBar($rawCondition)->all();

传递给参数的条件与条件定义返回的形式相同,即一个条件或条件组合数组的组合。

// Equivalente a la forma general expuesta en el tipo de condición anterior
Foo::find()->withBar(['and', 'oneCondition', 'anotherCondition'])->all();

将前面的地点示例翻译成这种条件形式,我们得到

// Devuelve los usuarios pertenecientes a localidades activas
Usuario::find()->withLocalidad('activas')->all(); 

尽管这种条件类型的主要优点是能够进行更复杂的条件筛选,例如

// Devuelve los usuarios con posts activos o no eliminados
Usuario::find()->withPosts(['or', 
    'activos',
    ['not', 'eliminados']
])->all(); 

关系条件的调用修改器

所有关系条件调用都接受从方法调用开始相同的逻辑修改器。

->[not]WithRelation...(...)
->[or|and][Not]WithRelation...(...)

例如

Foo::find()->notWithBar()->all();
Foo::find()->andWithBarOneCondition()->all();
Foo::find()->orNotWithBar('anotherCondition')->all();

与关系条件兼容的关系

可以使用的关联关系可以是简单的也可以是复合的。如果是复合的,那么无论是使用via还是viaTable方法构建的,都是兼容的。同样,如果关联关系在其实现中进行了如“andWhere”、“andCondition”之类的查询修改,则这种修改将被保留。相关查询的构建不是通过连接来实现的,而是在嵌套子查询上,这样既可以避免当关系实现中包含个性化时发生的冲突;也可以避免在hasMany关系的情况下元素重复的问题。

表达式评估条件

通常,只有在满足某些条件的情况下,才需要对查询应用条件,或者根据参数查询条件是正面的还是负面的,例如在生成报告时。

"If" 条件评估

这个条件通过将后缀“If”添加到方法名称来工作。这样做后,该方法期望接收一个参数,并会根据该参数的布尔值接收条件。如果参数返回一个等于true的值,条件将链接到查询,否则将省略。

Model::find()->fooIf($condicion)

例如,此操作只有在请求请求时才会获取活动用户。

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Usuario;

class UsuarioController extends Controller
{
    public function actionListarUsuarios()
    {
        $filtrarActivos = Yii::$app->request->get('mostrar_activos');

        $usuarios = Usuario::find()->activosIf($filtrarActivos)->all();
    }
}

"Based on" 条件评估

这个条件通过将后缀“BasedOn”添加到方法名称来工作。与“If”条件类似,它期望接收一个参数,但与“If”不同,“BasedOn”根据参数的布尔值返回正条件或负条件,如果参数有值为空,则省略条件。被解释为空的值是null、空字符串或只包含空格的字符串。其余的值为假(如false0[]),被视为负值,并将导致条件的否定。

Model::find()->fooBasedOn($condicion)

以下示例说明了根据请求接收的参数进行用户查询的结果。

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Usuario;

class UsuarioController extends Controller
{
    public function actionListarUsuarios()
    {
        // Si llega por ejemplo '1', es de valor positivo
        // Si llega por ejemplo '0', es de valor negativo
        // Si llega por ejemplo '', es de valor es nulo
        $conTelefono = Yii::$app->request->get('con_telefono');

        // Devuelve usuarios "con teléfono" si es de valor positivo
        // Devuelve usuarios "sin teléfono" si es de valor negativo
        // Devuelve todos los usuarios si es de valor nulo
        $usuarios = Usuario::find()->tienenTelefonoBasedOn($conTelefono)->all();
    }
}

列出的所有 逻辑修饰符 都与 条件评估 兼容。无论是直接条件还是关系条件都接受这些修饰符。以下是一些示例

Model::find()->andFooIf($condicion)->all();
Model::find()->orNotFooBasedOn($condicion)->all();
Foo::find()->notWithBarIf($condicion)->all();
Foo::find()->withBarOneConditionBasedOn($condicion)->all();
Foo::find()->withBarBasedOn($condicion, 'anotherCondition')->all();

注意:第一个参数始终是条件评估的条件。在最后一个示例中,可以看到使用关系条件与条件作为参数的参数被移至第二个位置,而将第一个位置留给评估。

向条件传递参数

很可能不久就需要定义依赖于某些参数的条件,为此我们通常会定义一个接收参数作为参数的函数。以下是有关条件定义和其调用的规范。

  • 可以直接条件定义所需的任何数量的参数。没有限制,调用时直接按照定义的方式进行。
// Definición de la condición
protected function conditionFoo($param1, $param2) {
    // ...
}

// Llamado a la condición
Model::foo('val1', 'val2')->all();
  • 可以将参数值传递给定义的条件,即使它们作为子条件出现在另一个条件的定义中也是如此。在 : 之后分配,并由 , 分隔。
// Definición de condición dependiente de foo con parámetros fijos
protected function conditionBar() {
    return 'foo:val1,val2';
}

// Definición de condición dependiente de foo con un parámetro fijo y uno variable
protected function conditionBaz($param1) {
    // Nótese las comillas dobles para la interpolación de $param1
    return "foo:val1,$param1";
}

// Equivale a llamar Model::foo('val1', 'val2')->all();
Model::bar()->all();

// Equivale a llamar Model::foo('val1', 'val3')->all();
Model::baz('val3')->all();
  • 纯关系不携带参数。正是由于它们是纯的,这意味着它们不受任何额外标准的约束。唯一存在的可能性是使用一个唯一的参数,这将自动将条件转换为 参数中的条件关系

  • 方法中的条件关系接收所需的任何数量的参数。这些参数将被应用于每个条件。在这种情况下使用参数主要是当使用单一条件时,这是最常见的情况。如果需要以不同的方式论证两个或多个条件,始终可以采用条件数组,将参数附加到子条件中,并在其后添加 :,如前所述。

  • 当应用 "If" 或 "Based On" 评估条件时,总是将参数移动一个位置,将第一个位置留给评估条件的参数,如 之前所述

结果修改器 "Condition"

如果从一个条件的调用中仅希望获取可由 Yii 解析的 array,请在方法名称末尾添加后缀 Condition。例如

namespace app\models;

use Yii;
use yii\db\ActiveQuery;
use MatiasMuller\YiiConditions\Conditions;

class UsuarioQuery extends ActiveQuery
{
    use Conditions;

    protected function conditionActivos()
    {
        return ['activo' => 1];
    }

    protected function conditionTienenTelefono()
    {
        return ['not', ['telefono' => null]];
    }

    protected function conditionEjemplo()
    {
        return ['and', 'activos', 'tienenTelefono'];
    }

    // ...
}
namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Usuario;

class UsuarioController extends Controller
{
    public function actionFoo()
    {
        // Ejemplo de la condición original
        $condicionPositiva = Usuario::find()->ejemploCondition();
        // Retorna ['and', ['activo' => 1], ['not', ['telefono' => null]]]
        
        // Ejemplo de la condición "negada"
        $condicionNegada = Usuario::find()->notEjemploCondition();
        // Retorna ['not', ['and', ['activo' => 1], ['not', ['telefono' => null]]]]

        // Cualquiera de las condiciones es compatible con ActiveQuery de Yii
        Usuario::find()->where($condicionPositiva)->all();
    }
}

列出的所有 逻辑修饰符 以及 条件评估 都是 结果修饰符 的兼容。无论是直接条件还是关系条件都接受。以下是一些示例

Model::find()->andNotFooCondition();
Model::find()->orFooBasedOnCondition($condicion);
Foo::find()->withBarCondition()->all();
Foo::find()->orNotWithBarBasedOnCondition($condicion, [
    'and', 'oneCondition', 'anotherCondition',
])->all();

whereCondition() 方法

允许以与 Yii 使用 where() 方法相同的方式进行评估,但支持 "raw conditions"。这在不需要声明即可即时执行复合条件时非常有用。接受一个包含选项的第二个参数的数组,这些选项可以是

// Activa el modo de condicional de evaluación
$evaluation           // string 'If' | 'BasedOn'

// Activa el modificador de resultado "Condition"
$returnsConditionOnly // bool,

// Asigna el operador en caso de agregar otra condición a una ya existente
$combineWith          // string 'and' | 'or'

还有 andWhereCondition()orWhereCondition() 方法,与框架中的本地 andWhere()orWhere() 类似。

包含的预定义条件

当前包包含在 trait Conditions 中预定义的一个条件,因此每个使用此 trait 的查询模型都可以使用它。

->elems($ids)

查找所有记录,其中其主键与通过参数传递的 idarray ids 相匹配。满足文档中提到的所有规范。独立于主键字段名称工作。以下是一些示例

// Consulta por el usuario con id = 1
Usuario::find()->elems(1)->one(); // Equivalente a Usuario::findOne(1)

// Consulta por los usuarios que tienen los ids 1 o 2. $filtrar = true, entonces 
// se ejecuta la condición, y el argumento de los ids se desplaza al segundo lugar.
$filtrar = true;
Usuario::find()->notElemsIf($filtrar, [1, 2])->all();

// Consulta los usuarios que correspondan a la localidad con id 1 o 2.
Usuario::find()->withLocalidadElems([1, 2])->all();

// Construye la consulta de los usuarios que, o bien sean activos, o bien sean los
// usuarios con id 3 o 4. Retorna un array compatible con ->where() de ActiveQuery.
$condicion = Usuario::find()->activos()->orElemsCondition([3, 4]);
// Ejecuta la consulta
Usuario::find()->where($condicion)->all();

条件标签

该包还包含条件标签,类似于"属性标签",这是ActiveRecord实现的,当构建使用可组合条件作为筛选器进行自定义查询的视图时很有用,例如用于报告。

// Definición de las etiquetas dentro del modelo de consulta
public function conditionsLabels()
{
    return [
        // ...
    ];
}

// Uso de las etiquetas
Model::find()->getConditionLabel($nombreCondicion);

例如

namespace app\models;

use Yii;
use yii\db\ActiveQuery;

class UsuarioQuery extends ActiveQuery
{
    public function conditionsLabels()
    {
        return [
            'activos' => 'Activos',
            'withLocalidadActivas' => 'Con localidad activa',
        ];
    }
}
Usuario::find()->getConditionLabel('activos'); 
// 'Activos'

Usuario::find()->getConditionLabel('withLocalidadActivas'); 
// 'Con localidad activa'

Usuario::find()->getConditionLabel('tienenTelefono'); 
// 'Tienen Telefono' (interpreta la notación "camel case" cuando no está definida)

使用的术语

  • 原始条件:一个字符串指令或包含指令的数组,可以是ActiveQuery的内置条件或其他Condition类型的条件。
  • 处理后的条件:一个可以由Yii的ActiveQuery的->where()解析的数组。

注意

  1. 可以通过仅将Trait包含在查询模型中,并逐个替换正常函数(如ejemplo())为相应的条件函数(如conditionEjemplo()),返回定义条件的数组而不是查询对象本身,来逐步替换正常的Yii实现。

  2. 请注意在查询模型中定义__call()函数。这个库使用MatiasMuller\MethodsStacks\StackableCall,所以如果已定义,请将其重命名为__callfromLoQueSea,它将继续正常工作。如需更多详细信息,请查看相关文档

  3. 如果需要应用排序或连接,例如,可以在条件定义中完成,只要返回的是"原始条件"的数组即可。

  4. 要将在所有模型中实现Yii Conditions,建议创建一个通用类,该类扩展自yii\db\ActiveQuery,并将Trait包含在这个新类中,用作查询模型的基类。