diff --git a/src/Annotation/Property.php b/src/Annotation/Property.php index 7342100..6a957b8 100644 --- a/src/Annotation/Property.php +++ b/src/Annotation/Property.php @@ -27,6 +27,8 @@ final class Property implements PropertyAnnotationInterface, DumperInterface */ public string $type; + public ?string $enumType = null; + public bool $multilanguage = false; /** @@ -37,7 +39,7 @@ final class Property implements PropertyAnnotationInterface, DumperInterface /** * The object name must be defined, if type is 'object' or 'nested' */ - public string $objectName; + public ?string $objectName = null; /** * Defines if related object will have one or multiple values. diff --git a/src/Mapping/DocumentParser.php b/src/Mapping/DocumentParser.php index 94d2a73..0e68d5a 100644 --- a/src/Mapping/DocumentParser.php +++ b/src/Mapping/DocumentParser.php @@ -103,10 +103,12 @@ public function getPropertiesMetadata(\ReflectionClass $documentReflection): arr switch ($propertyAnnotation::class) { case Property::class: $propertyMetadata[$propertyAnnotation->name] = [ - 'propertyName' => $propertyName, - 'type' => $propertyAnnotation->type, - 'multilanguage' => $propertyAnnotation->multilanguage, + 'propertyName' => $propertyName, + 'type' => $propertyAnnotation->type, ]; + if ($propertyAnnotation->multilanguage) { + $propertyMetadata[$propertyAnnotation->name]['multilanguage'] = true; + } // If property is a (nested) object if (\in_array($propertyAnnotation->type, ['object', 'nested'])) { @@ -122,6 +124,10 @@ public function getPropertiesMetadata(\ReflectionClass $documentReflection): arr 'className' => $child->getName(), ] ); + } else { + if (null !== $propertyAnnotation->enumType) { + $propertyMetadata[$propertyAnnotation->name]['enumType'] = $propertyAnnotation->enumType; + } } break; diff --git a/src/Result/DocumentConverter.php b/src/Result/DocumentConverter.php index bc44fcc..c79387d 100644 --- a/src/Result/DocumentConverter.php +++ b/src/Result/DocumentConverter.php @@ -85,7 +85,9 @@ public function assignArrayToObject(array $array, ObjectInterface $object, array continue; } - if (\in_array($propertyMetadata['type'], ['string', 'keyword', 'text']) && !empty($propertyMetadata['multilanguage'])) { + if (!empty($propertyMetadata['enumType'])) { + $objectValue = $propertyMetadata['enumType']::from($array[$esField]); + } elseif (\in_array($propertyMetadata['type'], ['string', 'keyword', 'text']) && !empty($propertyMetadata['multilanguage'])) { $objectValue = null; foreach ($array as $fieldName => $value) { $prefixLength = \strlen($esField.$this->languageSeparator); diff --git a/tests/App/fixture/Acme/FooBundle/Document/Customer.php b/tests/App/fixture/Acme/FooBundle/Document/Customer.php index d4de22a..ecea22a 100644 --- a/tests/App/fixture/Acme/FooBundle/Document/Customer.php +++ b/tests/App/fixture/Acme/FooBundle/Document/Customer.php @@ -5,6 +5,7 @@ use Sineflow\ElasticsearchBundle\Annotation as ES; use Sineflow\ElasticsearchBundle\Document\AbstractDocument; use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Provider\CustomerProvider; +use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum; /** * @ES\Document( @@ -16,15 +17,22 @@ class Customer extends AbstractDocument /** * Test adding raw mapping. * - * @var string - * * @ES\Property(name="name", type="keyword") */ - public $name; + public string $name; /** - * @var bool + * Test adding raw mapping. * + * @ES\Property( + * name="customer_type", + * type="integer", + * enumType=Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum::class + * ) + */ + public ?CustomerTypeEnum $customerType = null; + + /** * @ES\Property(name="active", type="boolean") */ private $active; diff --git a/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php b/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php new file mode 100644 index 0000000..1a0a2af --- /dev/null +++ b/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php @@ -0,0 +1,22 @@ + [ [ - '_id' => 111, - 'name' => 'Jane Doe', - 'title' => 'aaa bbb', - 'active' => true, + '_id' => 111, + 'name' => 'Jane Doe', + 'title' => 'aaa bbb', + 'active' => true, + 'customer_type' => CustomerTypeEnum::COMPANY, // When php-elasticsearch serializes the request, json_encode will convert this to a scalar value + ], + [ + '_id' => 222, + 'name' => 'John Doe', + 'title' => 'bbb', + 'customer_type' => 1, ], ], ]; @@ -153,12 +161,31 @@ public function testFindInMultipleTypesAndIndices(): void 'title' => 'bbb', ], ], + 'sort' => [ + '_id' => 'asc', + ], ]; $res = $finder->find(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody, Finder::RESULTS_OBJECT, [], $totalHits); $this->assertInstanceOf(DocumentIterator::class, $res); - $this->assertCount(2, $res); - $this->assertEquals(2, $totalHits); + $this->assertCount(3, $res); + $this->assertEquals(3, $totalHits); + $resAsArray = iterator_to_array($res); + + $this->assertInstanceOf(Customer::class, $resAsArray[0]); + $this->assertInstanceOf(Customer::class, $resAsArray[1]); + $this->assertInstanceOf(Product::class, $resAsArray[2]); + + $this->assertEquals(111, $resAsArray[0]->id); + $this->assertSame('Jane Doe', $resAsArray[0]->name); + $this->assertSame(CustomerTypeEnum::COMPANY, $resAsArray[0]->customerType); + + $this->assertEquals(222, $resAsArray[1]->id); + $this->assertSame('John Doe', $resAsArray[1]->name); + $this->assertSame(CustomerTypeEnum::INDIVIDUAL, $resAsArray[1]->customerType); + + $this->assertSame('doc2', $resAsArray[2]->id); + $this->assertSame('bbb', $resAsArray[2]->title); $res = $finder->find(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody, Finder::RESULTS_ARRAY); $this->assertArraySubset([ @@ -166,9 +193,15 @@ public function testFindInMultipleTypesAndIndices(): void 'title' => 'bbb', ], 111 => [ - 'name' => 'Jane Doe', - 'title' => 'aaa bbb', - 'active' => true, + 'name' => 'Jane Doe', + 'title' => 'aaa bbb', + 'active' => true, + 'customer_type' => 2, + ], + 222 => [ + 'name' => 'John Doe', + 'title' => 'bbb', + 'customer_type' => 1, ], ], $res); @@ -214,8 +247,8 @@ public function testCount(): void ], ]; - $this->assertEquals(1, $finder->count(['AcmeFooBundle:Customer'], $searchBody)); - $this->assertEquals(2, $finder->count(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody)); + $this->assertEquals(2, $finder->count(['AcmeFooBundle:Customer'], $searchBody)); + $this->assertEquals(3, $finder->count(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody)); } public function testGetTargetIndices(): void diff --git a/tests/Functional/Mapping/DocumentMetadataCollectorTest.php b/tests/Functional/Mapping/DocumentMetadataCollectorTest.php index 324a257..db153c3 100644 --- a/tests/Functional/Mapping/DocumentMetadataCollectorTest.php +++ b/tests/Functional/Mapping/DocumentMetadataCollectorTest.php @@ -14,6 +14,7 @@ use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Repository\ProductRepository; use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Customer; use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Provider\CustomerProvider; +use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum; use Symfony\Contracts\Cache\CacheInterface; /** @@ -41,6 +42,9 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase 'active' => [ 'type' => 'boolean', ], + 'customer_type' => [ + 'type' => 'integer', + ], ], 'fields' => [ ], @@ -48,14 +52,18 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase 'name' => [ 'propertyName' => 'name', 'type' => 'keyword', - 'multilanguage' => null, + 'propertyAccess' => 1, + ], + 'customer_type' => [ + 'propertyName' => 'customerType', + 'type' => 'integer', + 'enumType' => CustomerTypeEnum::class, 'propertyAccess' => 1, ], 'active' => [ - 'propertyName' => 'active', - 'type' => 'boolean', - 'multilanguage' => null, - 'methods' => [ + 'propertyName' => 'active', + 'type' => 'boolean', + 'methods' => [ 'getter' => 'isActive', 'setter' => 'setActive', ], @@ -190,43 +198,36 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase 'title' => [ 'propertyName' => 'title', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'description' => [ 'propertyName' => 'description', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'category' => [ 'propertyName' => 'category', 'type' => 'object', - 'multilanguage' => null, 'multiple' => null, 'propertiesMetadata' => [ 'id' => [ 'propertyName' => 'id', 'type' => 'integer', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'title' => [ 'propertyName' => 'title', 'type' => 'keyword', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'tags' => [ 'propertyName' => 'tags', 'type' => 'object', - 'multilanguage' => null, 'multiple' => true, 'propertiesMetadata' => [ 'tagname' => [ 'propertyName' => 'tagName', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], ], @@ -240,31 +241,26 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase 'related_categories' => [ 'propertyName' => 'relatedCategories', 'type' => 'object', - 'multilanguage' => null, 'multiple' => true, 'propertiesMetadata' => [ 'id' => [ 'propertyName' => 'id', 'type' => 'integer', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'title' => [ 'propertyName' => 'title', 'type' => 'keyword', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'tags' => [ 'propertyName' => 'tags', 'type' => 'object', - 'multilanguage' => null, 'multiple' => true, 'propertiesMetadata' => [ 'tagname' => [ 'propertyName' => 'tagName', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], ], @@ -278,25 +274,21 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase 'price' => [ 'propertyName' => 'price', 'type' => 'float', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'location' => [ 'propertyName' => 'location', 'type' => 'geo_point', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'limited' => [ 'propertyName' => 'limited', 'type' => 'boolean', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'released' => [ 'propertyName' => 'released', 'type' => 'date', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'ml_info' => [ @@ -314,7 +306,6 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase 'pieces_count' => [ 'propertyName' => 'tokenPiecesCount', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], '_id' => [ diff --git a/tests/Functional/Mapping/DocumentParserTest.php b/tests/Functional/Mapping/DocumentParserTest.php index f028edf..23058ab 100644 --- a/tests/Functional/Mapping/DocumentParserTest.php +++ b/tests/Functional/Mapping/DocumentParserTest.php @@ -2,6 +2,7 @@ namespace Sineflow\ElasticsearchBundle\Tests\Functional\Mapping; +use Doctrine\Common\Annotations\AnnotationException; use Doctrine\Common\Annotations\AnnotationReader; use Sineflow\ElasticsearchBundle\Mapping\DocumentLocator; use Sineflow\ElasticsearchBundle\Mapping\DocumentParser; @@ -10,6 +11,7 @@ use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\ObjTag; use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Product; use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Repository\ProductRepository; +use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\EntityWithInvalidEnum; class DocumentParserTest extends AbstractContainerAwareTestCase { @@ -32,6 +34,13 @@ public function testParseNonDocument(): void $this->assertEquals([], $res); } + public function testParseDocumentWithInvalidEnumFieldProperty() + { + $this->expectException(AnnotationException::class); + $reflection = new \ReflectionClass(EntityWithInvalidEnum::class); + $this->documentParser->parse($reflection, []); + } + public function testParse(): void { $reflection = new \ReflectionClass(Product::class); @@ -159,43 +168,36 @@ public function testParse(): void 'title' => [ 'propertyName' => 'title', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'description' => [ 'propertyName' => 'description', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'category' => [ 'propertyName' => 'category', 'type' => 'object', - 'multilanguage' => null, 'multiple' => null, 'propertiesMetadata' => [ 'id' => [ 'propertyName' => 'id', 'type' => 'integer', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'title' => [ 'propertyName' => 'title', 'type' => 'keyword', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'tags' => [ 'propertyName' => 'tags', 'type' => 'object', - 'multilanguage' => null, 'multiple' => true, 'propertiesMetadata' => [ 'tagname' => [ 'propertyName' => 'tagName', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], ], @@ -209,31 +211,26 @@ public function testParse(): void 'related_categories' => [ 'propertyName' => 'relatedCategories', 'type' => 'object', - 'multilanguage' => null, 'multiple' => true, 'propertiesMetadata' => [ 'id' => [ 'propertyName' => 'id', 'type' => 'integer', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'title' => [ 'propertyName' => 'title', 'type' => 'keyword', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'tags' => [ 'propertyName' => 'tags', 'type' => 'object', - 'multilanguage' => null, 'multiple' => true, 'propertiesMetadata' => [ 'tagname' => [ 'propertyName' => 'tagName', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], ], @@ -247,25 +244,21 @@ public function testParse(): void 'price' => [ 'propertyName' => 'price', 'type' => 'float', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'location' => [ 'propertyName' => 'location', 'type' => 'geo_point', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'limited' => [ 'propertyName' => 'limited', 'type' => 'boolean', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'released' => [ 'propertyName' => 'released', 'type' => 'date', - 'multilanguage' => null, 'propertyAccess' => 1, ], 'ml_info' => [ @@ -283,7 +276,6 @@ public function testParse(): void 'pieces_count' => [ 'propertyName' => 'tokenPiecesCount', 'type' => 'text', - 'multilanguage' => null, 'propertyAccess' => 1, ], '_id' => [