docs
docs copied to clipboard
Guidance Needed on Doctrine Custom Filters with Link Tables
Objective - To create a Custom Filter, so I can filter on a value in relationship tables (several layers deep) with WHERE clauses.
Background I currently have several entities setup:
- Attribute
- Product
- ProductAttribute
- Category
- CategoryProductLink
The CategoryProductLink has many-to-one relationships with both Category and Product. I've added some standard filters to the CategoryProductLink (Category (exact) and Product (Exact)) along with some other filters which are based on the properties within Product (ie. product.sku (partial)).
// src/Bundle/CatalogueBundle/Resources/config/api_resources.yaml
Peracto\Component\Category\Model\CategoryProductLinkInterface:
collectionOperations:
get:
filters:
- peracto.catalogue_bundle.filters.category_product_link_search
// src/Bundle/CatalogueBundle/Resources/config/filters.yaml
peracto.catalogue_bundle.filters.category_product_link_search:
parent: api_platform.doctrine.orm.search_filter
arguments:
- product: exact
product.sku: partial
product.attributes.value: partial
category: exact
tags:
- api_platform.filter
Problem
We store the Product Name in an alternative entity (ProductAttribute) table. This has relationships with the Product entity and an Attribute entity. I want to add a filter to CategoryProductLink, so that I can filter by Product Name. Product Name is an Attribute (Attribute entity) and the value for that Attribute is stored in the ProductAttribute table. I've achieved this, as you can see above, by adding product.attributes.value: partial as a filter argument.
The problem with this, is that this will allow you to filter the value on every Attribute associated with the Product. I only want to filter the Attribute value, where the 'code' of the Attribute is 'product_name'. Therefore, I looked at the Custom Filters Documentation (https://api-platform.com/docs/core/filters/#creating-custom-filters).
There are several things I do not understand and feel the Documentation is lacking somewhat. I was wondering if somebody could help guide me with some additional information:
- After setting up the ProductNameFilter, I cannot seem to get past the initial conditional block (see below) in my integration tests.
if (
!$this->isPropertyEnabled($property, $resourceClass) ||
!$this->isPropertyMapped($property, $resourceClass)
) {
return;
}
Its worth mentioning by this point, I had added product.name: partial to the original list of filter arguments (replacing product.attributes.value: partial), so it looked like this:
peracto.catalogue_bundle.filters.category_product_link_search:
parent: api_platform.doctrine.orm.search_filter
arguments:
- product: exact
product.sku: partial
product.name: partial
category: exact
tags:
- api_platform.filter
After tracing through the vendor directory, it seems that it is failing here: vendor/api-platform/core/src/Bridge/Doctrine/Common/PropertyHelperTrait.php:44.
// vendor/api-platform/core/src/Bridge/Doctrine/Common/PropertyHelperTrait.php
protected function isPropertyMapped(string $property, string $resourceClass, bool $allowAssociation = false): bool
{
if ($this->isPropertyNested($property, $resourceClass)) {
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
$property = $propertyParts['field'];
} else {
$metadata = $this->getClassMetadata($resourceClass);
}
return $metadata->hasField($property) || ($allowAssociation && $metadata->hasAssociation($property));
}
The $metadata->hasField($property) is returning false, which again once traced seems to be an issue here: vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php:1856.
// vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
public function hasField($fieldName)
{
return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
}
It seems that the fieldName it is looking for is product. embeddedClasses returns a blank array and fieldMappings only contains id. Presumably this is because the id is a field on my table, whereas Product and Category are many-to-one relationships. Which makes me question, do Custom Filters support relationships through link tables, such as the one I have described? It doesn't seem like it does to me.
- The Documentation doesn't seem clear in explaining where the name of the filter is defined. It mentions that whilst using the example code given, you can make a request by the following:
http://example.com/offers?regexp_email=^[FOO]. However where isregexp_emaildefined? The only place I can actually see this, is in thegetDescriptionmethod which purpose seems to only be there to document swagger. Can someone elaborate on where these properties are defined for the example filter?
My filter is setup like the following:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\Component\Category\Filter\ProductNameFilter: ~
// src/Bundle/CatalogueBundle/Resources/config/api_resources.yaml
Peracto\Component\Category\Model\CategoryProductLinkInterface:
collectionOperations:
get:
filters:
- peracto.catalogue_bundle.filters.category_product_link_search
- App\Component\Category\Filter\ProductNameFilter
// src/Bundle/CatalogueBundle/Resources/config/filters.yaml
peracto.catalogue_bundle.filters.category_product_link_search:
parent: api_platform.doctrine.orm.search_filter
arguments:
- product: exact
product.sku: partial
product.name: partial
category: exact
tags:
- api_platform.filter
As you can see above, I've added product.name: partial to the "non-custom" filter as the documentation doesn't state where you define this property and the $this->isPropertyEnabled($property, $resourceClass) check in the example custom filter would always fail for me if I did not. I don't believe this is correct, but again the documentation doesn't specify where you should list this.