Skip to content

QueryParameter with :property ignores properties argument if multiple filters #7592

@tacman

Description

@tacman

API Platform version(s) affected: 4.2.x

Description
When using ApiPlatform\Metadata\QueryParameter with a key containing :property and a Doctrine ORM filter (e.g. PartialSearchFilter, ExactFilter, OrderFilter), the properties argument is ignored, repeated or collapsed.

This contradicts the 4.2 documentation, which shows properties being honored for the :property placeholder on an operation:

How to reproduce

git clone [email protected]:tacman/api-parameter-issue.git && cd api-parameter-issue && composer install
symfony server:start -d
symfony open:local --path=/api?ui=re_doc#tag/Product/operation/api_products_get_collection
Image

The repo is simply doing the following:

symfony  new api-property-bug --webapp && cd api-property-bug 
composer req api && bin/console make:entity Product

Then replace Product.php with

<?php

namespace App\Entity;

use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use App\Repository\ProductRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;


#[ORM\Entity(repositoryClass: ProductRepository::class)]
#[ApiResource(
    operations: [
        new Get(
            normalizationContext: [
                'groups' => ['product.read', 'product.details'],
            ]
        ),
        new GetCollection(
            normalizationContext: [
                'groups' => ['product.read'],
            ],
            parameters: [
                // ?category=foo
                'brand' => new QueryParameter(
                    filter: new ExactFilter() // instance, ignored if not registered
                ),
                'search[:property]' => new QueryParameter(
                    filter: new PartialSearchFilter(),
                    properties: ['title', 'description']
                ),
                'filter[:property]' => new QueryParameter(
                    filter: new ExactFilter(),
                    properties: ['category','brand'], // self::FILTER_PROPS
                ),
                'order[:property]' => new QueryParameter(
                    filter: new OrderFilter(),
                    properties: ['rating']
                ),
            ]
        )],
    normalizationContext: ['groups' => ['product.read', 'product.details']],
)]


class Product
{

    public function __construct(
        #[ORM\Column(type: 'string', length: 255)]
        #[ORM\Id]
        #[Groups(['product.read'])]
        public ?string $sku,

        #[ORM\Column(type: 'string', length: 255)]
        #[ORM\Id]
        #[Groups(['product.read'])]
        public ?string $title,

        #[ORM\Column(type:Types::TEXT, nullable: true)]
        #[ORM\Id]
        #[Groups(['product.read'])]
        public ?string $description,


    )
    {
    }


    // virtual property
    #[Groups(['product.read'])]
    #[ORM\Column(nullable: true)]
    #[ApiProperty("category from extra, virtual but needs index")]
    public ?string $category;

    #[Groups(['product.read'])]
    #[ORM\Column(type: Types::STRING, nullable: true)]
    #[ApiProperty("the registered brand name")]
    public ?string $brand;

    #[ORM\Column(type: Types::SMALLFLOAT, nullable: true)]
    #[ApiProperty("exact price, float, for doctrine filters")]
    public ?float $exactPrice;

    #[Groups(['product.read'])]
    #[ApiProperty("rounded rating, for range slider")]
    #[ORM\Column(type: Types::INTEGER)]
    public int $rating;

    #[Groups(['product.read'])]
    #[ORM\Column(type: Types::INTEGER)]
    public int $stock;

    #[Groups(['product.read'])]
    #[ORM\Column(type: Types::JSON, nullable: true, options: ['jsonb' => true])]
    #[ApiProperty("array of tags")]
    public array $tags;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions