Skip to content

[Filters] apiResource() static method and PHP resource files don't apply filters correctly #7655

@ttskch

Description

@ttskch

API Platform version(s) affected: 4.2.11

For example:

#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;

    #[ORM\Column(type: Types::DATE_MUTABLE)]
    private ?\DateTime $date = null;

    #[ORM\Column(nullable: true)]
    private ?bool $published = null;

    /**
     * @var Collection<int, Comment>
     */
    #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'article', orphanRemoval: true)]
    private Collection $comments;

    public function __construct()
    {
        $this->comments = new ArrayCollection();
    }

    public static function apiResource(): array
    {
        return [
            new GetCollection(
                parameters: [
                    new QueryParameter(
                        key: ':property',
                        filter: new PartialSearchFilter(),
                        properties: ['title', 'comments.content'],
                    ),
                    new QueryParameter(
                        key: 'query',
                        filter: new FreeTextQueryFilter(new PartialSearchFilter()),
                        properties: ['title', 'comments.content'],
                    ),
                    new QueryParameter(
                        key: 'date',
                        filter: new DateFilter(),
                    ),
                    new QueryParameter(
                        key: 'published',
                        filter: new BooleanFilter(),
                    ),
                    new QueryParameter(
                        key: 'numeric[id]',
                        filter: new NumericFilter(),
                        property: 'id',
                    ),
                    new QueryParameter(
                        key: 'id',
                        filter: new RangeFilter(),
                    ),
                    new QueryParameter(
                        key: 'exists[:property]',
                        filter: new ExistsFilter(),
                        properties: ['content', 'comments'],
                    ),
                    new QueryParameter(
                        key: 'order[:property]',
                        filter: new OrderFilter(),
                        properties: ['id', 'date'],
                    ),
                ],
            ),
        ];
    }

    // ...
}

This code has the following problems:

Coding

  • QueryParmeterkey を連想配列のキーによって指定することができず、key 引数で指定しなければならない(さもないと、A Parameter should have a key. が発生する)

Actual behavior

  • Cannot search by ?title
  • Cannot search by ?comments.content
  • Searching by ?query results in [Semantical Error] line 0, col 92 near 'content) LIKE': Error: Class App\\Entity\\Article has no field or association named comments.content
  • Cannot search by ?exists[content]
  • Cannot search by ?exists[comments]
  • Searching by ?order[date] or ?order[id] results in Warning: Undefined array key "order"

OpenAPI Doc

Details

  • title and comments.content are not output, only :property is output.
  • date[after], date[before], date[strictly_after], date[strictly_before] are not output, only date is output.
  • id[gt], id[lt], id[gte], id[lte], id[between] are not output, only id is output.
  • exists[content] and exists[comments] are not output, only exists is output.
  • order[id] and order[date] are not output, only order is output.

Hydra search.template field

{
  "search": {
    "template": "/api/articles{?numeric[id]}"
  }
}
  • Only numeric[id] is output, nothing else.

Even with a few small tweaks like this...

diff --git a/src/Entity/Article.php b/src/Entity/Article.php
index 95251e4..7d9bc84 100644
--- a/src/Entity/Article.php
+++ b/src/Entity/Article.php
@@ -55,9 +55,14 @@ class Article
             new GetCollection(
                 parameters: [
                     new QueryParameter(
-                        key: ':property',
+                        key: 'title',
                         filter: new PartialSearchFilter(),
-                        properties: ['title', 'comments.content'],
+                        property: 'title',
+                    ),
+                    new QueryParameter(
+                        key: 'comments.content',
+                        filter: new PartialSearchFilter(),
+                        property: 'comments.content',
                     ),
                     new QueryParameter(
                         key: 'query',
@@ -82,14 +87,24 @@ class Article
                         filter: new RangeFilter(),
                     ),
                     new QueryParameter(
-                        key: 'exists[:property]',
+                        key: 'exists[content]',
                         filter: new ExistsFilter(),
-                        properties: ['content', 'comments'],
+                        property: 'content',
+                    ),
+                    new QueryParameter(
+                        key: 'exists[comments]',
+                        filter: new ExistsFilter(),
+                        property: 'comments',
+                    ),
+                    new QueryParameter(
+                        key: 'order[id]',
+                        filter: new OrderFilter(),
+                        property: 'id',
                     ),
                     new QueryParameter(
-                        key: 'order[:property]',
+                        key: 'order[date]',
                         filter: new OrderFilter(),
-                        properties: ['id', 'date'],
+                        property: 'date',
                     ),
                 ],
             ),
Details
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;

    #[ORM\Column(type: Types::DATE_MUTABLE)]
    private ?\DateTime $date = null;

    #[ORM\Column(nullable: true)]
    private ?bool $published = null;

    /**
     * @var Collection<int, Comment>
     */
    #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'article', orphanRemoval: true)]
    private Collection $comments;

    public function __construct()
    {
        $this->comments = new ArrayCollection();
    }

    public static function apiResource(): array
    {
        return [
            new GetCollection(
                parameters: [
                    new QueryParameter(
                        key: 'title',
                        filter: new PartialSearchFilter(),
                    ),
                    new QueryParameter(
                        key: 'comments.content',
                        filter: new PartialSearchFilter(),
                    ),
                    new QueryParameter(
                        key: 'query',
                        filter: new FreeTextQueryFilter(new PartialSearchFilter()),
                        properties: ['title', 'comments.content'],
                    ),
                    new QueryParameter(
                        key: 'date',
                        filter: new DateFilter(),
                    ),
                    new QueryParameter(
                        key: 'published',
                        filter: new BooleanFilter(),
                    ),
                    new QueryParameter(
                        key: 'numeric[id]',
                        filter: new NumericFilter(),
                        property: 'id',
                    ),
                    new QueryParameter(
                        key: 'id',
                        filter: new RangeFilter(),
                    ),
                    new QueryParameter(
                        key: 'exists[content]',
                        filter: new ExistsFilter(),
                        property: 'content',
                    ),
                    new QueryParameter(
                        key: 'exists[comments]',
                        filter: new ExistsFilter(),
                        property: 'comments',
                    ),
                    new QueryParameter(
                        key: 'order[id]',
                        filter: new OrderFilter(),
                        property: 'id',
                    ),
                    new QueryParameter(
                        key: 'order[date]',
                        filter: new OrderFilter(),
                        property: 'date',
                    ),
                ],
            ),
        ];
    }

    // ...
}

This code has the following problems:

Coding

  • Specifying property: 'title' explicitly is required, otherwise ApiPlatform\\Doctrine\\Orm\\Util\\QueryNameGenerator::generateParameterName(): Argument #1 ($name) must be of type string, null given, called in /path/to/project/vendor/api-platform/doctrine-orm/Filter/PartialSearchFilter.php on line 37 will occur, even though the key is title.
  • Specifying property: 'comments.content' explicitly is required, otherwise ApiPlatform\\Doctrine\\Orm\\Util\\QueryNameGenerator::generateParameterName(): Argument #1 ($name) must be of type string, null given, called in /path/to/project/vendor/api-platform/doctrine-orm/Filter/PartialSearchFilter.php on line 37 will occur, even though the key is comments.content.

Actual behavior

  • Searching by ?comments.content results in [Semantical Error] line 0, col 58 near 'content) LIKE': Error: Class App\\Entity\\Article has no field or association named comments.content
  • Searching by ?query results in [Semantical Error] line 0, col 92 near 'content) LIKE': Error: Class App\\Entity\\Article has no field or association named comments.content
  • Cannot search by ?exists[content]

OpenAPI Doc

Details

  • title is not output, only title[] is output. (But this is acceptable as a specification.)
  • comments.content is not output, only comments.content[] is output. (But this is acceptable as a specification.)
  • date[after], date[before], date[strictly_after], date[strictly_before] are not output, only date is output.
  • id[gt], id[lt], id[gte], id[lte], id[between] are not output, only id is output.

Hydra search.template field

{
  "search": {
    "template": "/api/articles{?title,comments.content,numeric[id],exists[content],exists[comments],order[id],order[date]}"
  }
}
  • date[after], date[before], date[strictly_after], date[strictly_before] are not output, and even date is not output.
  • id[gt], id[lt], id[gte], id[lte], id[between] are not output, and even id is not output.

Using PHP resource file causes the exact same issue

Details
diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml
index 02f295a..3959020 100644
--- a/config/packages/api_platform.yaml
+++ b/config/packages/api_platform.yaml
@@ -5,3 +5,5 @@ api_platform:
         stateless: true
         cache_headers:
             vary: ['Content-Type', 'Authorization', 'Origin']
+    mapping:
+        imports: ['%kernel.project_dir%/config/api_platform/resources']
diff --git a/src/Entity/Article.php b/src/Entity/Article.php
index 7d9bc84..5b0d20c 100644
--- a/src/Entity/Article.php
+++ b/src/Entity/Article.php
@@ -2,16 +2,6 @@
 
 namespace App\Entity;
 
-use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
-use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
-use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
-use ApiPlatform\Doctrine\Orm\Filter\FreeTextQueryFilter;
-use ApiPlatform\Doctrine\Orm\Filter\NumericFilter;
-use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
-use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
-use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
-use ApiPlatform\Metadata\GetCollection;
-use ApiPlatform\Metadata\QueryParameter;
 use App\Repository\ArticleRepository;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
@@ -49,68 +39,6 @@ class Article
         $this->comments = new ArrayCollection();
     }
 
-    public static function apiResource(): array
-    {
-        return [
-            new GetCollection(
-                parameters: [
-                    new QueryParameter(
-                        key: 'title',
-                        filter: new PartialSearchFilter(),
-                        property: 'title',
-                    ),
-                    new QueryParameter(
-                        key: 'comments.content',
-                        filter: new PartialSearchFilter(),
-                        property: 'comments.content',
-                    ),
-                    new QueryParameter(
-                        key: 'query',
-                        filter: new FreeTextQueryFilter(new PartialSearchFilter()),
-                        properties: ['title', 'comments.content'],
-                    ),
-                    new QueryParameter(
-                        key: 'date',
-                        filter: new DateFilter(),
-                    ),
-                    new QueryParameter(
-                        key: 'published',
-                        filter: new BooleanFilter(),
-                    ),
-                    new QueryParameter(
-                        key: 'numeric[id]',
-                        filter: new NumericFilter(),
-                        property: 'id',
-                    ),
-                    new QueryParameter(
-                        key: 'id',
-                        filter: new RangeFilter(),
-                    ),
-                    new QueryParameter(
-                        key: 'exists[content]',
-                        filter: new ExistsFilter(),
-                        property: 'content',
-                    ),
-                    new QueryParameter(
-                        key: 'exists[comments]',
-                        filter: new ExistsFilter(),
-                        property: 'comments',
-                    ),
-                    new QueryParameter(
-                        key: 'order[id]',
-                        filter: new OrderFilter(),
-                        property: 'id',
-                    ),
-                    new QueryParameter(
-                        key: 'order[date]',
-                        filter: new OrderFilter(),
-                        property: 'date',
-                    ),
-                ],
-            ),
-        ];
-    }
-
     public function getId(): ?int
     {
         return $this->id;

Reproducer

https://github.com/ttskch/api-platform-core-7655

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