Skip to content

Commit

Permalink
Merge pull request #1814 from alcaeus/validate-mapping-files-against-…
Browse files Browse the repository at this point in the history
…schema

[2.0] Validate mapping files against schema
  • Loading branch information
alcaeus committed Jul 27, 2018
2 parents ecf0636 + 4e73b85 commit c6000a7
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 61 deletions.
88 changes: 44 additions & 44 deletions doctrine-mongo-mapping.xsd
Expand Up @@ -14,12 +14,12 @@

<xs:element name="doctrine-mongo-mapping">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="document" type="odm:document" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="embedded-document" type="odm:document" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="mapped-superclass" type="odm:document" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="query-result-document" type="odm:document" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:choice>
</xs:complexType>
</xs:element>

Expand All @@ -36,7 +36,7 @@
</xs:complexType>

<xs:complexType name="document">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="id" type="odm:id" minOccurs="0" maxOccurs="1"/>
<xs:element name="field" type="odm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="embed-one" type="odm:embed-one" minOccurs="0" maxOccurs="unbounded"/>
Expand All @@ -51,7 +51,7 @@
<xs:element name="indexes" type="odm:indexes" minOccurs="0"/>
<xs:element name="shard-key" type="odm:shard-key" minOccurs="0"/>
<xs:element name="read-preference" type="odm:read-preference" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:choice>
<xs:attribute name="db" type="xs:NMTOKEN" />
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="writeConcern" type="xs:string" />
Expand All @@ -66,9 +66,9 @@
</xs:complexType>

<xs:complexType name="read-preference">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="tag-set" type="odm:read-preference-tag-set" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:choice>
<xs:attribute name="mode" type="odm:read-preference-values"/>
</xs:complexType>

Expand All @@ -95,9 +95,9 @@
</xs:complexType>

<xs:complexType name="id">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="generator-option" type="odm:id-generator-option" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:choice>
<xs:attribute name="type" type="xs:NMTOKEN" />
<xs:attribute name="strategy" type="xs:NMTOKEN" default="auto" />
<xs:attribute name="fieldName" type="xs:NMTOKEN" default="id" />
Expand All @@ -109,11 +109,11 @@
</xs:complexType>

<xs:complexType name="embed-one">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="discriminator-field" type="odm:discriminator-field" minOccurs="0"/>
<xs:element name="discriminator-map" type="odm:discriminator-map" minOccurs="0"/>
<xs:element name="default-discriminator-value" type="odm:default-discriminator-value" minOccurs="0"/>
</xs:sequence>
</xs:choice>
<xs:attribute name="target-document" type="xs:string" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fieldName" type="xs:NMTOKEN" />
Expand All @@ -123,11 +123,11 @@
</xs:complexType>

<xs:complexType name="embed-many">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="discriminator-field" type="odm:discriminator-field" minOccurs="0"/>
<xs:element name="discriminator-map" type="odm:discriminator-map" minOccurs="0"/>
<xs:element name="default-discriminator-value" type="odm:default-discriminator-value" minOccurs="0"/>
</xs:sequence>
</xs:choice>
<xs:attribute name="target-document" type="xs:string" />
<xs:attribute name="collection-class" type="xs:string" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
Expand All @@ -148,14 +148,14 @@
</xs:simpleType>

<xs:complexType name="reference-one">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="cascade" type="odm:cascade-type" minOccurs="0" />
<xs:element name="discriminator-field" type="odm:discriminator-field" minOccurs="0"/>
<xs:element name="discriminator-map" type="odm:discriminator-map" minOccurs="0"/>
<xs:element name="default-discriminator-value" type="odm:default-discriminator-value" minOccurs="0"/>
<xs:element name="sort" type="odm:sort-map" minOccurs="0" />
<xs:element name="criteria" type="odm:criteria-map" minOccurs="0" />
</xs:sequence>
</xs:choice>
<xs:attribute name="target-document" type="xs:string" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fieldName" type="xs:NMTOKEN" />
Expand All @@ -171,15 +171,15 @@
</xs:complexType>

<xs:complexType name="reference-many">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="cascade" type="odm:cascade-type" minOccurs="0" />
<xs:element name="discriminator-field" type="odm:discriminator-field" minOccurs="0"/>
<xs:element name="discriminator-map" type="odm:discriminator-map" minOccurs="0"/>
<xs:element name="default-discriminator-value" type="odm:default-discriminator-value" minOccurs="0"/>
<xs:element name="sort" type="odm:sort-map" minOccurs="0" />
<xs:element name="criteria" type="odm:criteria-map" minOccurs="0" />
<xs:element name="prime" type="odm:primers" minOccurs="0" />
</xs:sequence>
</xs:choice>
<xs:attribute name="target-document" type="xs:string" />
<xs:attribute name="collection-class" type="xs:string" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
Expand All @@ -204,9 +204,9 @@
</xs:complexType>

<xs:complexType name="sort-map">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="sort" type="odm:sort-type" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="criteria-type">
Expand All @@ -215,15 +215,15 @@
</xs:complexType>

<xs:complexType name="criteria-map">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="criteria" type="odm:criteria-type" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="primers">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="odm:primer-field" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="primer-field">
Expand All @@ -233,14 +233,14 @@
<xs:complexType name="emptyType"/>

<xs:complexType name="cascade-type">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="all" type="odm:emptyType" minOccurs="0" maxOccurs="1"/>
<xs:element name="persist" type="odm:emptyType" minOccurs="0" maxOccurs="1"/>
<xs:element name="merge" type="odm:emptyType" minOccurs="0" maxOccurs="1"/>
<xs:element name="remove" type="odm:emptyType" minOccurs="0" maxOccurs="1"/>
<xs:element name="refresh" type="odm:emptyType" minOccurs="0" maxOccurs="1"/>
<xs:element name="detach" type="odm:emptyType" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:simpleType name="inheritance-type">
Expand All @@ -264,9 +264,9 @@
</xs:complexType>

<xs:complexType name="discriminator-map">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="discriminator-mapping" type="odm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="discriminator-field">
Expand Down Expand Up @@ -296,9 +296,9 @@
</xs:complexType>

<xs:complexType name="lifecycle-callbacks">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="lifecycle-callback" type="odm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="index-key">
Expand All @@ -324,33 +324,33 @@
</xs:simpleType>

<xs:complexType name="partial-filter-expression-field">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="odm:partial-filter-expression-field" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="operator" type="odm:partial-filter-expression-operator" use="optional" />
<xs:attribute name="value" type="xs:string" use="optional" />
</xs:complexType>

<xs:complexType name="partial-filter-expression-and">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="odm:partial-filter-expression-field" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="partial-filter-expression">
<xs:choice>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="and" type="odm:partial-filter-expression-and" minOccurs="1" maxOccurs="unbounded" />
<xs:element name="field" type="odm:partial-filter-expression-field" minOccurs="1" maxOccurs="unbounded" />
</xs:choice>
</xs:complexType>

<xs:complexType name="index">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="key" type="odm:index-key" minOccurs="1" maxOccurs="unbounded"/>
<xs:element name="option" type="odm:index-option" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="partial-filter-expression" type="odm:partial-filter-expression" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN"/>
<xs:attribute name="drop-dups" type="xs:boolean"/>
<xs:attribute name="background" type="xs:boolean"/>
Expand All @@ -359,16 +359,16 @@
</xs:complexType>

<xs:complexType name="indexes">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="index" type="odm:index" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:complexType name="shard-key">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="key" type="odm:shard-key-key" minOccurs="1" maxOccurs="unbounded"/>
<xs:element name="option" type="odm:shard-key-option" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
<xs:attribute name="unique" type="xs:boolean"/>
<xs:attribute name="numInitialChunks" type="xs:integer"/>
</xs:complexType>
Expand All @@ -390,9 +390,9 @@


<xs:complexType name="also-load-methods">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="also-load-method" type="odm:also-load-method" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:choice>
</xs:complexType>

<xs:simpleType name="read-preference-values">
Expand All @@ -406,9 +406,9 @@
</xs:simpleType>

<xs:complexType name="read-preference-tag-set">
<xs:sequence>
<xs:element name="tag" type="read-preference-tag" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="tag" type="odm:read-preference-tag" minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
</xs:complexType>

<xs:complexType name="read-preference-tag">
Expand Down
42 changes: 41 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
Expand Up @@ -6,18 +6,27 @@

use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use DOMDocument;
use LibXMLError;
use function array_keys;
use function array_map;
use function constant;
use function count;
use function current;
use function explode;
use function implode;
use function in_array;
use function is_numeric;
use function iterator_to_array;
use function libxml_clear_errors;
use function libxml_get_errors;
use function libxml_use_internal_errors;
use function next;
use function preg_match;
use function simplexml_load_file;
use function sprintf;
use function strtoupper;
use function trim;

Expand Down Expand Up @@ -160,7 +169,7 @@ public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Ma
$attributes = $field->attributes();
foreach ($attributes as $key => $value) {
$mapping[$key] = (string) $value;
$booleanAttributes = ['id', 'reference', 'embed', 'unique', 'sparse'];
$booleanAttributes = ['reference', 'embed', 'unique', 'sparse'];
if (! in_array($key, $booleanAttributes)) {
continue;
}
Expand Down Expand Up @@ -533,6 +542,9 @@ private function transformReadPreference($xmlReadPreference)
protected function loadMappingFile($file)
{
$result = [];

$this->validateSchema($file);

$xmlElement = simplexml_load_file($file);

foreach (['document', 'embedded-document', 'mapped-superclass', 'query-result-document'] as $type) {
Expand All @@ -548,4 +560,32 @@ protected function loadMappingFile($file)

return $result;
}

private function validateSchema(string $filename): void
{
$document = new DOMDocument();
$document->load($filename);

$previousUseErrors = libxml_use_internal_errors(true);

try {
libxml_clear_errors();

if (! $document->schemaValidate(__DIR__ . '/../../../../../../doctrine-mongo-mapping.xsd')) {
throw MappingException::xmlMappingFileInvalid($filename, $this->formatErrors(libxml_get_errors()));
}
} finally {
libxml_use_internal_errors($previousUseErrors);
}
}

/**
* @param LibXMLError[] $xmlErrors
*/
private function formatErrors(array $xmlErrors): string
{
return implode("\n", array_map(function (LibXMLError $error): string {
return sprintf('Line %d:%d: %s', $error->line, $error->column, $error->message);
}, $xmlErrors));
}
}
5 changes: 5 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
Expand Up @@ -396,4 +396,9 @@ public static function repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($cla
{
return new self(sprintf("'repositoryMethod' used on '%s' in class '%s' can not be combined with skip, limit or sort.", $fieldName, $className));
}

public static function xmlMappingFileInvalid(string $filename, string $errorDetails): self
{
return new self(sprintf("The mapping file %s is invalid: \n%s", $filename, $errorDetails));
}
}
Expand Up @@ -578,3 +578,8 @@ public static function loadMetadata(ClassMetadata $metadata)
class PhonenumberCollection extends ArrayCollection
{
}

class InvalidMappingDocument
{
public $id;
}

0 comments on commit c6000a7

Please sign in to comment.