Completed
Pull Request — master (#3)
by Carlos C
01:39 queued 35s
created

SchemaValidator::createDocumentFromString()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
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 DOMDocument|string $content
24
     * @throws \InvalidArgumentException if content is empty
25
     * @throws SchemaValidatorException if malformed xml content
26
     */
27 14
    public function __construct($content)
28
    {
29 14
        if ($content instanceof DOMDocument) {
30 1
            $document = $content;
31
        } else {
32 13
            $document = $this->createDocumentFromString($content);
33
        }
34 12
        $this->document = $document;
35 12
    }
36
37
    /**
38
     * Validate the content by:
39
     * - Create the Schemas collection from the document
40
     * - Validate using validateWithSchemas
41
     *
42
     * @see validateWithSchemas
43
     * @return bool
44
     */
45 8
    public function validate(): bool
46
    {
47 8
        $this->error = '';
48
        try {
49
            // create the schemas collection
50 8
            $schemas = $this->buildSchemas();
51
            // validate the document against the schema collection
52 7
            $this->validateWithSchemas($schemas);
0 ignored issues
show
Bug introduced by
It seems like $schemas defined by $this->buildSchemas() on line 50 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...
53 4
        } catch (LibXmlException $ex) {
54 3
            $this->error = $ex->getMessage();
55 3
            return false;
56
        }
57 4
        return true;
58
    }
59
60 4
    public function getLastError(): string
61
    {
62 4
        return $this->error;
63
    }
64
65
    /**
66
     * Validate against a list of schemas (if any)
67
     *
68
     * @param Schemas $schemas
69
     * @return void
70
     *
71
     * @throws LibXmlException if schema validation fails
72
     */
73 10
    public function validateWithSchemas(Schemas $schemas)
74
    {
75
        // create the schemas collection, then validate the document against the schemas
76 10
        if (! $schemas->count()) {
77 2
            return;
78
        }
79
        // build the unique importing schema
80 8
        $xsd = $schemas->getImporterXsd();
81
        LibXmlException::useInternalErrors(function () use ($xsd) {
82 8
            $this->document->schemaValidateSource($xsd);
83 8
        });
84 4
    }
85
86
    /**
87
     * Retrieve a list of namespaces based on the schemaLocation attributes
88
     *
89
     * @throws SchemaValidatorException if the content of schemaLocation is not an even number of uris
90
     * @return Schemas|Schema[]
91
     */
92 8
    public function buildSchemas(): Schemas
93
    {
94 8
        $schemas = new Schemas();
95 8
        $xpath = new DOMXPath($this->document);
96
97
        // get the http://www.w3.org/2001/XMLSchema-instance namespace (it could not be 'xsi')
98 8
        $xsi = $this->document->lookupPrefix('http://www.w3.org/2001/XMLSchema-instance');
99
100
        // the namespace is not registered, no need to continue
101 8
        if (! $xsi) {
102 1
            return $schemas;
103
        }
104
105
        // get all the xsi:schemaLocation attributes in the document
106
        /** @var \DOMNodeList|false $schemasList */
107 7
        $schemasList = $xpath->query("//@$xsi:schemaLocation");
108
109
        // schemaLocation attribute not found, no need to continue
110 7
        if (false === $schemasList || 0 === $schemasList->length) {
111 1
            return $schemas;
112
        }
113
114
        // process every schemaLocation for even parts
115 6
        foreach ($schemasList as $node) {
116
            // get the node content
117 6
            $content = $node->nodeValue;
118
            // get parts without inner spaces
119 6
            $parts = array_values(array_filter(explode(' ', $content)));
120 6
            $partsCount = count($parts);
121
            // check that the list count is an even number
122 6
            if (0 !== $partsCount % 2) {
123 1
                throw new SchemaValidatorException(
124 1
                    "The schemaLocation value '" . $content . "' must have even number of URIs"
125
                );
126
            }
127
            // insert the uris pairs into the schemas
128 5
            for ($k = 0; $k < $partsCount; $k = $k + 2) {
129 5
                $schemas->create($parts[$k], $parts[$k + 1]);
130
            }
131
        }
132
133 5
        return $schemas;
134
    }
135
136 13
    private function createDocumentFromString(string $content): DOMDocument
137
    {
138 13
        if ('' === $content) {
139 1
            throw new \InvalidArgumentException('The content to validate must be a non-empty string');
140
        }
141 12
        $document = new DOMDocument();
142
        try {
143
            LibXmlException::useInternalErrors(function () use ($content, $document) {
144 12
                $document->loadXML($content);
145 12
            });
146 1
        } catch (LibXmlException $ex) {
147 1
            throw new SchemaValidatorException('Malformed XML Document: ' . $ex->getMessage());
148
        }
149 11
        return $document;
150
    }
151
}
152