Skip to content
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

Guidance Needed on Doctrine Custom Filters with Link Tables #1094

Open
DannyDanDan92 opened this issue Apr 17, 2020 · 0 comments
Open

Guidance Needed on Doctrine Custom Filters with Link Tables #1094

DannyDanDan92 opened this issue Apr 17, 2020 · 0 comments

Comments

@DannyDanDan92
Copy link

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:

  1. 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.

  1. 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 is regexp_email defined? The only place I can actually see this, is in the getDescription method 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.

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

No branches or pull requests

1 participant