Quantcast
Channel: Active questions tagged symfony4 - Stack Overflow
Viewing all articles
Browse latest Browse all 3925

API platform filters with uuid

$
0
0

I recently set up a project where I use api-platform in correlation with ramsey/uuid-doctrine in accordance with https://api-platform.com/docs/core/identifiers/.

Al the basic CRUD stuff works but I'm getting unexpected behavior on ApiFilter.

The basic setup is this, I have platform objects that in turn contain organisations both relaying on an astract class.

Abstract

class AbstaractEntity {       /**     * @var \Ramsey\Uuid\UuidInterface     *     * @ORM\Id     * @ORM\Column(type="uuid_binary_ordered_time", unique=true)     * @ORM\GeneratedValue(strategy="CUSTOM")     * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidOrderedTimeGenerator")     *      * @Groups({"read", "write"})     * @ApiProperty(iri="https://schema.org/identifier")     */    protected $id; * The Platform to which this object belongs. *  * @ORM\ManyToOne(targetEntity="App\Conduction\PlatformBundle\Entity\Platform") * @ORM\JoinColumn(name="platform_id", referencedColumnName="id") * @Groups({"read", "write"}) * @MaxDepth(1) * @ApiProperty() */protected $platform;...}

Organisation

/** * Organisation entity * * @ORM\Table( *     "organisation__organisation" * ) * @ORM\Entity * @ORM\HasLifecycleCallbacks * @ORM\EntityListeners({"App\Conduction\OrganisationBundle\EventListener\OrganisationListener","App\Conduction\AppBundle\EventListener\EntityListener"}) * @ApiResource(attributes={ *     "normalization_context"={"groups"={"read"}, "enable_max_depth"="true"}, *     "denormalization_context"={"groups"={"write"}} * }) * @ApiFilter(SearchFilter::class, properties={"id": "exact", "description": "partial", "name": "partial","organisation": "exact","platform": "exact"}) * @ApiFilter(DateFilter::class, properties={"dateCreated","datePublished","dateModified","dateDeleted"}) * @ApiFilter(OrderFilter::class, properties={"id", "name","dateCreated","datePublished","dateModified","dateDeleted"}, arguments={"orderParameterName"="order"}) * @Gedmo\SoftDeleteable( *     fieldName = "dateDeleted", *     timeAware = true * ) */class Organisation extends \App\Conduction\AppBundle\Entity\AbstaractEntity{....}

So all the basics are in order and a api/organisations gives met the following

{"@context": "/api/contexts/Organisation","@id": "/api/organisations","@type": "hydra:Collection","hydra:member": [        {"@id": "/api/organisations/ba8dc018-d8d5-11e8-bcb6-5254007d3b24","@type": "Organisation","slug": null,"organisation": "/api/organisations/ba8dc018-d8d5-11e8-bcb6-5254007d3b24","children": ["/api/organisations/ba8dc018-d8d5-11e8-bcb6-5254007d3b24"            ],"id": "ba8dc018-d8d5-11e8-bcb6-5254007d3b24","name": "Conduction","description": "This is the defeault platform","platform": {"@id": "/api/platforms/ba8d9958-d8d5-11e8-a9a5-5254007d3b24","@type": "Platform","approveUsers": true,"validateUsers": true,"organisation": "/api/organisations/ba8dc018-d8d5-11e8-bcb6-5254007d3b24","id": "ba8d9958-d8d5-11e8-a9a5-5254007d3b24","name": "Conduction","description": "This is the defeault platform","platform": "/api/platforms/ba8d9958-d8d5-11e8-a9a5-5254007d3b24","dateCreated": "2018-10-26T06:15:09+02:00","datePublished": "2018-10-26T06:15:09+02:00","dateModified": "2018-10-26T06:15:10+02:00"            },"dateCreated": "2018-10-26T06:15:09+02:00","datePublished": "2018-10-26T06:15:09+02:00","dateModified": "2018-10-26T06:15:10+02:00"        },        {"@id": "/api/organisations/5f87e080-d8d6-11e8-9809-5254007d3b24","@type": "Organisation","slug": null,"organisation": "/api/organisations/5f87e080-d8d6-11e8-9809-5254007d3b24","children": ["/api/organisations/5f87e080-d8d6-11e8-9809-5254007d3b24"            ],"id": "5f87e080-d8d6-11e8-9809-5254007d3b24","name": "test 1","description": "<p>test 1&nbsp;</p>","platform": null,"dateCreated": "2018-10-26T06:19:47+02:00","datePublished": "2018-10-26T06:19:47+02:00","dateModified": "2018-10-26T06:19:47+02:00"        },        {"@id": "/api/organisations/f2079af4-d8d6-11e8-ba9a-5254007d3b24","@type": "Organisation","slug": null,"organisation": "/api/organisations/f2079af4-d8d6-11e8-ba9a-5254007d3b24","children": ["/api/organisations/f2079af4-d8d6-11e8-ba9a-5254007d3b24"            ],"id": "f2079af4-d8d6-11e8-ba9a-5254007d3b24","name": "test 1","description": "<p>test 1</p>","platform": {"@id": "/api/platforms/f20723bc-d8d6-11e8-8ffb-5254007d3b24","@type": "Platform","approveUsers": true,"validateUsers": true,"organisation": "/api/organisations/f2079af4-d8d6-11e8-ba9a-5254007d3b24","id": "f20723bc-d8d6-11e8-8ffb-5254007d3b24","name": "test 1","description": "<p>test 1</p>","platform": "/api/platforms/f20723bc-d8d6-11e8-8ffb-5254007d3b24","dateCreated": "2018-10-26T06:23:52+02:00","datePublished": "2018-10-26T06:23:52+02:00","dateModified": "2018-10-26T06:23:52+02:00"            },"dateCreated": "2018-10-26T06:23:52+02:00","datePublished": "2018-10-26T06:23:52+02:00","dateModified": "2018-10-26T06:23:52+02:00"        }    ],"hydra:totalItems": 3,"hydra:search": {"@type": "hydra:IriTemplate","hydra:template": "/api/organisations{?id,id[],description,name,organisation,organisation[],platform,platform[],dateCreated[before],dateCreated[strictly_before],dateCreated[after],dateCreated[strictly_after],datePublished[before],datePublished[strictly_before],datePublished[after],datePublished[strictly_after],dateModified[before],dateModified[strictly_before],dateModified[after],dateModified[strictly_after],dateDeleted[before],dateDeleted[strictly_before],dateDeleted[after],dateDeleted[strictly_after],order[id],order[name],order[dateCreated],order[datePublished],order[dateModified],order[dateDeleted]}","hydra:variableRepresentation": "BasicRepresentation","hydra:mapping": [            {"@type": "IriTemplateMapping","variable": "id","property": "id","required": false            },            {"@type": "IriTemplateMapping","variable": "id[]","property": "id","required": false            },            {"@type": "IriTemplateMapping","variable": "description","property": "description","required": false            },            {"@type": "IriTemplateMapping","variable": "name","property": "name","required": false            },            {"@type": "IriTemplateMapping","variable": "organisation","property": "organisation","required": false            },            {"@type": "IriTemplateMapping","variable": "organisation[]","property": "organisation","required": false            },            {"@type": "IriTemplateMapping","variable": "platform","property": "platform","required": false            },            {"@type": "IriTemplateMapping","variable": "platform[]","property": "platform","required": false            },            {"@type": "IriTemplateMapping","variable": "dateCreated[before]","property": "dateCreated","required": false            },            {"@type": "IriTemplateMapping","variable": "dateCreated[strictly_before]","property": "dateCreated","required": false            },            {"@type": "IriTemplateMapping","variable": "dateCreated[after]","property": "dateCreated","required": false            },            {"@type": "IriTemplateMapping","variable": "dateCreated[strictly_after]","property": "dateCreated","required": false            },            {"@type": "IriTemplateMapping","variable": "datePublished[before]","property": "datePublished","required": false            },            {"@type": "IriTemplateMapping","variable": "datePublished[strictly_before]","property": "datePublished","required": false            },            {"@type": "IriTemplateMapping","variable": "datePublished[after]","property": "datePublished","required": false            },            {"@type": "IriTemplateMapping","variable": "datePublished[strictly_after]","property": "datePublished","required": false            },            {"@type": "IriTemplateMapping","variable": "dateModified[before]","property": "dateModified","required": false            },            {"@type": "IriTemplateMapping","variable": "dateModified[strictly_before]","property": "dateModified","required": false            },            {"@type": "IriTemplateMapping","variable": "dateModified[after]","property": "dateModified","required": false            },            {"@type": "IriTemplateMapping","variable": "dateModified[strictly_after]","property": "dateModified","required": false            },            {"@type": "IriTemplateMapping","variable": "dateDeleted[before]","property": "dateDeleted","required": false            },            {"@type": "IriTemplateMapping","variable": "dateDeleted[strictly_before]","property": "dateDeleted","required": false            },            {"@type": "IriTemplateMapping","variable": "dateDeleted[after]","property": "dateDeleted","required": false            },            {"@type": "IriTemplateMapping","variable": "dateDeleted[strictly_after]","property": "dateDeleted","required": false            },            {"@type": "IriTemplateMapping","variable": "order[id]","property": "id","required": false            },            {"@type": "IriTemplateMapping","variable": "order[name]","property": "name","required": false            },            {"@type": "IriTemplateMapping","variable": "order[dateCreated]","property": "dateCreated","required": false            },            {"@type": "IriTemplateMapping","variable": "order[datePublished]","property": "datePublished","required": false            },            {"@type": "IriTemplateMapping","variable": "order[dateModified]","property": "dateModified","required": false            },            {"@type": "IriTemplateMapping","variable": "order[dateDeleted]","property": "dateDeleted","required": false            }        ]    }}

one would then assume that api/organisations?platform=ba8d9958-d8d5-11e8-a9a5-5254007d3b24 gives all the organizations belonging to that platform, but no. It gives

{"@context": "/api/contexts/Organisation","@id": "/api/organisations","@type": "hydra:Collection","hydra:member": [],"hydra:totalItems": 0,"hydra:view": {"@id": "/api/organisations?platform=ba8d9958-d8d5-11e8-a9a5-5254007d3b24","@type": "hydra:PartialCollectionView"    },"hydra:search": {"@type": "hydra:IriTemplate","hydra:template": "/api/organisations{?id,id[],description,name,organisation,organisation[],platform,platform[],dateCreated[before],dateCreated[strictly_before],dateCreated[after],dateCreated[strictly_after],datePublished[before],datePublished[strictly_before],datePublished[after],datePublished[strictly_after],dateModified[before],dateModified[strictly_before],dateModified[after],dateModified[strictly_after],dateDeleted[before],dateDeleted[strictly_before],dateDeleted[after],dateDeleted[strictly_after],order[id],order[name],order[dateCreated],order[datePublished],order[dateModified],order[dateDeleted]}","hydra:variableRepresentation": "BasicRepresentation","hydra:mapping": [            {"@type": "IriTemplateMapping","variable": "id","property": "id","required": false            },            {"@type": "IriTemplateMapping","variable": "id[]","property": "id","required": false            },            {"@type": "IriTemplateMapping","variable": "description","property": "description","required": false            },            {"@type": "IriTemplateMapping","variable": "name","property": "name","required": false            },            {"@type": "IriTemplateMapping","variable": "organisation","property": "organisation","required": false            },            {"@type": "IriTemplateMapping","variable": "organisation[]","property": "organisation","required": false        },        {"@type": "IriTemplateMapping","variable": "platform","property": "platform","required": false        },        {"@type": "IriTemplateMapping","variable": "platform[]","property": "platform","required": false        },        {"@type": "IriTemplateMapping","variable": "dateCreated[before]","property": "dateCreated","required": false        },        {"@type": "IriTemplateMapping","variable": "dateCreated[strictly_before]","property": "dateCreated","required": false        },        {"@type": "IriTemplateMapping","variable": "dateCreated[after]","property": "dateCreated","required": false        },        {"@type": "IriTemplateMapping","variable": "dateCreated[strictly_after]","property": "dateCreated","required": false        },        {"@type": "IriTemplateMapping","variable": "datePublished[before]","property": "datePublished","required": false        },        {"@type": "IriTemplateMapping","variable": "datePublished[strictly_before]","property": "datePublished","required": false        },        {"@type": "IriTemplateMapping","variable": "datePublished[after]","property": "datePublished","required": false        },        {"@type": "IriTemplateMapping","variable": "datePublished[strictly_after]","property": "datePublished","required": false        },        {"@type": "IriTemplateMapping","variable": "dateModified[before]","property": "dateModified","required": false        },        {"@type": "IriTemplateMapping","variable": "dateModified[strictly_before]","property": "dateModified","required": false        },        {"@type": "IriTemplateMapping","variable": "dateModified[after]","property": "dateModified","required": false        },        {"@type": "IriTemplateMapping","variable": "dateModified[strictly_after]","property": "dateModified","required": false        },        {"@type": "IriTemplateMapping","variable": "dateDeleted[before]","property": "dateDeleted","required": false        },        {"@type": "IriTemplateMapping","variable": "dateDeleted[strictly_before]","property": "dateDeleted","required": false        },        {"@type": "IriTemplateMapping","variable": "dateDeleted[after]","property": "dateDeleted","required": false        },        {"@type": "IriTemplateMapping","variable": "dateDeleted[strictly_after]","property": "dateDeleted","required": false        },        {"@type": "IriTemplateMapping","variable": "order[id]","property": "id","required": false        },        {"@type": "IriTemplateMapping","variable": "order[name]","property": "name","required": false        },        {"@type": "IriTemplateMapping","variable": "order[dateCreated]","property": "dateCreated","required": false        },        {"@type": "IriTemplateMapping","variable": "order[datePublished]","property": "datePublished","required": false        },        {"@type": "IriTemplateMapping","variable": "order[dateModified]","property": "dateModified","required": false        },        {"@type": "IriTemplateMapping","variable": "order[dateDeleted]","property": "dateDeleted","required": false        }    ]}

}

Or simply put zero organization. Now for bug hunting purposes I build the entire thing again but now using incremental ID's and then it works like a charm. So the problem seems to be the way ApiFilter works. That would suggest that ApiFilter doesn't actually normalize the UUID before using it in a search...

-- Update --

Okey, so I tried bypassing this with an custom filter

namespace App\Conduction\AppBundle\Filter;use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;use Doctrine\ORM\QueryBuilder;use Ramsey\Uuid\Uuid;final class UuidFilter extends AbstractContextAwareFilter {    protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)    {        // otherwise filter is applied to order and page as well        if (                !$this->isPropertyEnabled($property, $resourceClass) ||                !$this->isPropertyMapped($property, $resourceClass)                ) {                    return;                }                $value =pack("h*", str_replace('-', '', $value));                $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters                $queryBuilder                ->andWhere("$property =:$parameterName")                ->setParameter($parameterName, $value);    }    // This function is only used to hook in documentation generators (supported by Swagger and Hydra)    public function getDescription(string $resourceClass): array    {        if (!$this->properties) {            return [];        }        $description = [];        foreach ($this->properties as $property => $strategy) {            $description["uuid"] = ['property' => $property,'type' => 'string','required' => false,'swagger' => ['description' => 'This filter allows to search the api based on object uuids','name' => 'UUID','type' => 'Primary identifier',                    ],            ];        }        return $description;    }}

But then I run into an doctrine error "[Semantical Error] line 0, col 76 near 'platform =:platform_p1': Error: 'platform' is not defined."

That of course suggests that platform isn't defined but as far as I can see it is.

--- UPDATE: Solved it! ---

Interesting thought, but unfortunately that also returns zero results. But interesting stuff is happening. So using the IRI doctrine does get the proper intel -> as seen in the logs as:[2018-10-30 10:00:50] doctrine.DEBUG: SELECT o0_.slug AS slug_0, o0_.id AS id_1, o0_.name AS name_2, o0_.description AS description_3, o0_.date_created AS date_created_4, o0_.date_published AS date_published_5, o0_.date_modified AS date_modified_6, o0_.date_deleted AS date_deleted_7, o0_.date_checkout AS date_checkout_8, o0_.version AS version_9, o0_.address_id AS address_id_10, o0_.organisation_id AS organisation_id_11, o0_.default_tax_id AS default_tax_id_12, o0_.member_group_id AS member_group_id_13, o0_.admin_group_id AS admin_group_id_14, o0_.image_id AS image_id_15, o0_.platform_id AS platform_id_16, o0_.user_owned AS user_owned_17, o0_.user_created AS user_created_18, o0_.user_updated AS user_updated_19, o0_.user_checkout AS user_checkout_20 FROM organisation__organisation o0_ WHERE o0_.platform_id = ? AND o0_.date_deleted IS NULL AND o0_.date_published < ? ORDER BY o0_.id ASC ["[object] (Ramsey\\Uuid\\Uuid: \"13be1406-d9c3-11e8-9d84-5254007d3b24\")","2018-10-30 10:00:50"] []

So here doctrine is apparently aware that it is searching for a Ramsy\Uuid instead of “13be1406-d9c3-11e8-9d84-5254007d3b2” directly. Okey so what if we search without the the platform IRI?That gives a nice response of 1 object, and the following doctrine log

[2018-10-30 10:09:46] doctrine.DEBUG: SELECT o0_.slug AS slug_0, o0_.id AS id_1, o0_.name AS name_2, o0_.description AS description_3, o0_.date_created AS date_created_4, o0_.date_published AS date_published_5, o0_.date_modified AS date_modified_6, o0_.date_deleted AS date_deleted_7, o0_.date_checkout AS date_checkout_8, o0_.version AS version_9, o0_.address_id AS address_id_10, o0_.organisation_id AS organisation_id_11, o0_.default_tax_id AS default_tax_id_12, o0_.member_group_id AS member_group_id_13, o0_.admin_group_id AS admin_group_id_14, o0_.image_id AS image_id_15, o0_.platform_id AS platform_id_16, o0_.user_owned AS user_owned_17, o0_.user_created AS user_created_18, o0_.user_updated AS user_updated_19, o0_.user_checkout AS user_checkout_20 FROM organisation__organisation o0_ WHERE o0_.date_deleted IS NULL AND o0_.date_published < ? ORDER BY o0_.id ASC ["2018-10-30 10:09:46"] []

So the difference really seams to be in the Ramsy\Uuid, peculiar thing here is that if we get .. /api/platforms/13be1406-d9c3-11e8-9d84-5254007d3b24 we get a nice response of that platform object.So the parsing of the Ramsy\Uuid in doctrine looks to be working, uh okey…. So what about that id field then? If we just grap the database and do a simple join. We are able to join the organization and id, so the platform.id and organization__organistation.platform_id field do contain the same value. And then for the real kicker….

.../api/organisations?platform.id=13be1406-d9c3-11e8-9d84-5254007d3b24

Does work! So that solves our problem “yeah”, just in an unexpected way. It present us with a doctrine log of:

[2018-10-30 10:19:15] doctrine.DEBUG: SELECT t0.editable AS editable_1, t0.memberGroup AS memberGroup_2, t0.id AS id_3, t0.name AS name_4, t0.description AS description_5, t0.date_created AS date_created_6, t0.date_published AS date_published_7, t0.date_modified AS date_modified_8, t0.date_deleted AS date_deleted_9, t0.date_checkout AS date_checkout_10, t0.version AS version_11, t0.organisation_id AS organisation_id_12, t13.slug AS slug_14, t13.id AS id_15, t13.name AS name_16, t13.description AS description_17, t13.date_created AS date_created_18, t13.date_published AS date_published_19, t13.date_modified AS date_modified_20, t13.date_deleted AS date_deleted_21, t13.date_checkout AS date_checkout_22, t13.version AS version_23, t13.address_id AS address_id_24, t13.organisation_id AS organisation_id_25, t13.default_tax_id AS default_tax_id_26, t13.member_group_id AS member_group_id_27, t13.admin_group_id AS admin_group_id_28, t13.image_id AS image_id_29, t13.platform_id AS platform_id_30, t13.user_owned AS user_owned_31, t13.user_created AS user_created_32, t13.user_updated AS user_updated_33, t13.user_checkout AS user_checkout_34, t0.parent_id AS parent_id_35, t0.image_id AS image_id_36, t0.platform_id AS platform_id_37, t0.user_owned AS user_owned_38, t0.user_created AS user_created_39, t0.user_updated AS user_updated_40, t0.user_checkout AS user_checkout_41 FROM organisation__organisation_group t0 LEFT JOIN organisation__organisation t13 ON t0.organisation_id = t13.id INNER JOIN organisation__organisation_group_member ON t0.id = organisation__organisation_group_member.group_id WHERE organisation__organisation_group_member.user_id = ? ["[object] (Ramsey\\Uuid\\Uuid: \"154f07d0-d9c3-11e8-bf04-5254007d3b24\")"] []

Wait wut? Yeah. If we do an api/organisations?platform=/api/platforms/ba8d9958-d8d5-11e8-a9a5-5254007d3b24. Doctrine doesn't do an inner join and can't correctly decode the UUID but when we do api/organisations?platform.id=ba8d9958-d8d5-11e8-a9a5-5254007d3b24. API platform forces an inner join and then it suddenly works...

Okay our problem is solved now, but I still think that this is something the either API Platform (that supports Ramsy\Uuid) or Ramsy\Uuid might want to look into


Viewing all articles
Browse latest Browse all 3925

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>