Yii2, haciendo filtrado y ordenamiento con atributos virtuales de un modelo ActiveRecord

Hace unos días, expliqué en un artículo anterior, cómo agregar atributos virtuales (que no están en la BD) a un modelo ActiveRecord en Yii2, Pero al mostrar el listado de registros generado por GridView, los nuevos campos (virtuales) no aparecen con las opciones de ordenar o filtrar la lista por ellos, como en el siguiente ejemplo:

save image

Para solucionarlo hay que modificar el modelo de búsquedas respectivo, que normalmente lleva el mismo nombre de la clase principal, pero con el sufijo Search. En este ejemplo dicho modelo es: ClienteSearch.php y explicaré por separado cómo agregar las características de filtrado y de ordenamiento.

Filtrado

Lo primero será agregar los campos virtuales (nombre_completo y edad) a la lista de campos «safe» del método rules():

    public function rules()
    {
        return [
            [['id'], 'integer'],
            [['nombre', 'apellido', 'fecha_nacimiento', 'email', 'nombre_completo', 'edad'], 'safe'],
        ];
    }

A continuación hay que agregar las condiciones respectivas para cada campo virtual, al método search():

    public function search($params)
    {
        $query = Cliente::find();
        ...
        ...

        $query->andFilterWhere(['like', 'CONCAT(nombre, " ", apellido)', $this->nombre_completo]);
        $query->andFilterWhere(['(YEAR(CURRENT_DATE) - YEAR(fecha_nacimiento) - (RIGHT(CURRENT_DATE, 5) < RIGHT(fecha_nacimiento, 5)))' => $this->edad]);
        ...
        ...
    }

Nótese que las condiciones de filtro generadas incluyen algo de «inteligencia» del lado del motor de BD, dado que ninguno de los dos campos existe tal cual en la tabla:

  • Se compara el campo virtual nombre_completo con el nombre y apellido concatenados
  • Se compara el campo virtual edad con una operación que calcula la edad en base a la fecha de nacimiento en la BD

Ordenamiento

Para lograr ordenar el listado con los campos virtuales, hay que agregar las condiciones de ordenamiento al método search():

    public function search($params)
    {
        $query = Cliente::find();
        ...
        ...

        $dataProvider->sort->attributes['nombre_completo'] = [
            'asc' => ['CONCAT(nombre, " ", apellido)' => SORT_ASC],
            'desc' => ['CONCAT(nombre, " ", apellido)' => SORT_DESC],
        ];
        
        $dataProvider->sort->attributes['edad'] = [
            'asc' => ['(YEAR(CURRENT_DATE) - YEAR(fecha_nacimiento) - (RIGHT(CURRENT_DATE, 5) < RIGHT(fecha_nacimiento, 5)))' => SORT_ASC],
            'desc' => ['(YEAR(CURRENT_DATE) - YEAR(fecha_nacimiento) - (RIGHT(CURRENT_DATE, 5) < RIGHT(fecha_nacimiento, 5)))' => SORT_DESC],
        ];
        ...
        ...
    }

Con lo cual ya disponemos de las opciones de filtrado y ordenamiento por los campos virtuales en el listado del GridView:

save image