Skip to content

Commit 8d855b3

Browse files
committed
When converting ES document array to an object, handle the case where there may be a single object value for a 'multiple' object field instead of an array and vice versa (fixes #9)
1 parent 2b76165 commit 8d855b3

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Sineflow\ElasticsearchBundle\Exception;
4+
5+
/**
6+
* Class DocumentConversionException
7+
*/
8+
class DocumentConversionException extends Exception
9+
{
10+
}

Result/DocumentConverter.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Sineflow\ElasticsearchBundle\Document\DocumentInterface;
66
use Sineflow\ElasticsearchBundle\Document\MLProperty;
77
use Sineflow\ElasticsearchBundle\Document\ObjectInterface;
8+
use Sineflow\ElasticsearchBundle\Exception\DocumentConversionException;
89
use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadata;
910
use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadataCollector;
1011

@@ -114,11 +115,26 @@ public function assignArrayToObject(array $array, ObjectInterface $object, array
114115
}
115116
}
116117
} elseif (in_array($propertyMetadata['type'], ['object', 'nested'])) {
118+
// ES doesn't mind having either single or multiple objects with the same mapping, but in this bundle we must specifically declare either.
119+
// So we must make sure everything works for a 'multiple' definition where we actually have a single object and vice versa.
120+
if ($propertyMetadata['multiple'] && key($array[$esField]) !== 0) {
121+
// field is declared multiple, but actual data is single object
122+
$data = [$array[$esField]];
123+
} elseif (!$propertyMetadata['multiple'] && key($array[$esField]) === 0) {
124+
// field is declared as single object, but actual data is an array of objects
125+
if (count($array[$esField]) > 1) {
126+
throw new DocumentConversionException(sprintf('Multiple objects found for a single object field `%s`', $propertyMetadata['propertyName']));
127+
}
128+
$data = current($array[$esField]);
129+
} else {
130+
$data = $array[$esField];
131+
}
132+
117133
if ($propertyMetadata['multiple']) {
118-
$objectValue = new ObjectIterator($this, $array[$esField], $propertyMetadata);
134+
$objectValue = new ObjectIterator($this, $data, $propertyMetadata);
119135
} else {
120136
$objectValue = $this->assignArrayToObject(
121-
$array[$esField],
137+
$data,
122138
new $propertyMetadata['className'](),
123139
$propertyMetadata['propertiesMetadata']
124140
);

Tests/Functional/Result/DocumentConverterTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Sineflow\ElasticsearchBundle\Tests\Functional\Result;
44

55
use Sineflow\ElasticsearchBundle\Document\MLProperty;
6+
use Sineflow\ElasticsearchBundle\Exception\DocumentConversionException;
67
use Sineflow\ElasticsearchBundle\Tests\AbstractContainerAwareTestCase;
78
use Sineflow\ElasticsearchBundle\Tests\app\fixture\Acme\BarBundle\Document\ObjCategory;
89
use Sineflow\ElasticsearchBundle\Tests\app\fixture\Acme\BarBundle\Document\Product;
@@ -26,6 +27,89 @@ class DocumentConverterTest extends AbstractContainerAwareTestCase
2627
'nonexisting' => 'should be skipped',
2728
];
2829

30+
public function testAssignArrayToObjectWithNestedSingleValueInsteadOfArray()
31+
{
32+
$converter = $this->getContainer()->get('sfes.document_converter');
33+
$metadataCollector = $this->getContainer()->get('sfes.document_metadata_collector');
34+
35+
// Raw doc with a single object value where an array of objects is expected according to metadata def
36+
$rawDoc = [
37+
'_id' => 'rawDocWithSingleObjValueInsteadOfArray',
38+
'related_categories' => [
39+
'id' => '123',
40+
'title' => 'Acme',
41+
],
42+
];
43+
44+
$product = new Product();
45+
$result = $converter->assignArrayToObject(
46+
$rawDoc,
47+
$product,
48+
$metadataCollector->getDocumentMetadata('AcmeBarBundle:Product')->getPropertiesMetadata()
49+
);
50+
$category = $result->relatedCategories->current();
51+
52+
$this->assertEquals($category->id, 123);
53+
}
54+
55+
public function testAssignArrayToObjectWithNestedSingleValueArrayInsteadOfSingleValue()
56+
{
57+
$converter = $this->getContainer()->get('sfes.document_converter');
58+
$metadataCollector = $this->getContainer()->get('sfes.document_metadata_collector');
59+
60+
// Raw doc with array of single object where a single object value is expected according to metadata def
61+
$rawDoc = [
62+
'_id' => 'rawDocWithArrayValueInsteadOfSingleObject',
63+
'category' => [
64+
[
65+
'id' => '123',
66+
'title' => 'Acme',
67+
],
68+
],
69+
];
70+
71+
$product = new Product();
72+
$result = $converter->assignArrayToObject(
73+
$rawDoc,
74+
$product,
75+
$metadataCollector->getDocumentMetadata('AcmeBarBundle:Product')->getPropertiesMetadata()
76+
);
77+
78+
$this->assertEquals($result->category->id, 123);
79+
}
80+
81+
public function testAssignArrayToObjectWithNestedMultiValueArrayInsteadOfSingleValue()
82+
{
83+
$converter = $this->getContainer()->get('sfes.document_converter');
84+
$metadataCollector = $this->getContainer()->get('sfes.document_metadata_collector');
85+
86+
// Raw doc with array of many objects where a single object value is expected according to metadata def
87+
$rawDoc = [
88+
'_id' => 'rawDocWithArrayValueInsteadOfSingleObject',
89+
'category' => [
90+
[
91+
'id' => '123',
92+
'title' => 'Acme',
93+
],
94+
[
95+
'id' => '234',
96+
'title' => 'Ucme',
97+
],
98+
],
99+
];
100+
101+
$product = new Product();
102+
103+
$this->expectException(DocumentConversionException::class);
104+
$this->expectExceptionMessage('Multiple objects found for a single object field `category`');
105+
106+
$converter->assignArrayToObject(
107+
$rawDoc,
108+
$product,
109+
$metadataCollector->getDocumentMetadata('AcmeBarBundle:Product')->getPropertiesMetadata()
110+
);
111+
}
112+
29113
public function testAssignArrayToObjectWithAllFieldsCorrectlySet()
30114
{
31115
$converter = $this->getContainer()->get('sfes.document_converter');

0 commit comments

Comments
 (0)