为什么 DOMDocument::schemaValidate() 对相同的 XML 和 XSD 文件的执行时间有时会比平时长得多?

Why DOMDocument::schemaValidate() over the same XML and XSD files have sometimes a execution time dramatically higher than usual?

奇怪的是,默认的 class 方法有时会以这种方式失败,即使传递给该方法的参数相同,通常也只需要几分之一秒。

Maximum execution time of 30 seconds exceeded in script_path on line script_line_number

在那一行中:

$result = $DOMDocument -> schemaValidate($schemaPath);

$DOMDocument 始终相同。并且它仅引用具有 ID 属性的相同 XML 的部分。它没有任何类似 URL 的属性,除了 Algorith 和 xmlns 属性,它们本质上不会从任何地方调用任何资源,我们正在谈论 PHP 的 DOMDocument class 和 XML 标准。

$schemaPath 始终相同,它指向服务器本地 XSD 文件,该文件始终存在,无论是在验证尝试之前还是之后,无论成功与否。该架构仅指向位于同一文件夹中的其他本地 xsd 文件,即 <xs:include schemaLocation="schema2.xsd"/>

我能想到的唯一可能的答案是,XSD 文件已通过该方法找到,但由于某种原因无法读取,因为光盘正忙。

什么可能导致该方法的执行时间如此之长?

除了增加PHP的最大执行时间限制外,还应该采取什么措施来防止错误发生?

XML 和 XSD 文件非常小,实际上完全相同的 XML 和 XSD 通常需要不到 ~ 0.1 秒来验证,但是执行时间极少数(约 1000 次中的 1 次)超过 30 秒。


编辑

我隔离了问题,所以我 post 一个样本。

Schema.xsd:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema targetNamespace="http://www.foo.bar/Car" xmlns:SiiDte="http://www.foo.bar/Car" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xs:include schemaLocation="schema2.xsd"/>
    <xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsignature_v10.xsd"/><!-- just the standar signature schema -->
    <xs:element name="ROOT" type="SiiDte:ROOTDefType"/>
    <xs:complexType name="ROOTDefType">
        <xs:sequence>
            <xs:element name="Element"></xs:element>
            <xs:element ref="ds:Signature">
                <xs:annotation>
                    <xs:documentation>Firma Digital sobre Documento</xs:documentation>
                </xs:annotation>
            </xs:element>
        </xs:sequence>
        <xs:attribute name="version" type="xs:decimal" use="required" fixed="1.0"/>
    </xs:complexType>
</xs:schema>

Schema2.xsd:

<xs:schema targetNamespace="http://www.foo.bar/Car" xmlns:ns1="http://www.foo.bar/Car" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xs:simpleType name="MOOType">
        <xs:restriction base="xs:positiveInteger">
            <xs:enumeration value="1"/>
            <xs:enumeration value="2"/>
            <xs:enumeration value="3"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

代码:

// ... a bunch of ther code...

$XML =
    '<?xml version="1.0"?>
    <ROOT xmlns="http://www.foo.bar/Car" version="1.0">
        <Element ID="A1">hello</Element>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                <Reference URI="#A1">
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>base64string</DigestValue>
                </Reference>
            </SignedInfo>
            <SignatureValue>base64string</SignatureValue>
            <KeyInfo>
                <KeyValue>
                    <RSAKeyValue>
                        <Modulus>base64string</Modulus>
                        <Exponent>AQAB</Exponent>
                    </RSAKeyValue>
                </KeyValue>
                <X509Data>
                    <X509Certificate>base64string</X509Certificate>
                </X509Data>
            </KeyInfo>
        </Signature>
    </ROOT>'
;

$DD = new DOMDocument();
$DD -> loadXML($XML);
$i = 0;

while ($i < 100) {
    // ... a bunch of other code...

    libxml_use_internal_errors(true);
    $old_libxml_disable_entity_loader = libxml_disable_entity_loader(false);        $result = $DD -> schemaValidate(__DIR__ . '/schema.xsd');
    libxml_disable_entity_loader($old_libxml_disable_entity_loader); // Se desactiva nuevamente carga de entidades para descartar entidades maliciosas
    $i++;
    echo str_pad($i, 5) . ($result ? 'true' : 'false') . '<br>';

    // ... a bunch of other code...
}

问题是整个脚本达到了 30 秒标记,而不是 DOMDocument::schemaValidate() 单独执行。

执行时间对应于完整脚本的执行,包括它的所有包含和迭代(如果有的话)。

考虑到执行时间不包括在脚本之外花费的任何时间,例如流操作、数据库查询等。因此,例如脚本可能看起来需要 1 分钟,而实际上它只需要 15 或 30 分钟。请参阅 http://php.net/manual/en/function.set-time-limit.php 其中指出:

Note: The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real.

执行需要 30 秒的不是 ::schemaValidate(),而是完整脚本。那么为什么一旦脚本达到 30 秒,错误就落在 schemaValidate() 内部?因为认为 ::schemaValidate() 是一个执行起来比较快的方法,它肯定是迭代中最复杂的代码,因此最大的机会是在执行 schemaValidate 时出错,重复 N 次后(实际情况 N 必须很多)。

所以答案是 ::schemaValidate() 消耗了大部分执行时间,因此错误总是在 ::schemaValidate() 执行时发生。