fix(parser): stop swallowing validation errors - #14
Merged
Conversation
loadFromString() wrapped its whole body in `catch (\Exception)` and rethrew everything as KmlParserException::failedToParse(). A validation failure raised by KmlValidator therefore reached the caller as a generic parse error, so "Invalid longitude value: 181" and "malformed XML" were indistinguishable by type. Validation exceptions now propagate untouched and malformed XML surfaces as invalidXml(), which until now was unreachable because the catch block below it re-wrapped it immediately. The content was also parsed twice, once by the validator and once by the parser, doubling the work and the peak memory of every load. The validator gained validateDocument(), which takes an already parsed document, and the parser builds the SimpleXMLElement once and hands it over. validate() keeps its string signature and delegates. Both classes flipped libxml_use_internal_errors(true) on and never restored it, changing libxml error handling for the rest of the application. The previous value is now saved and restored in a finally, on the success and failure paths alike. failedToParse() is no longer thrown and is marked deprecated rather than removed, so callers referencing it keep working for one cycle.
KmlValidator hardcoded the OGC 2.2 namespace and rejected everything else, so any document exported before the OGC took the format over (earth.google.com/kml/2.0, 2.1 and 2.2, still common in the wild) failed validation with "Invalid or missing KML namespace" and could not be parsed at all. The accepted namespaces are now a list, configurable through the new supported_namespaces key, and XPath is registered against the namespace the document actually declares rather than the one we assumed. That last part is what makes a 2.1 document parse end to end instead of validating and then returning no placemarks. The validator also ignored the kml-parser.namespace config entirely, even though KmlParser read it. Setting that key made every document invalid, since validation still demanded 2.2. KmlParser now passes the configured namespaces to the validator, so the key finally does what it says. KmlValidator keeps working standalone, defaulting to the known namespaces without touching the container.
danielebarbaro
force-pushed
the
fix/xml-parsing-and-error-handling
branch
from
September 8, 2026 13:14
1d06a29 to
2a454b7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four related defects in
KmlParser::loadFromString()andKmlValidator, all in the same few lines, so they are fixed together.1. Validation errors lose their type
loadFromString()wrapped its entire body incatch (\Exception)and rethrew everything throughKmlParserException::failedToParse():A
KmlExceptionraised by the validator ("Invalid longitude value: 181", "Missing required element: Document") arrived at the caller asKmlParserException: Failed to parse KML content: .... The original message survived inside the string, but the type did not, so there is no way to distinguish "this file is not XML" from "this file is XML but the coordinates are out of range" other than substring matching on the message.Validation exceptions now propagate untouched.
2.
invalidXml()was unreachablenew SimpleXMLElement($content)throws on malformed input, so execution never reached thelibxml_get_errors()check. And if it somehow had, thecatch (\Exception)two lines below would have caught theinvalidXmlexception and re-wrapped it asfailedToParse. Dead in two independent ways.invalidXml()is now what malformed XML actually throws, which is what its name always claimed.3. Everything was parsed twice
KmlValidator::validate()built aSimpleXMLElement, threw it away, and thenloadFromString()built a second one from the same string. Double the CPU and double the peak memory on every single load.KmlValidatorgainedvalidateDocument(SimpleXMLElement $xml), which validates an already parsed document.KmlParserparses once and passes the result.validate(string $content)keeps its existing signature and delegates, so direct users of the validator are unaffected.4. Global libxml state was never restored
Both classes called
libxml_use_internal_errors(true)and never put it back. That is process-global: after one KML parse, every other library in the application using libxml (DOM, XMLReader, an XML HTTP client) silently stopped emitting warnings for the rest of the request.The previous value is now captured and restored in a
finally, on the success and the failure path.Breaking change
Malformed XML previously threw:
and now throws:
Still
KmlParserException, different message prefix. Anything matching on the old string needs updating.tests/ExceptionsTest.phpis updated accordingly.Validation failures previously threw
KmlParserExceptionand now throw the underlyingKmlException. SinceKmlParserException extends KmlException, anycatch (KmlException)keeps working; acatch (KmlParserException)around a validation failure does not. That is the point of the fix, but it belongs in the changelog.KmlParserException::failedToParse()is no longer thrown by anything. Marked@deprecatedrather than deleted, so callers referencing it keep working for one cycle.Tests
tests/ParsingErrorsTest.php, all five failing onmain:KmlException, notKmlParserException, with its own unwrapped messageKmlParserExceptionprefixedXML parsing error:validateDocument()validates a pre-parsed documentPint and PHPStan clean.