docs icon indicating copy to clipboard operation
docs copied to clipboard

Elasticsearch - how to create a custom filter

Open signor-pedro opened this issue 1 year ago • 0 comments

Hi, I was trying to create a custom filter for geo-distance query.

I had a very hard time understanding what to do - this section is documented rather poorly.

I ended up doing something like this:

use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface;
use ApiPlatform\Elasticsearch\Filter\AbstractFilter;
use ApiPlatform\Metadata\Operation;

class GeoDistanceFilter extends AbstractFilter implements RequestBodySearchCollectionExtensionInterface
{
    public function apply(array $clauseBody, string $resourceClass, ?Operation $operation = null, array $context = []): array
    {
        throw new \LogicException(
            'This method is never called, but for some reason dictated by the AbstractFilter type.'
        );
    }

    public function applyToCollection(array $requestBody, string $resourceClass, ?Operation $operation = null, array $context = []): array
    {
        // Check for the presence of geo-search parameters
        $filters = $context['filters'] ?? [];

        if (!isset($filters['latitute'], $filters['longitude'], $filters['distance'])) {
            return $requestBody;
        }

        // ... construct and apply the geo-distance query ...

        return $requestBody;
    }

    public function getDescription(string $resourceClass): array
    {
        return [
            'latitute' => [
                'property' => 'lat',
                'type' => 'float',
                'required' => false,
                'description' => 'Latitude for geo-distance search.',
                'openapi' => [
                    'example' => 48.8566,
                ],
            ],
            'longitude' => [
                'property' => 'lon',
                'type' => 'float',
                'required' => false,
                'description' => 'Longitude for geo-distance search.',
                'openapi' => [
                    'example' => 2.3522,
                ],
            ],
            'distance' => [
                'property' => 'distance',
                'type' => 'string',
                'required' => false,
                'description' => 'The maximum distance from the point (lat, lon). Example: "30km".',
                'openapi' => [
                    'example' => '30km',
                ],
            ],
        ];
    }
}

I had a hard time understanding some of these concepts:

  • what is the point of the apply method if it never gets called on runtime - some sort of a BC/adapter thing?
  • what is a reasonable code example of a custom filter? The docs only show an example of an extension:
class AndOperatorFilterExtension implements RequestBodySearchCollectionExtensionInterface
{
    public function applyToCollection(array $requestBody, string $resourceClass, ?Operation $operation = null, array $context = []): array;
    {
        $requestBody['query'] = $requestBody['query'] ?? [];
        $andQuery = [
            'query' => $context['filters']['fullName'],
            'operator' => 'and',
        ];
        
        $requestBody['query']['constant_score']['filter']['bool']['must'][0]['match']['full_name'] = $andQuery;
        
        return $requestBody;
    }
}

However this example would not work by itself, as it does not implement AbstractFilter nor FilterInterface. A more complete example together with an example of the annotation such as

#[ApiFilter(CustomGeoDistanceFilter::class, properties: ['locations.point' => 'geo_distance'])]
class SearchOffer
{
}

would be very appreciated.

Thanks

signor-pedro avatar Mar 21 '24 09:03 signor-pedro