Skip to content

Commit

Permalink
feat(elasticsearch): filtering on nested fields (api-platform#5820)
Browse files Browse the repository at this point in the history
* chore(elasticsearch): define a valid, known type for 'baz'

* fix(elasticsearch): fix nested object paths (api-platform/api-platform#1375)

* fix(elasticsearch): support additional nesting within collections (api-platform#5642)

---------

Co-authored-by: Colin O'Dell <[email protected]>
  • Loading branch information
jonnyeom and colinodell authored Sep 14, 2023
1 parent 32242a2 commit d85884d
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 4 deletions.
34 changes: 33 additions & 1 deletion src/Elasticsearch/Tests/Filter/MatchFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function testApply(): void
);
}

public function testApplyWithNestedProperty(): void
public function testApplyWithNestedArrayProperty(): void
{
$fooType = new Type(Type::BUILTIN_TYPE_ARRAY, false, Foo::class, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, Foo::class));
$barType = new Type(Type::BUILTIN_TYPE_STRING);
Expand Down Expand Up @@ -119,6 +119,38 @@ public function testApplyWithNestedProperty(): void
);
}

public function testApplyWithNestedObjectProperty(): void
{
$fooType = new Type(Type::BUILTIN_TYPE_OBJECT, false, Foo::class);
$barType = new Type(Type::BUILTIN_TYPE_STRING);

$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
$propertyMetadataFactoryProphecy->create(Foo::class, 'foo')->willReturn((new ApiProperty())->withBuiltinTypes([$fooType]))->shouldBeCalled();
$propertyMetadataFactoryProphecy->create(Foo::class, 'bar')->willReturn((new ApiProperty())->withBuiltinTypes([$barType]))->shouldBeCalled();

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->isResourceClass(Foo::class)->willReturn(true)->shouldBeCalled();

$nameConverterProphecy = $this->prophesize(NameConverterInterface::class);
$nameConverterProphecy->normalize('foo.bar', Foo::class, null, Argument::type('array'))->willReturn('foo.bar')->shouldBeCalled();
$nameConverterProphecy->normalize('foo', Foo::class, null, Argument::type('array'))->willReturn('foo')->shouldBeCalled();

$matchFilter = new MatchFilter(
$this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(),
$propertyMetadataFactoryProphecy->reveal(),
$resourceClassResolverProphecy->reveal(),
$this->prophesize(IriConverterInterface::class)->reveal(),
$this->prophesize(PropertyAccessorInterface::class)->reveal(),
$nameConverterProphecy->reveal(),
['foo.bar' => null]
);

self::assertSame(
['bool' => ['must' => [['nested' => ['path' => 'foo', 'query' => ['match' => ['foo.bar' => 'Krupicka']]]]]]],
$matchFilter->apply([], Foo::class, null, ['filters' => ['foo.bar' => 'Krupicka']])
);
}

public function testApplyWithInvalidFilters(): void
{
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
Expand Down
34 changes: 33 additions & 1 deletion src/Elasticsearch/Tests/Filter/TermFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function testApply(): void
);
}

public function testApplyWithNestedProperty(): void
public function testApplyWithNestedArrayProperty(): void
{
$fooType = new Type(Type::BUILTIN_TYPE_ARRAY, false, Foo::class, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, Foo::class));
$barType = new Type(Type::BUILTIN_TYPE_STRING);
Expand Down Expand Up @@ -119,6 +119,38 @@ public function testApplyWithNestedProperty(): void
);
}

public function testApplyWithNestedObjectProperty(): void
{
$fooType = new Type(Type::BUILTIN_TYPE_OBJECT, false, Foo::class);
$barType = new Type(Type::BUILTIN_TYPE_STRING);

$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
$propertyMetadataFactoryProphecy->create(Foo::class, 'foo')->willReturn((new ApiProperty())->withBuiltinTypes([$fooType]))->shouldBeCalled();
$propertyMetadataFactoryProphecy->create(Foo::class, 'bar')->willReturn((new ApiProperty())->withBuiltinTypes([$barType]))->shouldBeCalled();

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->isResourceClass(Foo::class)->willReturn(true)->shouldBeCalled();

$nameConverterProphecy = $this->prophesize(NameConverterInterface::class);
$nameConverterProphecy->normalize('foo.bar', Foo::class, null, Argument::type('array'))->willReturn('foo.bar')->shouldBeCalled();
$nameConverterProphecy->normalize('foo', Foo::class, null, Argument::type('array'))->willReturn('foo')->shouldBeCalled();

$termFilter = new TermFilter(
$this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(),
$propertyMetadataFactoryProphecy->reveal(),
$resourceClassResolverProphecy->reveal(),
$this->prophesize(IriConverterInterface::class)->reveal(),
$this->prophesize(PropertyAccessorInterface::class)->reveal(),
$nameConverterProphecy->reveal(),
['foo.bar' => null]
);

self::assertSame(
['bool' => ['must' => [['nested' => ['path' => 'foo', 'query' => ['term' => ['foo.bar' => 'Krupicka']]]]]]],
$termFilter->apply([], Foo::class, null, ['filters' => ['foo.bar' => 'Krupicka']])
);
}

public function testApplyWithInvalidFilters(): void
{
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
Expand Down
13 changes: 13 additions & 0 deletions src/Elasticsearch/Tests/Util/FieldDatatypeTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ public function testGetNestedFieldPath(): void
$fieldDatatype = $this->getValidFieldDatatype();

self::assertSame('foo.bar', $fieldDatatype->getNestedFieldPath(Foo::class, 'foo.bar.baz'));
self::assertSame('foo', $fieldDatatype->getNestedFieldPath(Foo::class, 'foo.baz'));
self::assertNull($fieldDatatype->getNestedFieldPath(Foo::class, 'baz'));
}

public function testGetNestedFieldInNestedCollection(): void
{
$fieldDatatype = $this->getValidFieldDatatype();

self::assertSame('bar.foo', $fieldDatatype->getNestedFieldPath(Foo::class, 'bar.foo.baz'));
self::assertSame('bar', $fieldDatatype->getNestedFieldPath(Foo::class, 'bar.foo'));
self::assertNull($fieldDatatype->getNestedFieldPath(Foo::class, 'baz'));
}

Expand Down Expand Up @@ -72,17 +82,20 @@ public function testIsNestedField(): void
$fieldDatatype = $this->getValidFieldDatatype();

self::assertTrue($fieldDatatype->isNestedField(Foo::class, 'foo.bar.baz'));
self::assertTrue($fieldDatatype->isNestedField(Foo::class, 'foo.baz'));
self::assertFalse($fieldDatatype->isNestedField(Foo::class, 'baz'));
}

private function getValidFieldDatatype()
{
$fooType = new Type(Type::BUILTIN_TYPE_OBJECT, false, Foo::class);
$barType = new Type(Type::BUILTIN_TYPE_ARRAY, false, Foo::class, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, Foo::class));
$bazType = new Type(Type::BUILTIN_TYPE_STRING, false, Foo::class);

$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
$propertyMetadataFactoryProphecy->create(Foo::class, 'foo')->willReturn((new ApiProperty())->withBuiltinTypes([$fooType]))->shouldBeCalled();
$propertyMetadataFactoryProphecy->create(Foo::class, 'bar')->willReturn((new ApiProperty())->withBuiltinTypes([$barType]))->shouldBeCalled();
$propertyMetadataFactoryProphecy->create(Foo::class, 'baz')->willReturn((new ApiProperty())->withBuiltinTypes([$bazType]));

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->isResourceClass(Foo::class)->willReturn(true)->shouldBeCalled();
Expand Down
6 changes: 4 additions & 2 deletions src/Elasticsearch/Util/FieldDatatypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private function getNestedFieldPath(string $resourceClass, string $property): ?s
) {
$nestedPath = $this->getNestedFieldPath($nextResourceClass, implode('.', $properties));

return null === $nestedPath ? $nestedPath : "$currentProperty.$nestedPath";
return null === $nestedPath ? $currentProperty : "$currentProperty.$nestedPath";
}

if (
Expand All @@ -78,7 +78,9 @@ private function getNestedFieldPath(string $resourceClass, string $property): ?s
&& null !== ($className = $type->getClassName())
&& $this->resourceClassResolver->isResourceClass($className)
) {
return $currentProperty;
$nestedPath = $this->getNestedFieldPath($className, implode('.', $properties));

return null === $nestedPath ? $currentProperty : "$currentProperty.$nestedPath";
}
}

Expand Down

0 comments on commit d85884d

Please sign in to comment.