XmlReader::readerOperation()   B
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 12
cts 12
cp 1
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 12
nc 2
nop 3
crap 2
1
<?php
2
3
namespace TreeHouse\Feeder\Reader;
4
5
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
6
use Symfony\Component\HttpFoundation\ParameterBag;
7
use Symfony\Component\Serializer\Encoder\XmlEncoder;
8
use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
9
use Symfony\Component\Serializer\Serializer;
10
use TreeHouse\Feeder\Exception\ReadException;
11
use TreeHouse\Feeder\Resource\ResourceInterface;
12
13
class XmlReader extends AbstractReader
14
{
15
    /**
16
     * @var \XMLReader
17
     */
18
    protected $reader;
19
20
    /**
21
     * @var callable
22
     */
23
    protected $nextNode;
24
25
    /**
26
     * @var Serializer
27
     */
28
    protected $serializer;
29
30
    /**
31
     * @var int
32
     */
33
    protected $key;
34
35
    /**
36
     * @param mixed                    $resources  Optional resource collection. Can be a Resource, an array of
37
     *                                             Resource's, or a ResourceCollection. When empty, a new collection
38
     *                                             will be created.
39
     * @param EventDispatcherInterface $dispatcher Optional event dispatcher.
40
     */
41 52
    public function __construct($resources = null, EventDispatcherInterface $dispatcher = null)
42
    {
43 52
        parent::__construct($resources, $dispatcher);
44
45 52
        $this->serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]);
46 52
    }
47
48
    /**
49
     * @param mixed $nextNode Callback to get the next node from the current resource. Can be a callback or a node name.
50
     *
51
     * @throws \InvalidArgumentException
52
     *
53
     * @return callable
54
     */
55 52
    public function setNodeCallback($nextNode)
56 2
    {
57 52
        if ($nextNode instanceof \Closure) {
58
            return $this->nextNode = $nextNode;
59
        }
60
61 52
        if (!is_string($nextNode)) {
62
            throw new \InvalidArgumentException('Expecting a string of callback for nextNode');
63
        }
64
65 52
        $nodeName = mb_strtolower($nextNode);
66
67 52
        return $this->nextNode = function (\XMLReader $reader) use ($nodeName) {
68 30
            while ($this->readerOperation($reader, 'read')) {
69
                // stop if we found our node
70 24
                if (($reader->nodeType === \XMLReader::ELEMENT) && (mb_strtolower($reader->name) === $nodeName)) {
71 24
                    return true;
72
                }
73 24
            }
74
75 22
            return false;
76 52
        };
77
    }
78
79
    /**
80
     * @inheritdoc
81
     */
82
    protected function doKey()
83
    {
84
        return $this->key;
85
    }
86
87
    /**
88
     * @inheritdoc
89
     */
90 24
    protected function doCurrent()
91
    {
92 24
        return $this->readerOperation($this->reader, 'readOuterXml');
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98 30
    protected function doNext()
99
    {
100 30
        $this->moveToNextNode($this->reader);
101 24
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106
    protected function doRewind()
107
    {
108
        $this->reader->close();
109
        $this->open($this->resource->getFile()->getPathname());
110
111
        $this->key = -1;
112
113
        $this->next();
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119 24
    protected function doValid()
120
    {
121 24
        return (boolean) $this->doCurrent();
122
    }
123
124
    /**
125
     * @param \XMLReader $reader
126
     *
127
     * @throws \LogicException
128
     *
129
     * @return mixed
130
     */
131 30
    protected function moveToNextNode(\XMLReader $reader)
132
    {
133 30
        if (!$this->nextNode instanceof \Closure) {
134
            throw new \LogicException('No callback set to get next node');
135
        }
136
137 30
        ++$this->key;
138
139 30
        return call_user_func($this->nextNode, $reader);
140
    }
141
142
    /**
143
     * @inheritdoc
144
     */
145 30
    protected function createReader(ResourceInterface $resource)
146
    {
147 30
        $this->reader = new \XmlReader();
148 30
        $this->open($resource->getFile()->getPathname());
149
150 30
        $this->key = -1;
151 30
        $this->next();
152 24
    }
153
154
    /**
155
     * @inheritdoc
156
     */
157 22
    protected function serialize($data)
158
    {
159 22
        return new ParameterBag((array) $this->serializer->decode($data, 'xml'));
160
    }
161
162
    /**
163
     * @param string $file
164
     * @param int    $options
165
     */
166 30
    protected function open($file, $options = null)
167
    {
168 30
        if (is_null($options)) {
169 30
            $options = LIBXML_NOENT | LIBXML_NONET | LIBXML_COMPACT | LIBXML_PARSEHUGE | LIBXML_NOERROR | LIBXML_NOWARNING;
170 30
        }
171
172 30
        $this->reader->open($file, null, $options);
173 30
    }
174
175
    /**
176
     * @return string
177
     */
178 30
    private function getXmlError()
179
    {
180
        // just return the first error
181 30
        if ($error = libxml_get_last_error()) {
182 6
            return sprintf('[%s %s] %s (in %s - line %d, column %d)',
183 6
                LIBXML_ERR_WARNING === $error->level ? 'WARNING' : 'ERROR',
184 6
                $error->code,
185 6
                trim($error->message),
186 6
                $error->file ? $error->file : 'n/a',
187 6
                $error->line,
188 6
                $error->column
189 6
            );
190
        }
191
192 24
        return null;
193
    }
194
195
    /**
196
     * @param \XmlReader $reader
197
     * @param string     $method
198
     * @param array      $args
199
     *
200
     * @throws ReadException
201
     *
202
     * @return mixed
203
     */
204 30
    private function readerOperation(\XmlReader $reader, $method, array $args = [])
205
    {
206
        // clear any previous errors
207 30
        libxml_clear_errors();
208
209
        // remember current settings
210 30
        $errors = libxml_use_internal_errors(true);
211 30
        $entities = libxml_disable_entity_loader(true);
212
213
        // perform the operation
214 30
        $retval = call_user_func_array([$reader, $method], $args);
215
216
        // get the last error, if any
217 30
        $error = $this->getXmlError();
218
219
        // reset everything, clear the error buffer again
220 30
        libxml_clear_errors();
221 30
        libxml_use_internal_errors($errors);
222 30
        libxml_disable_entity_loader($entities);
223
224 30
        if (!empty($error)) {
225 6
            throw new ReadException($error);
226
        }
227
228 24
        return $retval;
229
    }
230
}
231