Completed
Push — next-release ( 785062...73d4c0 )
by Carlos C
02:29
created

SchemaValidator::buildSchemas()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 8.006

Importance

Changes 0
Metric Value
dl 0
loc 47
ccs 21
cts 22
cp 0.9545
rs 7.9119
c 0
b 0
f 0
cc 8
nc 6
nop 0
crap 8.006
1
<?php
2
namespace XmlSchemaValidator;
3
4
use DOMDocument;
5
use DOMXPath;
6
7
/**
8
 * This class is an XML schema validator
9
 * It is needed because some XML can contain more than one external schema
10
 * and DOM library fails to load it.
11
 */
12
class SchemaValidator
13
{
14
    /** @var DOMDocument */
15
    private $document;
16
17
    /** @var string */
18
    private $error = '';
19
20
    /**
21
     * SchemaValidator constructor.
22
     *
23
     * @param string $content
24
     * @throws \InvalidArgumentException if content is empty
25
     * @throws SchemaValidatorException if malformed xml content
26
     */
27 13
    public function __construct(string $content)
28
    {
29 13
        if ('' === $content) {
30 1
            throw new \InvalidArgumentException('The content to validate must be a non-empty string');
31
        }
32 12
        $document = new DOMDocument();
33
        try {
34
            LibXmlException::useInternalErrors(function () use ($content, $document) {
35 12
                $document->loadXML($content, LIBXML_NOWARNING);
36 12
            });
37 1
        } catch (LibXmlException $ex) {
38 1
            throw new SchemaValidatorException('Malformed XML Document: ' . $ex->getMessage());
39
        }
40 11
        $this->document = $document;
41 11
    }
42
43
    /**
44
     * Validate the content by:
45
     * - Create the Schemas collection from the document
46
     * - Validate using validateWithSchemas
47
     *
48
     * @see validateWithSchemas
49
     * @return bool
50
     */
51 8
    public function validate(): bool
52
    {
53 8
        $this->error = '';
54
        try {
55
            // create the schemas collection
56 8
            $schemas = $this->buildSchemas();
57
            // validate the document against the schema collection
58 7
            $this->validateWithSchemas($schemas);
0 ignored issues
show
Bug introduced by
It seems like $schemas defined by $this->buildSchemas() on line 56 can also be of type array<integer,object<XmlSchemaValidator\Schema>>; however, XmlSchemaValidator\Schem...::validateWithSchemas() does only seem to accept object<XmlSchemaValidator\Schemas>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
59 4
        } catch (LibXmlException $ex) {
60 3
            $this->error = $ex->getMessage();
61 3
            return false;
62
        }
63 4
        return true;
64
    }
65
66 4
    public function getLastError(): string
67
    {
68 4
        return $this->error;
69
    }
70
71
    /**
72
     * Validate against a list of schemas (if any)
73
     *
74
     * @param Schemas $schemas
75
     * @return void
76
     *
77
     * @throws LibXmlException if schema validation fails
78
     */
79 10
    public function validateWithSchemas(Schemas $schemas)
80
    {
81
        // create the schemas collection, then validate the document against the schemas
82 10
        if (! $schemas->count()) {
83 2
            return;
84
        }
85
        // build the unique importing schema
86 8
        $xsd = $schemas->getImporterXsd();
87 8
        $document = $this->document;
88
        LibXmlException::useInternalErrors(function () use ($document, $xsd) {
89 8
            $document->schemaValidateSource($xsd);
90 8
        });
91 4
    }
92
93
    /**
94
     * Retrieve a list of namespaces based on the schemaLocation attributes
95
     *
96
     * @throws SchemaValidatorException if the content of schemaLocation is not an even number of uris
97
     * @return Schemas|Schema[]
98
     */
99 8
    public function buildSchemas(): Schemas
100
    {
101 8
        $schemas = new Schemas();
102 8
        $xpath = new DOMXPath($this->document);
103
104
        // get the http://www.w3.org/2001/XMLSchema-instance namespace (it could not be 'xsi')
105 8
        $xsi = $this->document->lookupPrefix('http://www.w3.org/2001/XMLSchema-instance');
106
107
        // the namespace is not registered, no need to continue
108 8
        if (! $xsi) {
109 1
            return $schemas;
110
        }
111
112
        // get all the xsi:schemaLocation attributes in the document
113
        /** @var \DOMNodeList|false $schemasList */
114 7
        $schemasList = $xpath->query("//@$xsi:schemaLocation");
115
116
        // schemaLocation attribute not found, no need to continue
117 7
        if (false === $schemasList || 0 === $schemasList->length) {
118 1
            return $schemas;
119
        }
120
121
        // process every schemaLocation for even parts
122 6
        for ($s = 0; $s < $schemasList->length; $s++) {
123 6
            $node = $schemasList->item($s);
124 6
            if (null === $node) {
125
                continue;
126
            }
127
            // get the node content
128 6
            $content = $node->nodeValue;
129
            // get parts without inner spaces
130 6
            $parts = array_values(array_filter(explode(' ', $content)));
131 6
            $partsCount = count($parts);
132
            // check that the list count is an even number
133 6
            if (0 !== $partsCount % 2) {
134 1
                throw new SchemaValidatorException(
135 1
                    "The schemaLocation value '" . $content . "' must have even number of URIs"
136
                );
137
            }
138
            // insert the uris pairs into the schemas
139 5
            for ($k = 0; $k < $partsCount; $k = $k + 2) {
140 5
                $schemas->create($parts[$k], $parts[$k + 1]);
141
            }
142
        }
143
144 5
        return $schemas;
145
    }
146
}
147