Skip to content

doc(doctrine): document how to compute and sort a virtual field #7113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 29, 2025

Conversation

soyuka
Copy link
Member

@soyuka soyuka commented Apr 28, 2025

Q A
Branch? 4.1
Tickets N/a
License MIT

This PR introduces the ability to use and filter on computed fields within API Platform, specifically for Doctrine entities.

Here's a configuration example with a filter and static functions :

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Tests\Fixtures\TestBundle\Filter\SortComputedFieldFilter;
use Doctrine\ORM\QueryBuilder;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;

#[GetCollection(
    stateOptions: new Options(handleLinks: [self::class, 'handleLinks']),
    processor: [self::class, 'process'],
    write: true,
    parameters: [
        'sort[:property]' => new QueryParameter(
            filter: new SortComputedFieldFilter(),
            properties: ['totalQuantity']
        ),
    ]
)]
class Cart
{
    public static function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
    {
        foreach ($data as &$value) {
            $cart = $value[0];
            $cart->totalQuantity = $value['totalQuantity'] ?? 0;
            $value = $cart;
        }

        return $data;
    }

    public static function handleLinks(QueryBuilder $queryBuilder, array $uriVariables, QueryNameGeneratorInterface $queryNameGenerator, array $context): void
    {
        $rootAlias = $queryBuilder->getRootAliases()[0] ?? 'o';
        $itemsAlias = $queryNameGenerator->generateParameterName('items');
        $queryBuilder->leftJoin(\sprintf('%s.items', $rootAlias), $itemsAlias)
            ->addSelect(\sprintf('COALESCE(SUM(%s.quantity), 0) AS totalQuantity', $itemsAlias))
            ->addGroupBy(\sprintf('%s.id', $rootAlias));
    }
}
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;

class SortComputedFieldFilter implements FilterInterface
{
    public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
    {
        if ($context['parameter']->getValue() instanceof ParameterNotFound) {
            return;
        }

        $queryBuilder->addOrderBy('totalQuantity', $context['parameter']->getValue()['totalQuantity'] ?? 'ASC');
    }
}

A new functional test (ComputedFieldTest.php) demonstrating the usage which will be transformed into a guide.

@soyuka soyuka changed the title docs: document how to compute and sort a virtual field doc(doctirne): document how to compute and sort a virtual field Apr 28, 2025
@dunglas dunglas changed the title doc(doctirne): document how to compute and sort a virtual field doc(doctrine): document how to compute and sort a virtual field Apr 28, 2025
@dunglas
Copy link
Member

dunglas commented Apr 28, 2025

Clever! We should really rename "handleLinks" if we use it like that, it's not related at all to links now :)

@soyuka
Copy link
Member Author

soyuka commented Apr 29, 2025

Clever! We should really rename "handleLinks" if we use it like that, it's not related at all to links now :)

Maybe that we should add something like repositoryMethod to the stateOptions to hook at the call:

$queryBuilder = $repository->createQueryBuilder('o');

It'd be even better and one could use the same query builder but still handle links on subresources.

@soyuka soyuka merged commit 95242f6 into api-platform:4.1 Apr 29, 2025
92 of 96 checks passed
@soyuka soyuka deleted the virtual-field branch April 29, 2025 08:31
@g-ra
Copy link

g-ra commented May 6, 2025

I suspect that the current solution has a drawback, how can we substitute the data of the current authorized user into this request?

for example, if we need to look at the total of only a multiple of the current authorized user

I think this problem will be solved if we switch to repository methods and not methods for getting in the entity itself

@soyuka
Copy link
Member Author

soyuka commented May 6, 2025

so #7115 would be more appropriate? Can you share an example?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants