30-05-2023
Introducció
Si fem servir un Query Bus1, possiblement tenim una interfície que defineix una Query:
<?php
declare(strict_types=1);
namespace rubenrubiob\Application;
interface Query
{
}
I una altra interfície que defineix el QueryBus:
<?php
declare(strict_types=1);
namespace rubenrubiob\Infrastructure\QueryBus;
use rubenrubiob\Application\Query;
use Throwable;
interface QueryBus
{
    public function __invoke(Query $query): mixed;
}
Veient un cas d’ús concret, tindríem per exemple aquesta Query, que retorna un llibre:
<?php
declare(strict_types=1);
namespace rubenrubiob\Application\Query\Llibre;
use rubenrubiob\Application\Query;
use rubenrubiob\Domain\DTO\Llibre\LlibreDTO;
final readonly class GetLlibreDTOByIdQuery implements Query
{
    public function __construct(
        public string $id,
    ){
    }
}
Aquesta Query es cridaria des del controlador de la següent manera:
<?php
declare(strict_types=1);
namespace rubenrubiob\Infrastructure\Ui\Http\Controller;
use rubenrubiob\Application\Query\Llibre\GetLlibreDTOByIdQuery;
use rubenrubiob\Domain\DTO\Llibre\LlibreDTO;
use rubenrubiob\Infrastructure\QueryBus\QueryBus;
final readonly class GetLlibreController
{
    public function __construct(private QueryBus $queryBus)
    {
    }
    public function __invoke(string $llibreId): LlibreDTO
    {
        /** @var LlibreDTO $llibre */
        $llibre = $this->queryBus->__invoke(
            new GetLlibreDTOByIdQuery(
                $llibreId,
            )
        );
        return $llibre;
    }
}
El problema que tenim és que no sabem quin tipus de dada retorna el QueryBus, perquè el tipus de retorn és mixed,
que pot ser qualsevol cosa: retorna un objecte? Un array? No retorna res?
L’única manera de saber-ho és forçant un type hint a la variable dins del controlador. Això, a part del problema que l’anotació quedi obsoleta en algun moment, també genera problemes amb els analitzadors de codi estàtic com Psalm o PHPStan.
Ara bé, precisament amb la introducció d’aquests analitzadors, una cosa similar als genèrics es van començar a poder fer servir a PHP. També PHPStorm dona suport als genèrics, i en va introduint suport gradualment.
En aquest post veurem com podem emprar genèrics al nostre QueryBus per a saber-ne el tipus de retorn.
Implementació
Query
El que farem és connectar la Query amb el valor de resposta del QueryHandler que la gestiona. Ho farem amb genèrics,
que a PHP es descriuen com a template.
A la interfície Query el que fem és afegir una anotació per indicar que és un template:
<?php
declare(strict_types=1);
namespace rubenrubiob\Application;
/** @template T */
interface Query
{
}
Un template o genèric el que indica és que tindrem un tipus variable, igual que tenim variables normal i corrents.
En aquest cas, tenim un tipus variable que anomenem T. El nom és arbitrari. Així, indicarem que totes les classes que
implementin la interfície Query, implementaran un tipus genèric T.
QueryBus
Ara podem tipar el nostre QueryBus així:
<?php
declare(strict_types=1);
namespace rubenrubiob\Infrastructure\QueryBus;
use rubenrubiob\Application\Query;
use Throwable;
interface QueryBus
{
    /**
     * @template T
     *
     * @param Query<T> $query
     *
     * @return T
     *
     * @throws Throwable
     */
    public function __invoke(Query $query): mixed;
}
Com a argument, rebem una Query que incorpora el tipus T. Recordem que T és un tipus variable, i que el nom és
arbitrari. El valor de retorn del QueryBus será aquest tipus variable que incorpora la Query, T.
Si ens fixem en els tipus, la descripció del QueryBus és:
f(Query<T>) => T
GetLlibreDTOByIdQuery
Si ara actualitzem la Query concreta GetLlibreDTOByIdQuery, ens queda així:
<?php
declare(strict_types=1);
namespace rubenrubiob\Application\Query\Llibre;
use rubenrubiob\Application\Query;
use rubenrubiob\Domain\DTO\Llibre\LlibreDTO;
/** @implements Query<LlibreDTO> */
final readonly class GetLlibreDTOByIdQuery implements Query
{
    public function __construct(
        public string $id,
    ){
    }
}
Tornant a mirar la descripció dels tipus, si substituïm el tipus T pel nostre cas concret, LlibreDTO, tenim la
següent descripció:
f(Query<T>) => T
f(Query<LlibreDTO>) => LlibreDTO
Veiem que PHPStorm també detecta correctament el tipus de dada:

Conclusions
Emprant genèrics aconseguim tipar el nostre QueryBus: les implementacions de Query indiquen el tipus de resposta, i
aquest és l’enllaç necessari que necessitem.
Resum
- Hem vist el problema de tipat que suposa no tipar un 
QueryBus. - Hem explicat molt resumidament què són els genèrics.
 - Hem implementat una descripció de tipus que permet tipar correctament el nostre 
QueryBus. 
- 
      
Cal plantejar-nos si realment necessitem un
QueryBus. Per què ens cal? UnCommandBusaporta tots els middleware que poden, per exemple, obrir i tancar una transacció a la base de dades. Però, quin middleware fem servir a unQueryBus? Potser podem substituir elQueryBuspelQueryHandlerque crida; o, si som encara més radicals, potser ni tan sols necessitem elsQueryiQueryHandler, i podem cridar directament al servei de domini que ens retorna la dada que necessitem. Per a veure’n un debat més extens, vegeu el següen article i els comentaris. ↩