Descubre cómo puedes obligar a doctrine a devolver los resultados de la consulta con un orden predefinido de una matriz en un entorno MySQL.

Cómo ordenar el resultado de una consulta de Doctrine 2 por un orden específico de una matriz usando MySQL en Symfony 5

Recientemente, trabajando con un proyecto que requería la implementación de un motor de búsqueda difuso basado solo en PHP , noté un comportamiento especial de una consulta que buscaba múltiples registros basados ​​en la clave principal (ID). Verifique el siguiente código:

// El repositorio de alguna entidad
$songsRepo = $this->getDoctrine()->getRepository(Songs::class);

// El id de los elementos que queremos encontrar en la base de datos
$ids = [5, 2, 3, 4, 1];

// Ejecute la consulta usando where IN para buscar por múltiples ID
$results = $songsRepo->createQueryBuilder("song")
    ->where('song.id IN (:ids)')
    ->setParameter('ids', $ids)
    ->getQuery()
    ->getResult();

// Esperamos obtener las filas con el orden especificado en la matriz
// Sin embargo, la base de datos devolverá los resultados en el siguiente orden:
// 1,2,3,4,5
// Algo que obviamente no queremos si damos una orden específica😡!
dump($results);

Como puede ver, aunque la matriz dada tiene el orden específico de buscar los siguientes elementos en la base de datos "5, 2, 3, 4, 1", el resultado los devolverá en el orden en que devuelve la base de datos.

Después de leer la documentación oficial, las preguntas, etc., algo me quedó claro rápidamente, parece que no puede obtener sus datos con un orden personalizado de su base de datos utilizando solo el ORM. En entornos que no son mysql o PostgreSQL, es posible que deba modificar su lógica y consultar cada fila de la base de datos para obtener el orden que desee. Afortunadamente para mí y tal vez para mucha gente, utilizo Doctrine 2 en Symfony para el proveedor específico de bases de datos MySQL, que ofrece una forma de resolver este problema a través de la función FIELD .

En este artículo, aprenderá cómo crear y registrar la función FIELD personalizada de MySQL para Doctrine 2 en Symfony 5.

1. Crear función de campo

Para empezar, crea la función de campo en el siguiente directorio de la aplicación: /app/src/DQL/Mysql. El archivo tendrá el nombre FieldFunction.phpy contendrá el siguiente código (esta función es originalmente de Beberlei / DoctrineExtensions ):

<?php

// /app/src/DQL/Mysql/FieldFunction.php
namespace App\DQL\Mysql;

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

/**
 * @author Jeremy Hicks <[email protected]>
 */
class FieldFunction extends FunctionNode
{
    private $field = null;

    private $values = [];

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

        // Do the field.
        $this->field = $parser->ArithmeticPrimary();

         // Agrega las cadenas a la matriz de valores. FIELD debe
         // se utilizará con al menos 1 cadena sin incluir el campo.

        $lexer = $parser->getLexer();

        while (count($this->values) < 1 ||
            $lexer->lookahead['type'] != Lexer::T_CLOSE_PARENTHESIS) {
            $parser->match(Lexer::T_COMMA);
            $this->values[] = $parser->ArithmeticPrimary();
        }

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

    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
    {
        $query = 'FIELD(';

        $query .= $this->field->dispatch($sqlWalker);

        $query .= ', ';

        for ($i = 0; $i < count($this->values); $i++) {
            if ($i > 0) {
                $query .= ', ';
            }

            $query .= $this->values[$i]->dispatch($sqlWalker);
        }

        $query .= ')';

        return $query;
    }
}

Al hacer esto, tendrá la nueva estructura siguiente en su proyecto:

SymfonyApp/
└── src/
    └── DQL/
        └── Mysql/
            └── FieldFunction.php

2. Registrar la función DQL

Ahora, deberá registrar la función creada anteriormente en el doctrine.yamlarchivo de configuración de esta manera:

# app/config/packages/doctrine.yaml
doctrine:
    # ...
    orm:
        # ...
        dql:
            string_functions:
                FIELD: App\DQL\Mysql\FieldFunction

Esto registrará la función en su proyecto, por lo que podrá usar la nueva función en sus consultas como describiremos en el siguiente ejemplo.

3. Ordenar los resultados por una serie de IDS

Finalmente, solo necesita aprender a usar la función CAMPO en sus consultas. Solo necesitas adjuntar una nueva cláusula a tu querybuilder, específicamente la orderBycláusula, especificando como valor de la parte DQL, la función FIELD que espera como primer argumento el nombre del campo por el que quieres ordenar los resultados, en este caso el id y como segundo parámetro la matriz de identificadores:

$results = $this->getDoctrine()->getRepository(Entity::class)
    ->createQueryBuilder("a")
    ->where('a.id IN (:ids)')
    // Agregue la nueva cláusula orderBy especificando el orden dado en la matriz ids
    ->add("orderBy", "FIELD(a.id, :ids)")
    // Configura las claves primarias del registro que desea buscar
    ->setParameter('ids', [5, 2, 3, 4, 1])
    ->getQuery()
    ->getResult();

El siguiente ejemplo muestra cómo podríamos ordenar los resultados con el orden específico de la matriz en nuestro ejemplo de canciones:

// El repositorio de alguna entidad
$songsRepo = $this->getDoctrine()->getRepository(Songs::class);

// El id de los elementos que queremos encontrar en la base de datos
$ids = [5, 2, 3, 4, 1];

// Ejecuta la consulta usando where IN para buscar por múltiples ID
// Ordenar los resultados por orden específico
$results = $songsRepo->createQueryBuilder("song")
    ->where('song.id IN (:ids)')
    // Agregue la nueva cláusula orderBy especificando el pedido
    ->add("orderBy", "FIELD(song.id, :ids)")
    ->setParameter('ids', $ids)
    ->getQuery()
    ->getResult();

// Ahora las filas se ordenarán con el orden especificado en la matriz:
// 5, 2, 3, 4, 1
dump($results);

Que te diviertas ❤️!


Interesado en la programación desde los 14 años, Carlos es un programador autodidacta, fundador y autor de la mayoría de los artículos de Our Code World.

Conviertete en un programador más sociable

Patrocinadores