Completed
Push — master ( 6fed26...e3ac15 )
by Guilh
08:12
created

Routing/Loader/RestXmlCollectionLoader.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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;
74 3
                $currentDir = dirname($path);
75
76 3
                $parents = [];
77 3 View Code Duplication
                if (!empty($parent)) {
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