Buy Access to Course
09.

Seleccionar en un nuevo objeto DTO

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Tener la flexibilidad de seleccionar los datos que queramos es genial. Tratar con la matriz asociativa que obtenemos de vuelta es... ¡menos alucinante! Me gusta trabajar con objetos siempre que sea posible. Afortunadamente, Doctrine nos ofrece una forma sencilla de mejorar esta situación: consultamos los datos que queremos... pero le decimos que nos dé un objeto.

Crear el DTO

En primer lugar, necesitamos crear una nueva clase que contenga los datos de nuestra consulta. Crearé un nuevo directorio llamado src/Model/... pero podría llamarse como quieras. Llama a la clase... ¿qué tal CategoryFortuneStats.

El único propósito de esta clase es contener los datos de esta consulta específica. Así que añade un public function __construct() con unas cuantas propiedades public para simplificar:public int $fortunesPrinted, public float $fortunesAverage, ypublic string $categoryName.

15 lines | src/Model/CategoryFortuneStats.php
// ... lines 1 - 4
class CategoryFortuneStats
{
public function __construct(
public int $fortunesPrinted,
public float $fortunesAverage,
public string $categoryName,
)
{
}
}

¡Estupendo!

De vuelta al repositorio, en realidad no necesitamos ninguna magia de Doctrine para utilizar esta nueva clase. Podríamos consultar la matriz asociativa, devolver new CategoryFortuneStats()y pasarle cada clave.

Es una gran opción, muy sencilla y además este método del repositorio devolvería un objeto en lugar de un array. Pero... Doctrine lo hace aún más fácil gracias a una función poco conocida.

Añade un nuevo ->select() que contendrá todas estas selecciones en una. Añade también un sprintf(): verás por qué en un minuto. Dentro, ¡mira esto! DiNEW %s() y luego pasa CategoryFortuneStats::class por ese marcador de posición. Básicamente, estamos diciendo NEW App\Model\CategoryFortuneStats()... Sólo quería evitar escribir ese nombre de clase tan largo.

Dentro de NEW, coge cada una de las 3 cosas que queremos seleccionar y pégalas, como si las pasáramos directamente como primer, segundo y tercer argumento al constructor de nuestra nueva clase.

90 lines | src/Repository/FortuneCookieRepository.php
// ... lines 1 - 18
class FortuneCookieRepository extends ServiceEntityRepository
{
// ... lines 21 - 25
public function countNumberPrintedForCategory(Category $category): array
{
$result = $this->createQueryBuilder('fortuneCookie')
->select(sprintf(
'NEW %s(
SUM(fortuneCookie.numberPrinted) AS fortunesPrinted,
AVG(fortuneCookie.numberPrinted) fortunesAverage,
category.name
)',
CategoryFortuneStats::class
))
// ... lines 37 - 44
}
// ... lines 46 - 88
}

¿No es genial? ¡Vamos a dd($result) para ver cómo queda!

Sin aliasing con NEW

Si nos dirigimos y actualizamos... oh... me aparece un error: T_CLOSE_PARENTHESIS, got 'AS'. Cuando seleccionamos datos en un objeto, el aliasing ya no es necesario... ni está permitido. Y tiene sentido: Doctrine pasará lo que sea esto al primer argumento de nuestro constructor, esto al segundo argumento y esto al tercero. Como los alias ya no tienen sentido... elimínalos.

90 lines | src/Repository/FortuneCookieRepository.php
// ... lines 1 - 25
public function countNumberPrintedForCategory(Category $category): array
{
$result = $this->createQueryBuilder('fortuneCookie')
->select(sprintf(
'NEW %s(
SUM(fortuneCookie.numberPrinted),
AVG(fortuneCookie.numberPrinted),
category.name
)',
CategoryFortuneStats::class
))
// ... lines 37 - 44
}
// ... lines 46 - 90

Si lo comprobamos ahora... ¡lo tengo! ¡Me encanta! ¡Tenemos un objeto con nuestros datos dentro!

Vamos a celebrarlo limpiando nuestro método. En lugar de un array, vamos a devolver un CategoryFortuneStats. Elimina también el dd($result) de aquí abajo.

89 lines | src/Repository/FortuneCookieRepository.php
// ... lines 1 - 25
public function countNumberPrintedForCategory(Category $category): CategoryFortuneStats
{
// ... lines 28 - 43
}
// ... lines 45 - 89

De vuelta en el controlador, para mostrar lo bonito que es esto, cambia $result por... qué tal $stats. Entonces podemos utilizar $stats->fortunesPrinted, $stats->fortunesAverage, y $stats->categoryName.

47 lines | src/Controller/FortuneController.php
// ... lines 1 - 12
class FortuneController extends AbstractController
{
// ... lines 15 - 30
public function showCategory(int $id, CategoryRepository $categoryRepository, FortuneCookieRepository $fortuneCookieRepository): Response
{
// ... lines 33 - 36
$stats = $fortuneCookieRepository->countNumberPrintedForCategory($category);
// ... line 38
return $this->render('fortune/showCategory.html.twig',[
'category' => $category,
'fortunesPrinted' => $stats->fortunesPrinted,
'fortunesAverage' => $stats->fortunesAverage,
'categoryName' => $stats->categoryName,
]);
}
}

Ahora que lo hemos arreglado un poco, comprobemos si sigue funcionando. Y... funciona.

Siguiente: A veces las consultas son tan complejas... que la mejor opción es escribirlas en SQL nativo. Hablemos de cómo hacerlo.