Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate XML mapping against XSD file #6728

Merged
merged 1 commit into from
Apr 25, 2022

Conversation

greg0ire
Copy link
Member

@greg0ire greg0ire commented Sep 25, 2017

TODO

  • fix all the tests I broke
  • make it optional and disabled by default, and trigger deprecation if disabled?
  • establish what is the right kind of exception to throw (there is a test that asserts a mapping exception from doctrine/common should be thrown, I don't know why yet)

*/
public static function fromLibXmlErrors(array $errors) : self
{
return new self(array_reduce(array_map(function (\LibXMLError $error) : string {
Copy link
Member Author

@greg0ire greg0ire Sep 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First time ever playing with array_reduce, this is fun! But I should probably use implode instead, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that looks like it could perfectly be implemented with implode(PHP_EOL, array_map(...);.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change

@greg0ire
Copy link
Member Author

greg0ire commented Sep 26, 2017

Regarding the exception issue, I found that the xml driver extends a class from another package (doctrine/common). One of the parent methods throws an exception from that package if the result from loadMappingFile is missing a $className key.

Would you consider throwing an exception from this package a BC-break or not? Also, if we forget about this PR for a moment, wouldn't it be better to do this? Asking because ExceptionFromThisPackage does not extend ExceptionFromAnotherPackage

<?php
try {
   // method call from another package
} catch (ExceptionFromAnotherPackage $e) {
    throw new ExceptionFromThisPackage('message', 0, $e);
}

Copy link
Contributor

@Majkl578 Majkl578 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be a nice addition to 3.0 and have it enabled by default.

Note that this introduces a dependency on ext-dom.

@@ -861,6 +862,21 @@ protected function loadMappingFile($file)
return $result;
}

private function assertValid(string $file): void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about call it validateMapping?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be less misleading since I don't use actual assertions here

{
$backedUpErrorSetting = libxml_use_internal_errors(true);
$document = new \DOMDocument();
$document->load($file);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause duplicated IO, better pass already fetched XML string to this function and use DOMDocument::loadXML().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but it gives worse error messages

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!$document->schemaValidate(__DIR__ . '/../../../../../doctrine-mapping.xsd')) {
$errors = libxml_get_errors();
libxml_clear_errors();
libxml_use_internal_errors(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two directives could be put into a more DRY finally block, and use $backedUpErrorSetting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I lost my train of thought it seems

$backedUpErrorSetting = libxml_use_internal_errors(true);
$document = new \DOMDocument();
$document->load($file);
if (!$document->schemaValidate(__DIR__ . '/../../../../../doctrine-mapping.xsd')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space around ! is needed.

@Majkl578
Copy link
Contributor

Majkl578 commented Oct 1, 2017

Would you consider throwing an exception from this package a BC-break or not?

In 3.0: No.
In 2.x: No, because this behavior would not be enabled by default (if accepted for 2.x - not sure about it).


Please add some tests to illustrate how the error propagation from libxml looks like.
It'd also be interesting to know how many (if any) tests fail with this enabled.

@greg0ire
Copy link
Member Author

greg0ire commented Oct 1, 2017

Right now it's not optional so... congrats, no failures!

@greg0ire
Copy link
Member Author

greg0ire commented Oct 1, 2017

Right now it's not optional so... congrats, no failures!

Uuuuh what am I saying, there is one failure that was not intended (I think):

1) Doctrine\Tests\ORM\Tools\Export\XmlClassMetadataExporterTest::testExportDirectoryAndFilesAreCreated
Doctrine\ORM\Mapping\MappingException: libxml error: Element '{http://doctrine-project.org/schemas/orm/doctrine-mapping}join-column', attribute 'on-update': The attribute 'on-update' is not allowed.
/home/greg/dev/doctrine2/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml:35
libxml error: Element '{http://doctrine-project.org/schemas/orm/doctrine-mapping}cascade-persist': This element is not expected. Expected is one of ( {http://doctrine-project.org/schemas/orm/doctrine-mapping}cascade-detach, ##other{http://doctrine-project.org/schemas/orm/doctrine-mapping}* ).
/home/greg/dev/doctrine2/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml:59

/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/MappingException.php:829
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php:873
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php:840
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php:115
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php:59
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php:151
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:333
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php:78
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:226
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:115
/home/greg/dev/doctrine2/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php:91

BTW, here is the error message when loading a string instead of a file:

1) Doctrine\Tests\ORM\Tools\Export\XmlClassMetadataExporterTest::testExportDirectoryAndFilesAreCreated
Doctrine\ORM\Mapping\MappingException: libxml error: Element '{http://doctrine-project.org/schemas/orm/doctrine-mapping}join-column', attribute 'on-update': The attribute 'on-update' is not allowed.
/home/greg/dev/doctrine2/:35
libxml error: Element '{http://doctrine-project.org/schemas/orm/doctrine-mapping}cascade-persist': This element is not expected. Expected is one of ( {http://doctrine-project.org/schemas/orm/doctrine-mapping}cascade-detach, ##other{http://doctrine-project.org/schemas/orm/doctrine-mapping}* ).
/home/greg/dev/doctrine2/:59

/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/MappingException.php:829
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php:873
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php:840
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php:115
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php:59
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php:151
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:333
/home/greg/dev/doctrine2/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php:78
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:226
/home/greg/dev/doctrine2/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:115
/home/greg/dev/doctrine2/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php:91

@greg0ire greg0ire force-pushed the validate_xml_against_xsd branch 3 times, most recently from 6dd616c to e3985e3 Compare October 1, 2017 21:08
@greg0ire
Copy link
Member Author

greg0ire commented Oct 1, 2017

Note that this introduces a dependency on ext-dom.

Made that explicit

@greg0ire greg0ire changed the base branch from master to develop October 1, 2017 21:17
composer.json Outdated
@@ -15,6 +15,7 @@
"minimum-stability": "dev",
"require": {
"php": "^7.1",
"ext-dom": "*",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a question: Isn't this extension optional? The usage of XML in Doctrine is optional too for a user, isn't it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, it's an optional dependency. However, making it an explicit dependency is the defensive way here: we prevent the code from blowing up in the users face with a fatal error if they use XML mappings.

Remember that other bundles (e.g. the FOSUserBundle) specify XML mappings which are automatically imported, so the user won't know that he's using XML mappings and thus should add the ext-dom dependency.

One more thought: moving this to suggest would require all bundles that specify XML mappings to add a require entry for ext-dom, which would be a BC break towards the bundles. IMO it's safer to add it as an explicit dependency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking about "Bundles" sounds more like Symfony instead of Doctrine. This repository is independent from frameworks. Should in this case the require be added in DoctrineBundle? What are "all bundles"?

Copy link
Member

@alcaeus alcaeus Oct 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SenseException replace "bundles" with "anything that provides XML mappings". Suggesting ext-dom instead of requiring it will break third party software if they already use XML mappings. That's a BC break that would move this feature back to 3.0. The way it is now it's possible to add this to the 2.x line.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alcaeus I rebased on 3.x yesterday 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, didn't see that, my bad. IMO it's better to have the explicit dependency anyways. 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings about this. One one hand, it's true that user would need explicit ext-dom dependency. On the other one, we should check at runtime for DOM presence, it just feels wrong to force people with annotations install DOM. (Well, we can say the same about doctrine/annotations, but that's slightly different story).
Since this PR is now 3.0 only, it should be fine fine to do either way, but question is which one is better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other one, we should check at runtime for DOM presence, it just feels wrong to force people with annotations install DOM.

https://webmozart.io/blog/2014/10/24/defining-php-annotations-in-xml/

@Majkl578
Copy link
Contributor

Majkl578 commented Dec 8, 2017

Needs a rebase.

@lcobucci lcobucci added this to the 3.0 milestone Dec 17, 2017
@Majkl578 Majkl578 changed the base branch from develop to master January 2, 2018 20:30
@greg0ire greg0ire changed the base branch from master to 2.7 January 11, 2018 22:51
greg0ire added a commit to greg0ire/doctrine-orm that referenced this pull request Apr 17, 2018
This means that a class implementing a doctrine/common interface or
extending a class from that package can throw a specialized exception
that will still be caught by code in the parent package.
Blocks doctrine#6728
@greg0ire
Copy link
Member Author

establish what is the right kind of exception to throw (there is a test that asserts a mapping exception from doctrine/common should be thrown, I don't know why yet)

#7199 should solve this

beberlei
beberlei previously approved these changes Apr 23, 2022
Copy link
Member

@beberlei beberlei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unrelated follow up could be to get rid of SimpleXML in the driver and use only ext-dom

derrabus
derrabus previously approved these changes Apr 23, 2022
@greg0ire
Copy link
Member Author

@lcobucci regarding the OCP, I read the link you gave and it says (rightly IMO) that the OCP is something that should be taken into account when designing a class. XMLDriver does not seem closed against adding validation functionality, as there is no way to inject a validator in it. Also, I agree that decoration is a great way to add functionality, and use it a lot in my projects, but here the method we would want to decorate is loadMappingFile(), which is protected. That means we cannot use decoration, but would be forced to use inheritance instead to "decorate" the method. Even if it was public and we could do proper decoration, the interface of XMLDriver is not exactly small, which means we would have to proxify a lot of methods to achieve that.

I think a sensible way to violate the OCP as little as possible could be to do the following:

interface XMLValidator
{
    public function __invoke(string $file);
}

class DomXMLValidator implements XMLValidator
{
    // … implementation goes here
}

class XMLDriver
{
    // inject validator in constructor and call it
}

But given the size of the added piece of code, and given how unlikely I think it is people would want to inject their own implementation of the XMLValidator, I'm not sure it's worth it.

Do you still have concerns about runtime deprecations, now that we have switched to https://github.com/doctrine/deprecations/ ? That lib wasn't discussed at all in doctrine/doctrine-website#200

@greg0ire greg0ire requested a review from lcobucci April 24, 2022 09:55
@lcobucci
Copy link
Member

@lcobucci regarding the OCP, I read the link you gave and it says (rightly IMO) that the OCP is something that should be taken into account when designing a class. XMLDriver does not seem closed against adding validation functionality, as there is no way to inject a validator in it. (...)

The great thing about "principles" that version of me from 4 years ago hadn't learned well is that they aren't absolute rules 😆. I (now) know that understanding the context and allowing some flexibility is important - even though I tend to stick to them as much as possible.

If I was making this feature, I'd still aim to add a new component instead of modifying the existing one to conditionally change its behaviour based on a boolean flag. I believe that adding a new (final) class (SchemaEnforcingXmlDriver, whateva) that - initially - inherits the XmlDriver makes things more explicit in the long run and gives more flexibility on refactoring than the boolean flag approach 😄

With that said, I understand your arguments and agree that a completely different design would make things much better buuuuut 🤣

Do you still have concerns about runtime deprecations, now that we have switched to https://github.com/doctrine/deprecations/ ? That lib wasn't discussed at all in doctrine/doctrine-website#200

Doctrine has fully embraced that deprecations library so I believe that's a moot point now 👍

lcobucci
lcobucci previously approved these changes Apr 25, 2022
Copy link
Member

@lcobucci lcobucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor improvements on the SA side of things =)

tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php Outdated Show resolved Hide resolved
tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php Outdated Show resolved Hide resolved
Co-Authored-By: Axel Venet <avenet@wamiz.com>
Co-authored-by: Luís Cobucci <lcobucci@gmail.com>
@greg0ire greg0ire merged commit f4585b9 into doctrine:2.13.x Apr 25, 2022
@greg0ire greg0ire deleted the validate_xml_against_xsd branch April 25, 2022 21:04
@greg0ire
Copy link
Member Author

Thanks for reviving this @Kern046 ! And thanks for your help @lcobucci ! 5 years after the start, let's finally merge this :)

Davidmattei added a commit to Davidmattei/elasticms that referenced this pull request Jan 1, 2024
User Deprecated: Using XML mapping driver with XSD validation disabled is deprecated and will not be supported in Doctrine ORM 3.0. (XmlDriver.php:60 called by SimplifiedXmlDriver.php:23, doctrine/orm#6728, package doctrine/orm)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants