Passed
Push — master ( 6d2edc...84c866 )
by Tim
01:33
created

SchemaValidationTestTrait::validateDocument()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 8
nop 1
dl 0
loc 25
rs 9.5555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Test\XML;
6
7
use DOMDocument;
8
use libXMLError;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\XML\Exception\SchemaViolationException;
11
use XMLReader;
12
13
use function array_unique;
14
use function class_exists;
15
use function implode;
16
use function libxml_get_last_error;
17
use function libxml_use_internal_errors;
18
use function trim;
19
20
/**
21
 * Test for AbstractElement classes to perform schema validation tests.
22
 *
23
 * @package simplesamlphp\xml-common
24
 */
25
trait SchemaValidationTestTrait
26
{
27
    /** @var class-string */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
28
    protected string $testedClass;
29
30
    /** @var string */
31
    protected string $schema;
32
33
    /** @var \DOMDocument|null */
34
    protected ?DOMDocument $xmlRepresentation = null;
35
36
37
    /**
38
     * Test schema validation.
39
     */
40
    public function testSchemaValidation(): void
41
    {
42
        if (!class_exists($this->testedClass)) {
43
            $this->markTestSkipped(
0 ignored issues
show
Bug introduced by
It seems like markTestSkipped() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

43
            $this->/** @scrutinizer ignore-call */ 
44
                   markTestSkipped(
Loading history...
44
                'Unable to run ' . self::class . '::testSchemaValidation(). Please set ' . self::class
45
                . ':$testedClass to a class-string representing the XML-class being tested',
46
            );
47
        } elseif (empty($this->schema)) {
48
            $this->markTestSkipped(
49
                'Unable to run ' . self::class . '::testSchemaValidation(). Please set ' . self::class
50
                . ':$schema to point to a schema file',
51
            );
52
        } elseif (empty($this->xmlRepresentation)) {
53
            $this->markTestSkipped(
54
                'Unable to run ' . self::class . '::testSchemaValidation(). Please set ' . self::class
55
                . ':$xmlRepresentation to a DOMDocument representing the XML-class being tested',
56
            );
57
        } else {
58
            $predoc = XMLReader::XML($this->xmlRepresentation->saveXML());
59
            Assert::notFalse($predoc);
60
61
            /** @psalm-var \XMLReader $predoc */
62
            $pre = $this->validateDocument($predoc);
0 ignored issues
show
Bug introduced by
$predoc of type boolean is incompatible with the type XMLReader expected by parameter $xmlReader of SimpleSAML\Test\XML\Sche...ait::validateDocument(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
            $pre = $this->validateDocument(/** @scrutinizer ignore-type */ $predoc);
Loading history...
63
            $this->assertTrue($pre);
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

63
            $this->/** @scrutinizer ignore-call */ 
64
                   assertTrue($pre);
Loading history...
64
65
            $class = $this->testedClass::fromXML($this->xmlRepresentation->documentElement);
66
            $serializedClass = $class->toXML();
67
68
            $postdoc = XMLReader::XML($serializedClass->ownerDocument->saveXML());
69
            Assert::notFalse($postdoc);
70
            /** @psalm-var \XMLReader $postdoc */
71
            $post = $this->validateDocument($postdoc);
72
            $this->assertTrue($post);
73
        }
74
    }
75
76
77
    /**
78
     * @param \XMLReader $doc
79
     * @return boolean
80
     */
81
    private function validateDocument(XMLReader $xmlReader): bool
82
    {
83
        $xmlReader->setSchema($this->schema);
84
85
        libxml_use_internal_errors(true);
86
87
        $msgs = [];
88
89
        while ($xmlReader->read()) {
90
            if (!$xmlReader->isValid()) {
91
                /** @psalm-var \libXMLError|false $err */
92
                $err = libxml_get_last_error();
93
                if ($err instanceof libXMLError) {
94
                    $msgs[] = trim($err->message) . ' on line ' . $err->line;
95
                }
96
            }
97
        }
98
99
        if ($msgs) {
100
            throw new SchemaViolationException(
101
                "XML schema validation errors:\n - " . implode("\n - ", array_unique($msgs))
102
            );
103
        }
104
105
        return true;
106
    }
107
}
108