Completed
Pull Request — master (#2141)
by Christian
05:23 queued 03:47
created

RestXmlCollectionLoader::supports()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
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
 * @internal
26
 */
27
class RestXmlCollectionLoader extends XmlFileLoader
28
{
29
    protected $collectionParents = [];
30
    private $processor;
31
    private $includeFormat;
32
    private $formats;
33
    private $defaultFormat;
34
35
    /**
36
     * @param string[] $formats
37
     */
38 21
    public function __construct(
39
        FileLocatorInterface $locator,
40
        RestRouteProcessor $processor,
41
        bool $includeFormat = true,
42
        array $formats = [],
43
        string $defaultFormat = null
44
    ) {
45 21
        parent::__construct($locator);
46
47 21
        $this->processor = $processor;
48 21
        $this->includeFormat = $includeFormat;
49 21
        $this->formats = $formats;
50 21
        $this->defaultFormat = $defaultFormat;
51 21
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56 9
    protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file): void
57
    {
58 9
        switch ($node->tagName) {
59 9
            case 'route':
60 6
                $this->parseRoute($collection, $node, $path);
61
62 6
                break;
63 3
            case 'import':
64 3
                $name = (string) $node->getAttribute('id');
65 3
                $resource = (string) $node->getAttribute('resource');
66 3
                $prefix = (string) $node->getAttribute('prefix');
67 3
                $namePrefix = (string) $node->getAttribute('name-prefix');
68 3
                $parent = (string) $node->getAttribute('parent');
69 3
                $type = (string) $node->getAttribute('type');
70 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...
71 3
                $currentDir = dirname($path);
72
73 3
                $parents = [];
74 3
                if (!empty($parent)) {
75 3
                    if (!isset($this->collectionParents[$parent])) {
76 1
                        throw new \InvalidArgumentException(sprintf('Cannot find parent resource with name %s', $parent));
77
                    }
78
79 2
                    $parents = $this->collectionParents[$parent];
80
                }
81
82 2
                $imported = $this->processor->importResource($this, $resource, $parents, $prefix, $namePrefix, $type, $currentDir);
83
84 2
                if (!empty($name) && $imported instanceof RestRouteCollection) {
85 2
                    $parents[] = (!empty($prefix) ? $prefix.'/' : '').$imported->getSingularName();
86 2
                    $prefix = null;
87
88 2
                    $this->collectionParents[$name] = $parents;
89
                }
90
91 2
                if (!empty($host)) {
92
                    $imported->setHost($host);
93
                }
94
95 2
                $imported->addPrefix((string) $prefix);
96 2
                $collection->addCollection($imported);
0 ignored issues
show
Documentation introduced by
$imported is of type object<Symfony\Component\Routing\RouteCollection>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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