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

XmlReader::parseValidation()   B

Complexity

Conditions 10
Paths 9

Size

Total Lines 38
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 19
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 38
rs 7.6666
ccs 19
cts 19
cp 1
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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