{% block name %}Cast de colonnes{% endblock %} {% block content %}

Dans certains cas il est nécessaire de concaténer deux colonnes en une seule pour l'affichage (ex: nom / prénom).

Si la colonne n'est pas filtrable / triable, la création d'une fonction dans l'entité permettant la récupération de cette concaténation est possible (mais non recommandée):

class Entity {
    ...
    public function nomPrenom() {
        return $this->nom . ' ' . $this->prenom;
    }
    ...
}
// on peut donc mettre en colonne: d.nomPrenom

Cependant, les filtres et tri étant appliqué au niveau de doctrine, la concaténation n'est pas faites et une erreur "colonne 'nomPrenom' non trouvé" surviendra.

Faire fonctionner le tri

En SQL, un tri se fait sur une colonne du select, il est donc possible de faire un cast de la colonne dans le select: CONCAT(d.nom, ' ', d.prenom) AS nomPrenom

La colonne a utiliser dans la configuration de la liste sera l'alias que vous aurez donné au concat, ici, nomPrenom.

Faire fonctionner un filtre de recherche

En SQL, un where est appliqué sur le jeu de données du from et des join, il est donc nécessaire de créer un filtre custom pour nous permettre d'injecter notre CAST. En effet, avec les queryBuilder il n'est pas possible d'ajouter des sous-requêtes, ce qui aurait été plus simple mais impossible.

Créer une classe qui hérite de SearchFilter comme cela:

class NomPrenomFilter extends SearchFilter {
    public function __construct(
        array $columns,
        array $listColumns,
        array $options = []
    ) {
        parent::__construct($columns, $listColumns, $options); // on délégue l'initialisation à la classe parente
        
        $this->processors = []; // on vide tout les processeurs obsolètes
        $this->addProcessor(new NomPrenomSearchProcessor()); // on ajoute notre custom incluant notre logique
    }
}

Comme on vois, un processeur est ajouté permettant l'application du filtre. Celui-ci dois implémenter FilterProcessor et peut utiliser le trait DoctrineListProcessorTrait:

class NomPrenomSearchProcessor implements FilterProcessor {
    use DoctrineListProcessorTrait;

    public function process(&$value, FilterData $data, array $submitted = []): void
    {
        // la structure actuel ne nous permet pas d'hériter de SearchFilter
        // on doit donc copier coller

        $columns = $data->getParent()->getColumns();
        $actualValue = mb_strtolower($data->getValue());
        $paramName = 'value'.uniqid();
        $value->andWhere(
            $value->expr()->orX(
                ...array_map(
                    function (string $column) use ($value, $paramName) {
                        $column = $this->reduceColumn($column);

                        if($column === "nomPrenom") {
                            // on a affaire à notre colonne concaténée
                            $column = "CONCAT(d.nom, ' ', d.prenom)";
                        }
                        return $value->expr()->like("LOWER($column)", ':'.$paramName);
                    },
                    $columns
                )
            )
        )->setParameter($paramName, "%$actualValue%");
    }

}

Dans cette classe on peut voir qu'à un moment, lorsqu'on rencontre notre colonne nomPrenom, on la remplace par notre concaténation nous permettant donc de filtrer dessus.

Attention, il est nécessaire d'ajouter soit une méthode dans l'entité pour concaténer ou la concaténation dans le select, pour permettre l'affichage de la colonne. Cette manipulation avec le filtre ne garantit pas la présence de la colonne dans le jeu de données final mais la possibilité de filtrer dessus.

{% endblock %} {% block preview %} {% endblock %}