Completed
Push — master ( 2cb393...81b69d )
by Eric
02:29
created

XmlDeserializationVisitor::navigate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 3
1
<?php
2
3
/*
4
 * This file is part of the Ivory Serializer package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\Serializer\Visitor\Xml;
13
14
use Ivory\Serializer\Context\ContextInterface;
15
use Ivory\Serializer\Instantiator\InstantiatorInterface;
16
use Ivory\Serializer\Mapping\PropertyMetadataInterface;
17
use Ivory\Serializer\Mapping\TypeMetadata;
18
use Ivory\Serializer\Mapping\TypeMetadataInterface;
19
use Ivory\Serializer\Mutator\MutatorInterface;
20
use Ivory\Serializer\Type\Type;
21
use Ivory\Serializer\Visitor\AbstractDeserializationVisitor;
22
23
/**
24
 * @author GeLo <[email protected]>
25
 */
26
class XmlDeserializationVisitor extends AbstractDeserializationVisitor
27
{
28
    /**
29
     * @var string
30
     */
31
    private $entry;
32
33
    /**
34
     * @var string
35
     */
36
    private $entryAttribute;
37
38
    /**
39
     * @param InstantiatorInterface $instantiator
40
     * @param MutatorInterface      $mutator
41
     * @param string                $entry
42
     * @param string                $entryAttribute
43
     */
44
    public function __construct(
45
        InstantiatorInterface $instantiator,
46
        MutatorInterface $mutator,
47
        $entry = 'entry',
48
        $entryAttribute = 'key'
49
    ) {
50
        parent::__construct($instantiator, $mutator);
51
52
        $this->entry = $entry;
53
        $this->entryAttribute = $entryAttribute;
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    protected function decode($data)
60
    {
61
        $internalErrors = libxml_use_internal_errors(true);
62
        $disableEntityLoader = libxml_disable_entity_loader(true);
63
64
        $document = simplexml_load_string($data);
65
66
        libxml_use_internal_errors($internalErrors);
67
        libxml_disable_entity_loader($disableEntityLoader);
68
69
        if ($document === false) {
70
            throw new \InvalidArgumentException(libxml_get_last_error());
71
        }
72
73
        return $document;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
80
    {
81
        $this->result = [];
82
83
        if (isset($data[$this->entry])) {
84
            $entries = $data[$this->entry];
85
            $entries = is_array($entries) ? $entries : [$entries];
86
87
            foreach ($entries as $key => $value) {
88
                $this->visitArrayItem($key, $value, $type, $context);
89
            }
90
        }
91
92
        foreach ($data as $key => $value) {
93
            if ($key !== $this->entry) {
94
                $this->visitArrayItem($key, $value, $type, $context);
95
            }
96
        }
97
98
        return $this->result;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    protected function doVisitObjectProperty(
105
        $data,
106
        $name,
107
        PropertyMetadataInterface $property,
108
        ContextInterface $context
109
    ) {
110
        $data = $this->visitNode($data, new TypeMetadata(Type::ARRAY_));
111
112
        if (!isset($data[$name])) {
113
            return false;
114
        }
115
116
        $data[$name] = $this->visitNode($data[$name], $property->getType());
117
118
        return parent::doVisitObjectProperty($data, $name, $property, $context);
119
    }
120
121
    /**
122
     * @param mixed                 $key
123
     * @param mixed                 $value
124
     * @param TypeMetadataInterface $type
125
     * @param ContextInterface      $context
126
     */
127
    private function visitArrayItem($key, $value, TypeMetadataInterface $type, ContextInterface $context)
128
    {
129
        $keyType = $type->getOption('key');
130
        $valueType = $type->getOption('value');
131
132
        if ($value instanceof \SimpleXMLElement) {
133
            $key = $value->getName();
134
135
            if ($key === $this->entry) {
136
                $attributes = $value->attributes();
137
                $key = isset($attributes[$this->entryAttribute]) ? $attributes[$this->entryAttribute] : null;
138
            }
139
140
            $value = $this->visitNode($value, $valueType);
141
        } elseif ($key === $this->entry) {
142
            $key = null;
143
        }
144
145
        $key = $this->navigator->navigate($this->visitNode($key, $keyType), $context, $keyType);
146
        $result = $this->navigator->navigate($value, $context, $valueType);
147
148
        if ($key === null) {
149
            $this->result[] = $result;
150
        } else {
151
            $this->result[$key] = $result;
152
        }
153
    }
154
155
    /**
156
     * @param mixed                      $data
157
     * @param TypeMetadataInterface|null $type
158
     *
159
     * @return mixed
160
     */
161
    private function visitNode($data, TypeMetadataInterface $type = null)
162
    {
163
        if (!$data instanceof \SimpleXMLElement) {
164
            return $data;
165
        }
166
167
        if (count($data)) {
168
            return (array) $data;
169
        }
170
171
        $data = (string) $data;
172
173
        if ($data !== '') {
174
            return $data;
175
        }
176
177
        if ($type !== null && $type->getName() === Type::ARRAY_) {
178
            return [];
179
        }
180
    }
181
}
182