Test Failed
Pull Request — master (#12)
by Stanislau
03:12
created

XmlReader::parseBody()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.0131

Importance

Changes 0
Metric Value
cc 6
eloc 12
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 27
rs 9.2222
ccs 13
cts 14
cp 0.9286
crap 6.0131
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 *  This file is part of the Micro framework package.
7
 *
8
 *  (c) Stanislau Komar <[email protected]>
9
 *
10
 *  For the full copyright and license information, please view the LICENSE
11
 *  file that was distributed with this source code.
12
 */
13
14
namespace Micro\Library\DTO\Reader;
15
16
use Micro\Library\DTO\Merger\MergerFactoryInterface;
17
18
/**
19
 * @TODO: Temporary solution. MVP
20
 * @TODO: Get XSD api version
21
 */
22
class XmlReader implements ReaderInterface
23
{
24
    /**
25
     * @param iterable<string> $classDefinitionFilesCollection
26
     */
27 6
    public function __construct(
28
        private iterable $classDefinitionFilesCollection,
29
        private MergerFactoryInterface $mergerFactory
30
    ) {
31 6
    }
32
33 6
    public function read(): iterable
34
    {
35 6
        $classCollection = [];
36 6
        foreach ($this->classDefinitionFilesCollection as $filePath) {
37 6
            $xml = $this->createDom($filePath);
38
39 4
            foreach ($xml->getElementsByTagName(self::TAG_CLASS_DEFINITION) as $classDef) {
40 4
                $classCollection[] = $this->parseClass($classDef);
41
            }
42
        }
43
44 4
        return $this->mergerFactory->create($classCollection)->merge();
45
    }
46
47
    /**
48
     * @param \DOMDocument $document
49
     *
50
     * @return string[]
51
     */
52 5
    protected function lookupXsd(\DOMDocument $document): array
53
    {
54 5
        $schemaLocation = $document->getElementsByTagName('dto')[0]->getAttribute('xsi:schemaLocation');
55 5
        if (!$schemaLocation) {
56
            throw new \RuntimeException('XSD Scheme should be declared on <dto xsi:schemaLocation="">');
57
        }
58
59 5
        $location = explode(' ', $schemaLocation);
60 5
        if (2 !== \count($location)) {
61
            throw new \RuntimeException(sprintf('XSD Scheme declaration failed <dto xsi:schemaLocation="%s">', $schemaLocation));
62
        }
63
64 5
        return $location;
65
    }
66
67
    /**
68
     * @return array<string, mixed>
69
     */
70 4
    protected function parseClass(\DOMNode $classDef): array
71
    {
72 4
        return $this->parseBody($classDef);
73 4
        // dump($this->parseBody($classDef)); exit;
74 4
75
        $class = [];
0 ignored issues
show
Unused Code introduced by
$class = array() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
76
        $props = [];
77
        if (null === $classDef->attributes) {
78
            return $class;
79 4
        }
80 4
81
        /** @var \DOMNode $attribute */
82
        foreach ($classDef->attributes as $attribute) {
83
            $class[$attribute->nodeName] = $attribute->nodeValue;
84 4
        }
85 4
86 4
        /** @var \DOMNode $node */
87
        foreach ($classDef->childNodes as $node) {
88
            if (str_starts_with($node->nodeName, '#')) {
89 4
                continue;
90 4
            }
91 4
92 1
            $propCfg = [];
93
            $validation = $this->parseValidation($node);
94
            if ($validation) {
95 4
                $propCfg['validation'] = $validation;
96
            }
97
98
            if (null === $node->attributes) {
99 4
                continue;
100 4
            }
101
102
            foreach ($node->attributes as $attribute) {
103
                $propCfg[$attribute->nodeName] = $attribute->nodeValue;
104
            }
105
            /**
106 4
             * @psalm-suppress PossiblyInvalidArgument
107
             * @psalm-suppress InvalidArgument
108
             */
109
            if (\array_key_exists($propCfg[self::PROP_PROP_NAME], $props)) {
110
                throw new \RuntimeException(sprintf('Property "%s" already defined. Location: %s" ',  $propCfg[self::PROP_PROP_NAME], $classDef->baseURI));
111 4
            }
112
113
            /** @psalm-suppress PossiblyNullArrayOffset  */
114 4
            $props[$propCfg[self::PROP_PROP_NAME]] = $propCfg;
115
        }
116 4
117
        $class[self::PATH_PROP] = $props;
118
119
        return $class;
120
    }
121
122
    /**
123
     * @param \DOMNode $attribute
124 4
     *
125
     * @return mixed[]
126 4
     */
127 4
    protected function parseValidation(\DOMNode $attribute): array|null
128
    {
129
        if (!$attribute->childNodes->count()) {
130 1
            return null;
131
        }
132 1
133 1
        $constraints = [];
134 1
        /** @var \DOMNode $validationNode */
135
        foreach ($attribute->childNodes as $validationNode) {
136 1
            if (!$validationNode->childNodes->count() || 'validation' !== $validationNode->nodeName) {
137
                continue;
138 1
            }
139 1
            $groupConstraints = [];
140 1
            /** @var \DOMNode $constraintNode */
141
            foreach ($validationNode->childNodes as $constraintNode) {
142
                if ('#text' === $constraintNode->nodeName) {
143 1
                    continue;
144
                }
145 1
146 1
                $constraintAttributes = [];
147 1
                /** @var \DOMAttr $constraintItemAttribute */
148
                if ($constraintNode->attributes) {
149
                    foreach ($constraintNode->attributes as $constraintItemAttribute) {
150
                        $constraintAttributes[$constraintItemAttribute->nodeName] = $constraintItemAttribute->nodeValue;
151 1
                    }
152 1
                }
153
154
                if (empty($constraintAttributes)) {
155 1
                    $constraintAttributes = [];
156
                }
157
158 1
                $groupConstraints[] = [$constraintNode->nodeName, $constraintAttributes];
159
            }
160
161 1
            $constraints[] = $groupConstraints;
162
        }
163
164 6
        return $constraints;
165
    }
166 6
167 1
    protected function parseBody(\DOMNode $node): array
168
    {
169
        $childNodes = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $childNodes is dead and can be removed.
Loading history...
170 5
        $attributes = [];
171
172
        if ($node->hasAttributes()) {
173
            /**
174 5
             * @var \DOMAttr $tmpAttribute
175 5
             */
176
            foreach ($node->attributes as $tmpAttribute) {
177 5
                $attributes[$tmpAttribute->nodeName] = $tmpAttribute->nodeValue;
178 5
            }
179
        }
180 5
181
        if ($node->hasChildNodes()) {
182 5
            /** @var \DOMNode $child */
183 4
            foreach ($node->childNodes as $child) {
184
                $childName = $child->nodeName;
185
                if ('#text' === $childName) {
186 1
                    continue;
187
                }
188 1
189 1
                $attributes[$childName][] = $this->parseBody($child);
190
            }
191
        }
192 1
193
        return $attributes;
194 1
    }
195
196 1
    protected function createDom(string $filePath): \DOMDocument
197
    {
198
        if (!file_exists($filePath)) {
199
            throw new \RuntimeException(sprintf('File %s is not found', $filePath));
200
        }
201
202
        if (!is_readable($filePath)) {
203
            throw new \RuntimeException(sprintf('Has no access to read the file %s', $filePath));
204
        }
205
206
        $xml = new \DOMDocument();
207
        $xml->load($filePath);
208
209
        $xsdSchemaCfg = $this->lookupXsd($xml);
210
        $xsdSchemaLocation = $xsdSchemaCfg[1];
211
212
        libxml_use_internal_errors(true);
213
214
        if ($xml->schemaValidate($xsdSchemaLocation)) {
215
            return $xml;
216
        }
217
218
        $errs = [];
219
220
        foreach (libxml_get_errors() as $error) {
221
            $errs[] = sprintf('%s in file `%s` on line %d', $error->message, $error->file, $error->line);
222
        }
223
224
        $errorMessage = implode("\n ", $errs);
225
226
        libxml_use_internal_errors(false);
227
228
        throw new \RuntimeException(sprintf("Schema validation exception: \r\n %s\r", $errorMessage));
229
    }
230
}
231