Completed
Push — v0.10 ( f42b97...309689 )
by Simonas
8s
created

Converter::assignArrayToObject()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 34
rs 4.909
cc 9
eloc 20
nc 11
nop 3
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\ElasticsearchBundle\Result;
13
14
use ONGR\ElasticsearchBundle\Document\DocumentInterface;
15
use ONGR\ElasticsearchBundle\Mapping\ClassMetadata;
16
use ONGR\ElasticsearchBundle\Mapping\Proxy\ProxyInterface;
17
use Symfony\Component\PropertyAccess\PropertyAccess;
18
use Symfony\Component\PropertyAccess\PropertyAccessor;
19
20
/**
21
 * This class converts array to document object.
22
 */
23
class Converter
24
{
25
    /**
26
     * @var array
27
     */
28
    private $typesMapping;
29
30
    /**
31
     * @var array
32
     */
33
    private $bundlesMapping;
34
35
    /**
36
     * @var PropertyAccessor
37
     */
38
    private $propertyAccessor;
39
40
    /**
41
     * Constructor.
42
     *
43
     * @param array $typesMapping
44
     * @param array $bundlesMapping
45
     */
46
    public function __construct($typesMapping, $bundlesMapping)
47
    {
48
        $this->typesMapping = $typesMapping;
49
        $this->bundlesMapping = $bundlesMapping;
50
    }
51
52
    /**
53
     * Converts raw array to document.
54
     *
55
     * @param array $rawData
56
     *
57
     * @return DocumentInterface
58
     *
59
     * @throws \LogicException
60
     */
61
    public function convertToDocument($rawData)
62
    {
63
        if (!isset($this->typesMapping[$rawData['_type']])) {
64
            throw new \LogicException("Got document of unknown type '{$rawData['_type']}'.");
65
        }
66
67
        /** @var ClassMetadata $metadata */
68
        $metadata = $this->bundlesMapping[$this->typesMapping[$rawData['_type']]];
69
        $data = isset($rawData['_source']) ? $rawData['_source'] : array_map('reset', $rawData['fields']);
70
        $proxy = $metadata->getProxyNamespace();
71
72
        /** @var DocumentInterface $object */
73
        $object = $this->assignArrayToObject($data, new $proxy(), $metadata->getAliases());
74
75
        if ($object instanceof ProxyInterface) {
76
            $object->__setInitialized(true);
77
        }
78
79
        $this->setObjectFields($object, $rawData, ['_id', '_score', 'highlight', 'fields _parent', 'fields _ttl']);
80
81
        return $object;
82
    }
83
84
    /**
85
     * Assigns all properties to object.
86
     *
87
     * @param array  $array
88
     * @param object $object
89
     * @param array  $aliases
90
     *
91
     * @return object
92
     */
93
    public function assignArrayToObject(array $array, $object, array $aliases)
94
    {
95
        foreach ($array as $name => $value) {
96
            if (!array_key_exists($name, $aliases) || $value === null) {
97
                $object->{$name} = $value;
98
                continue;
99
            }
100
101
            if ($aliases[$name]['type'] === 'date') {
102
                $newValue = \DateTime::createFromFormat(
103
                    isset($aliases[$name]['format']) ? $aliases[$name]['format'] : \DateTime::ISO8601,
104
                    $value
105
                );
106
                
107
                $value = $newValue === false ? $value : $newValue;
108
            }
109
110
            if (array_key_exists('aliases', $aliases[$name])) {
111
                if ($aliases[$name]['multiple']) {
112
                    $value = new ObjectIterator($this, $value, $aliases[$name]);
113
                } else {
114
                    $value = $this->assignArrayToObject(
115
                        $value,
116
                        new $aliases[$name]['proxyNamespace'](),
117
                        $aliases[$name]['aliases']
118
                    );
119
                }
120
            }
121
122
            $this->getPropertyAccessor()->setValue($object, $aliases[$name]['propertyName'], $value);
123
        }
124
125
        return $object;
126
    }
127
128
    /**
129
     * Converts object to an array.
130
     *
131
     * @param DocumentInterface $object
132
     * @param array             $aliases
133
     *
134
     * @return array
135
     */
136
    public function convertToArray($object, $aliases = [])
137
    {
138
        if (empty($aliases)) {
139
            $aliases = $this->getAlias($object);
140
        }
141
142
        $array = [];
143
        // Special fields.
144
        if ($object instanceof DocumentInterface) {
145
            $this->setArrayFields($array, $object, ['_id', '_parent', '_ttl']);
146
        }
147
148
        // Variable $name defined in client.
149
        foreach ($aliases as $name => $alias) {
150
            $value = $this->getPropertyAccessor()->getValue($object, $alias['propertyName']);
151
152
            if (isset($value)) {
153
                if (array_key_exists('aliases', $alias)) {
154
                    $new = [];
155
                    if ($alias['multiple']) {
156
                        $this->isTraversable($value);
157
                        foreach ($value as $item) {
158
                            $this->checkVariableType($item, [$alias['namespace'], $alias['proxyNamespace']]);
159
                            $new[] = $this->convertToArray($item, $alias['aliases']);
160
                        }
161
                    } else {
162
                        $this->checkVariableType($value, [$alias['namespace'], $alias['proxyNamespace']]);
163
                        $new = $this->convertToArray($value, $alias['aliases']);
164
                    }
165
                    $value = $new;
166
                }
167
168
                if ($value instanceof \DateTime) {
169
                    $value = $value->format(isset($alias['format']) ? $alias['format'] : \DateTime::ISO8601);
170
                }
171
172
                $array[$name] = $value;
173
            }
174
        }
175
176
        return $array;
177
    }
178
179
    /**
180
     * Sets fields into object from raw response.
181
     *
182
     * @param object $object      Object to set values to.
183
     * @param array  $rawResponse Array to take values from.
184
     * @param array  $fields      Values to take.
185
     */
186
    private function setObjectFields($object, $rawResponse, $fields = [])
187
    {
188
        foreach ($fields as $field) {
189
            $path = $this->getPropertyPathToAccess($field);
190
            $value = $this->getPropertyAccessor()->getValue($rawResponse, $path);
191
192
            if ($value !== null) {
193
                if (strpos($path, 'highlight') !== false) {
194
                    $value = new DocumentHighlight($value);
195
                }
196
197
                $this->getPropertyAccessor()->setValue($object, $this->getPropertyToAccess($field), $value);
198
            }
199
        }
200
    }
201
202
    /**
203
     * Sets fields into array from object.
204
     *
205
     * @param array  $array  To set values to.
206
     * @param object $object Take values from.
207
     * @param array  $fields Fields to set.
208
     */
209
    private function setArrayFields(&$array, $object, $fields = [])
210
    {
211
        foreach ($fields as $field) {
212
            $value = $this->getPropertyAccessor()->getValue($object, $this->getPropertyToAccess($field));
213
214
            if ($value !== null) {
215
                $this
216
                    ->getPropertyAccessor()
217
                    ->setValue($array, $this->getPropertyPathToAccess($field), $value);
218
            }
219
        }
220
    }
221
222
    /**
223
     * Returns property to access for object used by property accessor.
224
     *
225
     * @param string $field
226
     *
227
     * @return string
228
     */
229
    private function getPropertyToAccess($field)
230
    {
231
        $deep = strpos($field, ' ');
232
        if ($deep !== false) {
233
            $field = substr($field, $deep + 1);
234
        }
235
236
        return $field;
237
    }
238
239
    /**
240
     * Returns property to access for array used by property accessor.
241
     *
242
     * @param string $field
243
     *
244
     * @return string
245
     */
246
    private function getPropertyPathToAccess($field)
247
    {
248
        return '[' . str_replace(' ', '][', $field) . ']';
249
    }
250
251
    /**
252
     * Check if class matches the expected one.
253
     *
254
     * @param object $object
255
     * @param array  $expectedClasses
256
     *
257
     * @throws \InvalidArgumentException
258
     */
259
    private function checkVariableType($object, array $expectedClasses)
260
    {
261
        if (!is_object($object)) {
262
            $msg = 'Expected variable of type object, got ' . gettype($object) . ". (field isn't multiple)";
263
            throw new \InvalidArgumentException($msg);
264
        }
265
266
        $class = get_class($object);
267
        if (!in_array($class, $expectedClasses)) {
268
            throw new \InvalidArgumentException("Expected object of type {$expectedClasses[0]}, got {$class}.");
269
        }
270
    }
271
272
    /**
273
     * Check if object is traversable, throw exception otherwise.
274
     *
275
     * @param mixed $value
276
     *
277
     * @return bool
278
     *
279
     * @throws \InvalidArgumentException
280
     */
281
    private function isTraversable($value)
282
    {
283
        if (!(is_array($value) || (is_object($value) && $value instanceof \Traversable))) {
284
            throw new \InvalidArgumentException("Variable isn't traversable, although field is set to multiple.");
285
        }
286
287
        return true;
288
    }
289
290
    /**
291
     * Returns aliases for certain document.
292
     *
293
     * @param DocumentInterface $document
294
     *
295
     * @return array
296
     *
297
     * @throws \DomainException
298
     */
299
    private function getAlias($document)
300
    {
301
        $class = get_class($document);
302
303
        foreach ($this->bundlesMapping as $repository) {
304
            if (in_array($class, [$repository->getNamespace(), $repository->getProxyNamespace()])) {
305
                return $repository->getAliases();
306
            }
307
        }
308
309
        throw new \DomainException("Aliases could not be found for {$class} document.");
310
    }
311
312
    /**
313
     * Returns property accessor instance.
314
     *
315
     * @return PropertyAccessor
316
     */
317
    private function getPropertyAccessor()
318
    {
319
        if (!$this->propertyAccessor) {
320
            $this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
321
                ->enableMagicCall()
322
                ->getPropertyAccessor();
323
        }
324
325
        return $this->propertyAccessor;
326
    }
327
}
328