Les fonctions DQL personnalisées avec Symfony

Qu’est ce que Doctrine ?

Doctrine est un ORM (Object Relational Mapping) pour les applications PHP.

Il permet de travailler de manière orientée objet, abstrait les requêtes SQL basiques et rend l’interaction avec la base de données plus intuitive.

Qu’est ce que le DQL ?

DQL (Doctrine Query Language) : un langage de requête spécialisée pour l’ORM Doctrine, similaire à SQL mais optimisée pour les relations entre objets.

SELECT u FROM User u JOIN u.post WHERE p.createdAt > '2024-01-01'

Comment créer une fonction DQL personnalisée ?

Il peut y avoir des fonctions que DQL ne fournit pas, et nous pouvons vouloir les développer nous-mêmes.

Écrivons une commande DQL personnalisée qui prend une chaîne donnée, la divise et renvoie l’index souhaité en exemple :

namespace App\Dql;

use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

class SplitStr extends FunctionNode
{
    public $stringExpression = null;
    public $delimiterExpression = null;
    public $positionExpression = null;

    public function parse(Parser $parser)
    {
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);

        $this->stringExpression = $parser->StringPrimary();
        $parser->match(Lexer::T_COMMA);

        $this->delimiterExpression = $parser->StringPrimary();
        $parser->match(Lexer::T_COMMA);

        $this->positionExpression = $parser->ArithmeticPrimary();

        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
    }

    public function getSql(SqlWalker $sqlWalker)
    {
        return sprintf(
            'REPLACE(SUBSTRING(SUBSTRING_INDEX(%s, %s, %s), ' .
            'LENGTH(SUBSTRING_INDEX(%s, %s, %s - 1)) + 1), %s, \'\')',
            $this->stringExpression->dispatch($sqlWalker),
            $this->delimiterExpression->dispatch($sqlWalker),
            $this->positionExpression->dispatch($sqlWalker),
            $this->stringExpression->dispatch($sqlWalker),
            $this->delimiterExpression->dispatch($sqlWalker),
            $this->positionExpression->dispatch($sqlWalker),
            $this->delimiterExpression->dispatch($sqlWalker)
        );
    }
}

Dans les commandes DQL personnalisés, il y a deux fonctions auxquelles nous devons prêter attention : getSql et parse.

Dans la section parse, nous devons spécifier les composants de la fonction que nous créons.

Voyons comment cette commande DQL est traitée dans parse() :

  1. On associe la fonction SPLIT_STR à un IDENTIFIER.
  2. On recherche la parenthèse ouvrante.
  3. On analyse la chaîne à diviser, notre premier paramètre.
  4. On associe la virgule pour le deuxième paramètre.
  5. On analyse le délimiteur (second paramètre).
  6. On associe la virgule pour le troisième paramètre.
  7. On analyse l’index (troisième paramètre).
  8. On associe la parenthèse fermante.

Après que la fonction parse ait été exécuté, nous passons à la fonction getSQL.

Cette fonction doit retourner une instruction SQL sous forme de chaîne de caractères.

SELECT SPLIT_STR(‘a|bb|ccc|dd’, ‘|’, 3) FROM Product e

Le code DQL ci-dessus devient alors une instruction SQL.

SELECT REPLACE(SUBSTRING(SUBSTRING_INDEX(‘a|bb|ccc|dd’, ‘|’, 3), LENGTH(SUBSTRING_INDEX(‘a|bb|ccc|dd’, ‘|’, 3–1)) + 1), ‘|’, ‘’) AS sclr_0 
FROM Products p0_

En résumé, écrire du DQL personnalisé nous permet de créer des fonctions SQL qui ne sont pas prises en charge par le DQL natif ou qui seraient normalement complexes et longues à implémenter.