XmlBuilder::getEntityFromCache()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 10
loc 10
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
/**
3
 * @link    https://github.com/nnx-framework/jms-serializer-module
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace Nnx\JmsSerializerModule\DataContainerBuilder;
7
8
use Nnx\JmsSerializerModule\DataContainer;
9
use Ramsey\Uuid\Uuid;
10
use SimpleXMLElement;
11
12
13
14
/**
15
 * Class ManagerRegistryFactory
16
 *
17
 * @package Nnx\JmsSerializerModule\Util
18
 */
19
class XmlBuilder implements XmlBuilderInterface
20
{
21
    /**
22
     * Имя корневого тега по умолчанию
23
     *
24
     * @var string
25
     */
26
    protected $defaultRootName = 'result';
27
28
    /**
29
     * Имя тега являющегося контейнером для сущности
30
     *
31
     * @var string
32
     */
33
    protected $xmlEntryName = 'entry';
34
35
    /**
36
     * Xpath запрос для получения вложенных сущностей
37
     *
38
     * @var string
39
     */
40
    protected $childEntryQuery;
41
42
    /**
43
     * Ключем является хеш SimpleXMLElement, а значением закешированные данные
44
     *
45
     * @var array
46
     */
47
    protected $itemNodeUuidToCacheData = [];
48
49
    /**
50
     * Имя атрибута для того что бы связать SimpleXMLElement и контейнер с данными
51
     *
52
     * @var string
53
     */
54
    protected $defaultUuidAttribute = '____UUID____';
55
56
    /**
57
     * Определяет есть ли готовые данные для данного элемента
58
     *
59
     * @param SimpleXMLElement $resource
60
     *
61
     * @return boolean
62
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
63
     */
64
    public function hasDataInCache(SimpleXMLElement $resource)
65
    {
66
        if ($this->hasUuidAttribute($resource)) {
67
            $uuid = $this->getUuidAttribute($resource);
68
            return array_key_exists($uuid, $this->itemNodeUuidToCacheData);
69
        }
70
        return false;
71
    }
72
73
    /**
74
     * Возвращает контейнер с данными из кеша
75
     *
76
     * @param SimpleXMLElement $resource
77
     *
78
     * @return DataContainer\DataContainerInterface
79
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
80
     */
81 View Code Duplication
    public function getDataContainerFromCache(SimpleXMLElement $resource)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
82
    {
83
        if (!$this->hasDataInCache($resource)) {
84
            $errMsg = 'Data container not found';
85
            throw new Exception\RuntimeException($errMsg);
86
        }
87
88
        $uuid = $this->getUuidAttribute($resource);
89
        return $this->itemNodeUuidToCacheData[$uuid]['dataContainer'];
90
    }
91
92
    /**
93
     * Возвращает контейнер с данными для сущности
94
     *
95
     * @param SimpleXMLElement $resource
96
     *
97
     * @return DataContainer\EntityInterface
98
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
99
     */
100 View Code Duplication
    public function getEntityFromCache(SimpleXMLElement $resource)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
    {
102
        if (!$this->hasDataInCache($resource)) {
103
            $errMsg = 'Entity not found';
104
            throw new Exception\RuntimeException($errMsg);
105
        }
106
107
        $uuid = $this->getUuidAttribute($resource);
108
        return $this->itemNodeUuidToCacheData[$uuid]['entity'];
109
    }
110
111
    /**
112
     * Подготавливает нормализованный контейнер с данными на основе узла SimpleXMLElement
113
     *
114
     * @param SimpleXMLElement $resource
115
     *
116
     * @return DataContainer\DataContainerInterface
117
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
118
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\XmlBuilder\Exception\InvalidParserContextException
119
     * @throws \Nnx\JmsSerializerModule\DataContainer\Exception\InvalidArgumentException
120
     * @throws Exception\InvalidResourceException
121
     */
122
    public function loadDataFromResource(SimpleXMLElement $resource)
123
    {
124
        if ($this->hasUuidAttribute($resource)) {
125
            $uuid = $this->getUuidAttribute($resource);
126
            if (array_key_exists($uuid, $this->itemNodeUuidToCacheData)) {
127
                return $this->itemNodeUuidToCacheData[$uuid]['dataContainer'];
128
            }
129
        }
130
131
        $query = sprintf('/descendant-or-self::%s/%s', $this->getDefaultRootName(), $this->getXmlEntryName());
132
        $itemNodes = $resource->xpath($query);
133
134
        if (0 === count($itemNodes)) {
135
            $itemNodes = $resource->xpath('.');
136
        }
137
138
139
        $index = new DataContainer\Index();
140
        $dataContainer = new DataContainer\DataContainer($index);
141
142
        $context = new XmlBuilder\ParserContext();
143
        $context
144
            ->setIndex($index)
145
            ->setItemNodes($itemNodes)
146
            ->setDataContainer($dataContainer);
147
148
        $this->parseItem($context);
149
150
        return $dataContainer;
151
    }
152
153
154
    /**
155
     * Обработка набора узлов xml документа, в которых описываются данные для сущности
156
     *
157
     * @param XmlBuilder\ParserContext $context
158
     *
159
     * @return DataContainer\DataContainerInterface
160
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
161
     * @throws \Nnx\JmsSerializerModule\DataContainer\Exception\InvalidArgumentException
162
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\XmlBuilder\Exception\InvalidParserContextException
163
     *
164
     */
165
    protected function parseItem(XmlBuilder\ParserContext $context)
166
    {
167
        $context->validate();
168
169
        $itemNodes = $context->getItemNodes();
170
        $level = $context->getLevel();
171
        $parentEntity = $context->getParentEntity();
172
        $parentAssociationName = $context->getParentAssociation();
173
        $dataContainer = $context->getDataContainer();
174
        $index = $context->getIndex();
175
176
177
        foreach ($itemNodes as $itemNode) {
178
            $properties = $itemNode->xpath('./*');
179
180
            if (0 === count($properties)) {
181
                continue;
182
            }
183
184
            $entity = new DataContainer\Entity();
185
            $entity->setLevel($level);
186
187
188
            if (!$this->hasUuidAttribute($itemNode)) {
189
                $this->generateUuid($itemNode);
190
            }
191
            $currentUuid = $this->getUuidAttribute($itemNode);
192
193
            if (!array_key_exists($currentUuid, $this->itemNodeUuidToCacheData)) {
194
                $this->itemNodeUuidToCacheData[$currentUuid] = [
195
                    'entity' => $entity,
196
                    'dataContainer' => $dataContainer
197
                ];
198
            }
199
200
201
            if (null !== $parentEntity) {
202
                $entity->setParentEntity($parentEntity);
203
204
                if (!$parentEntity->hasAssociation($parentAssociationName)) {
205
                    $association = new DataContainer\Association($index);
206
                    $association->setName($parentAssociationName);
207
                    $parentEntity->addAssociation($association);
208
                } else {
209
                    $association = $parentEntity->getAssociation($parentAssociationName);
210
                }
211
212
                $association->addEntity($entity);
213
            } else {
214
                $dataContainer->addEntity($entity);
215
            }
216
217
            $existingProperties = [];
218
            foreach ($properties as $property) {
219
                $propertyName = $property->getName();
220
221
                if (array_key_exists($propertyName, $existingProperties)) {
222
                    $errMsg = sprintf('Property %s already exists', $propertyName);
223
                    throw new Exception\RuntimeException($errMsg);
224
                }
225
                $existingProperties[$propertyName] = $propertyName;
226
227
                $childElements = $property->xpath('./*');
228
229
                if (count($childElements) > 0) {
230
                    $childEntryCollection = $property->xpath($this->getChildEntryQuery());
231
                    if (count($childEntryCollection) > 0) {
232
                        $childItems = $childEntryCollection;
233
                    } else {
234
                        $childItems = $property->xpath('.');
235
                    }
236
237
                    $childLevel = $level + 1;
238
                    $newContext = new XmlBuilder\ParserContext();
239
                    $newContext
240
                        ->setItemNodes($childItems)
241
                        ->setParentEntity($entity)
242
                        ->setLevel($childLevel)
243
                        ->setParentAssociation($propertyName)
244
                        ->setDataContainer($context->getDataContainer())
245
                        ->setIndex($context->getIndex());
246
247
                    $this->parseItem($newContext);
248
                } else {
249
                    $propertyValue = (string)$property;
250
                    $property = new DataContainer\Property();
251
                    $property
252
                        ->setName($propertyName)
253
                        ->setValue($propertyValue)
254
                    ;
255
                    $entity->addProperty($property);
256
                }
257
            }
258
        }
259
    }
260
261
    /**
262
     * Определяет есть ли uuid атрибут у элемента
263
     *
264
     * @param SimpleXMLElement $itemNode
265
     *
266
     * @return bool
267
     */
268
    public function hasUuidAttribute(SimpleXMLElement $itemNode)
269
    {
270
        $uuidAttribute = $this->getDefaultUuidAttribute();
271
        $attributesItemNode = $itemNode->attributes();
272
        return isset($attributesItemNode[$uuidAttribute]);
273
    }
274
275
276
    /**
277
     * Определяет есть ли uuid атрибут у элемента
278
     *
279
     * @param SimpleXMLElement $itemNode
280
     *
281
     * @return string
282
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
283
     */
284
    public function getUuidAttribute(SimpleXMLElement $itemNode)
285
    {
286
        if (!$this->hasUuidAttribute($itemNode)) {
287
            $errMsg = sprintf('Uuid attribute not found in %s', $itemNode->getName());
288
            throw new Exception\RuntimeException($errMsg);
289
        }
290
291
        $uuidAttribute = $this->getDefaultUuidAttribute();
292
        return (string)$itemNode[$uuidAttribute];
293
    }
294
295
    /**
296
     * Генерирует uuid атррибут
297
     *
298
     * @param SimpleXMLElement $itemNode
299
     *
300
     * @return SimpleXMLElement
301
     * @throws \Nnx\JmsSerializerModule\DataContainerBuilder\Exception\RuntimeException
302
     */
303
    public function generateUuid(SimpleXMLElement $itemNode)
304
    {
305
        if ($this->hasUuidAttribute($itemNode)) {
306
            $errMsg = sprintf('Uuid attribute already exists %s', $itemNode->getName());
307
            throw new Exception\RuntimeException($errMsg);
308
        }
309
310
        $uuidAttribute = $this->getDefaultUuidAttribute();
311
        $uuid = Uuid::uuid4()->toString();
312
        $itemNode->addAttribute($uuidAttribute, $uuid);
313
314
        return $itemNode;
315
    }
316
317
    /**
318
     * Возвращает имя корневого тега по умолчанию
319
     *
320
     * @return string
321
     */
322
    public function getDefaultRootName()
323
    {
324
        return $this->defaultRootName;
325
    }
326
327
    /**
328
     * Устанавливает имя корневого тега по умолчанию
329
     *
330
     * @param string $defaultRootName
331
     *
332
     * @return $this
333
     */
334
    public function setDefaultRootName($defaultRootName)
335
    {
336
        $this->defaultRootName = $defaultRootName;
337
338
        return $this;
339
    }
340
341
    /**
342
     * Возвращает имя тега являющегося контейнером для сущности
343
     *
344
     * @return string
345
     */
346
    public function getXmlEntryName()
347
    {
348
        return $this->xmlEntryName;
349
    }
350
351
    /**
352
     * Устанавливает имя тега являющегося контейнером для сущности
353
     *
354
     * @param string $xmlEntryName
355
     *
356
     * @return $this
357
     */
358
    public function setXmlEntryName($xmlEntryName)
359
    {
360
        $this->xmlEntryName = $xmlEntryName;
361
362
        return $this;
363
    }
364
365
    /**
366
     * Возвращает  Xpath запрос для получения вложенных сущностей
367
     *
368
     * @return string
369
     */
370
    public function getChildEntryQuery()
371
    {
372
        if (null === $this->childEntryQuery) {
373
            $this->childEntryQuery = sprintf('./%s', $this->getXmlEntryName());
374
        }
375
376
        return $this->childEntryQuery;
377
    }
378
379
    /**
380
     * Возвращает имя атрибута для того что бы связать SimpleXMLElement и контейнер с данными
381
     *
382
     * @return string
383
     */
384
    public function getDefaultUuidAttribute()
385
    {
386
        return $this->defaultUuidAttribute;
387
    }
388
389
    /**
390
     * Устанавливает имя атрибута для того что бы связать SimpleXMLElement и контейнер с данными
391
     *
392
     * @param string $defaultUuidAttribute
393
     *
394
     * @return $this
395
     */
396
    public function setDefaultUuidAttribute($defaultUuidAttribute)
397
    {
398
        $this->defaultUuidAttribute = $defaultUuidAttribute;
399
400
        return $this;
401
    }
402
}
403