Completed
Pull Request — master (#1576)
by Chad
06:28
created

RestXmlCollectionLoader::getOptions()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
ccs 14
cts 14
cp 1
rs 9.2
cc 4
eloc 11
nc 3
nop 1
crap 4
1
<?php
2
3
/*
4
 * This file is part of the FOSRestBundle package.
5
 *
6
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
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 FOS\RestBundle\Routing\Loader;
13
14
use FOS\RestBundle\Routing\RestRouteCollection;
15
use Symfony\Component\Config\FileLocatorInterface;
16
use Symfony\Component\Config\Util\XmlUtils;
17
use Symfony\Component\Routing\Loader\XmlFileLoader;
18
use Symfony\Component\Routing\RouteCollection;
19
20
/**
21
 * RestXmlCollectionLoader XML file collections loader.
22
 *
23
 * @author Donald Tyler <[email protected]>
24
 */
25
class RestXmlCollectionLoader extends XmlFileLoader
26
{
27
    protected $collectionParents = [];
28
    private $processor;
29
    private $includeFormat;
30
    private $formats;
31
    private $defaultFormat;
32
33
    /**
34
     * Initializes xml loader.
35
     *
36
     * @param FileLocatorInterface $locator
37
     * @param RestRouteProcessor   $processor
38
     * @param bool                 $includeFormat
39
     * @param string[]             $formats
40
     * @param string               $defaultFormat
41
     */
42 22 View Code Duplication
    public function __construct(
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...
43
        FileLocatorInterface $locator,
44
        RestRouteProcessor $processor,
45
        $includeFormat = true,
46
        array $formats = [],
47
        $defaultFormat = null
48
    ) {
49 22
        parent::__construct($locator);
50
51 22
        $this->processor = $processor;
52 22
        $this->includeFormat = $includeFormat;
53 22
        $this->formats = $formats;
54 22
        $this->defaultFormat = $defaultFormat;
55 22
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 9
    protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file)
61
    {
62 9
        switch ($node->tagName) {
63 9
            case 'route':
64 6
                $this->parseRoute($collection, $node, $path);
65 6
                break;
66 3
            case 'import':
67 3
                $name = (string) $node->getAttribute('id');
68 3
                $resource = (string) $node->getAttribute('resource');
69 3
                $prefix = (string) $node->getAttribute('prefix');
70 3
                $namePrefix = (string) $node->getAttribute('name-prefix');
71 3
                $parent = (string) $node->getAttribute('parent');
72 3
                $type = (string) $node->getAttribute('type');
73 3
                $host = isset($config['host']) ? $config['host'] : null;
0 ignored issues
show
Bug introduced by
The variable $config seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
74 3
                $currentDir = dirname($path);
75
76 3
                $parents = [];
77 3 View Code Duplication
                if (!empty($parent)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
78 3
                    if (!isset($this->collectionParents[$parent])) {
79 1
                        throw new \InvalidArgumentException(sprintf('Cannot find parent resource with name %s', $parent));
80
                    }
81
82 2
                    $parents = $this->collectionParents[$parent];
83 2
                }
84
85 2
                $imported = $this->processor->importResource($this, $resource, $parents, $prefix, $namePrefix, $type, $currentDir);
86
87 2
                if (!empty($name) && $imported instanceof RestRouteCollection) {
88 2
                    $parents[] = (!empty($prefix) ? $prefix.'/' : '').$imported->getSingularName();
89 2
                    $prefix = null;
90
91 2
                    $this->collectionParents[$name] = $parents;
92 2
                }
93
94 2
                if (!empty($host)) {
95
                    $imported->setHost($host);
96
                }
97
98 2
                $imported->addPrefix($prefix);
99 2
                $collection->addCollection($imported);
100 2
                break;
101
            default:
102
                throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
103 8
        }
104 8
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 6
    protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path)
110
    {
111 6
        if ($this->includeFormat) {
112 5
            $path = $node->getAttribute('path');
113
            // append format placeholder if not present
114 5
            if (false === strpos($path, '{_format}')) {
115 5
                $node->setAttribute('path', $path.'.{_format}');
116 5
            }
117
118
            // set format requirement if configured globally
119 5
            $requirements = $node->getElementsByTagNameNS(self::NAMESPACE_URI, 'requirement');
120 5
            $format = null;
121 5
            for ($i = 0; $i < $requirements->length; ++$i) {
122
                $item = $requirements->item($i);
123
                if ($item instanceof \DOMElement && $item->hasAttribute('_format')) {
124
                    $format = $item->getAttribute('_format');
125
                    break;
126
                }
127
            }
128
129 5
            if (null === $format && !empty($this->formats)) {
130 5
                $requirement = $node->ownerDocument->createElementNs(
131 5
                    self::NAMESPACE_URI,
132 5
                    'requirement',
133 5
                    implode('|', array_keys($this->formats))
134 5
                );
135 5
                $requirement->setAttribute('key', '_format');
136 5
                $node->appendChild($requirement);
137 5
            }
138 5
        }
139
140
        // set the default format if configured
141 6
        if (null !== $this->defaultFormat) {
142 1
            $defaultFormatNode = $node->ownerDocument->createElementNS(
143 1
                self::NAMESPACE_URI,
144 1
                'default',
145 1
                $this->defaultFormat
146 1
            );
147 1
            $defaultFormatNode->setAttribute('key', '_format');
148 1
            $node->appendChild($defaultFormatNode);
149 1
        }
150
151 6
        $options = $this->getOptions($node);
152
153 6
        foreach ($options as $option) {
154 1
            $node->appendChild($option);
155 6
        }
156
157 6
        $length = $node->childNodes->length;
158 6
        for ($i = 0; $i < $length; ++$i) {
159 6
            $loopNode = $node->childNodes->item($i);
160 6
            if ($loopNode->nodeType === XML_TEXT_NODE) {
161 6
                continue;
162
            }
163
164 6
            $newNode = $node->ownerDocument->createElementNS(
165 6
                self::NAMESPACE_URI,
166 6
                $loopNode->nodeName,
167 6
                $loopNode->nodeValue
168 6
            );
169
170 6
            foreach ($loopNode->attributes as $value) {
171 6
                $newNode->setAttribute($value->name, $value->value);
172 6
            }
173
174 6
            $node->appendChild($newNode);
175 6
        }
176
177 6
        parent::parseRoute($collection, $node, $path);
178 6
    }
179
180 6
    private function getOptions(\DOMElement $node)
181
    {
182 6
        $options = [];
183 6
        foreach ($node->childNodes as $child) {
184 6
            if ($child instanceof \DOMElement && $child->tagName === 'option') {
185 1
                $option = $node->ownerDocument->createElementNs(
186 1
                    self::NAMESPACE_URI,
187 1
                    'option',
188 1
                    $child->nodeValue
189 1
                );
190 1
                $option->setAttribute('key', $child->getAttribute('key'));
191 1
                $options[] = $option;
192 1
            }
193 6
        }
194
195 6
        return $options;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 2
    public function supports($resource, $type = null)
202
    {
203 2
        return is_string($resource) &&
204 2
            'xml' === pathinfo($resource, PATHINFO_EXTENSION) &&
205 2
            'rest' === $type;
206
    }
207
208
    /**
209
     * @param \DOMDocument $dom
210
     *
211
     * @throws \InvalidArgumentException When xml doesn't validate its xsd schema
212
     */
213 10
    protected function validate(\DOMDocument $dom)
214
    {
215 10
        $restRoutinglocation = realpath(__DIR__.'/../../Resources/config/schema/routing/rest_routing-1.0.xsd');
216 10
        $restRoutinglocation = rawurlencode(str_replace('\\', '/', $restRoutinglocation));
217 10
        $routinglocation = realpath(__DIR__.'/../../Resources/config/schema/routing-1.0.xsd');
218 10
        $routinglocation = rawurlencode(str_replace('\\', '/', $routinglocation));
219
        $source = <<<EOF
220
<?xml version="1.0" encoding="utf-8" ?>
221
<xsd:schema xmlns="http://symfony.com/schema"
222
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
223
    targetNamespace="http://symfony.com/schema"
224
    elementFormDefault="qualified">
225
226
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
227
    <xsd:import namespace="http://friendsofsymfony.github.com/schema/rest" schemaLocation="$restRoutinglocation" />
228 10
    <xsd:import namespace="http://symfony.com/schema/routing" schemaLocation="$routinglocation" />
229 10
</xsd:schema>
230 10
EOF;
231
232 10
        $current = libxml_use_internal_errors(true);
233 10
        libxml_clear_errors();
234
235 10
        if (!$dom->schemaValidateSource($source)) {
236 1
            throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors_($current)));
237
        }
238 9
        libxml_use_internal_errors($current);
239 9
    }
240
241
    /**
242
     * {@inheritdoc}
243
     *
244
     * @internal
245
     */
246 10
    protected function loadFile($file)
247
    {
248 10
        if (class_exists('Symfony\Component\Config\Util\XmlUtils')) {
249 10
            $dom = XmlUtils::loadFile($file);
250 10
            $this->validate($dom);
251
252 9
            return $dom;
253
        }
254
255
        return parent::loadFile($file);
256
    }
257
258
    /**
259
     * Retrieves libxml errors and clears them.
260
     *
261
     * Note: The underscore postfix on the method name is to ensure compatibility with versions
262
     *       before 2.0.16 while working around a bug in PHP https://bugs.php.net/bug.php?id=62956
263
     *
264
     * @param bool $internalErrors The previous state of internal errors to reset it
265
     *
266
     * @return array An array of libxml error strings
267
     */
268 1
    private function getXmlErrors_($internalErrors)
269
    {
270 1
        $errors = [];
271 1
        foreach (libxml_get_errors() as $error) {
272 1
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
273 1
                LIBXML_ERR_WARNING === $error->level ? 'WARNING' : 'ERROR',
274 1
                $error->code,
275 1
                trim($error->message),
276 1
                $error->file ? $error->file : 'n/a',
277 1
                $error->line,
278 1
                $error->column
279 1
            );
280 1
        }
281
282 1
        libxml_clear_errors();
283 1
        libxml_use_internal_errors($internalErrors);
284
285 1
        return $errors;
286
    }
287
}
288