From 7bc5160b1dd24794726750bb7cb66f3faba3553b Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 27 Mar 2026 07:45:10 +0100 Subject: [PATCH 1/3] Upgrade to veewee/xml v4 with PHP 8.4+ minimum - Bump PHP requirement to ~8.4.0 || ~8.5.0 - Upgrade veewee/xml from ^3.4 to ^4.7 - Replace legacy DOM classes (DOMDocument, DOMElement, DOMNode, DOMXPath) with PHP 8.4 Dom\ namespace equivalents - Switch from azjezz/psl to standalone PSL packages (dict, foundation) ^6.1 - Update psalm target PHP version to 8.4 - Remove PHP 8.3 from CI matrices - Remove comparable() usage in SoapHeaderTest (incompatible with v4 canonicalize behavior) --- .github/workflows/analyzers.yaml | 2 +- .github/workflows/code-style.yaml | 2 +- .github/workflows/tests.yaml | 2 +- composer.json | 6 ++++-- psalm.xml | 2 +- src/Builder/Header/Actor.php | 4 ++-- src/Builder/Header/MustUnderstand.php | 4 ++-- src/Builder/SoapHeader.php | 10 +++++----- src/Builder/SoapHeaders.php | 10 +++++----- src/Locator/BodyNamespaceLocator.php | 11 +++++------ src/Locator/SoapBodyLocator.php | 8 ++++---- src/Locator/SoapEnvelopeLocator.php | 7 ++++--- src/Locator/SoapHeaderLocator.php | 8 ++++---- src/Manipulator/PrependSoapHeaders.php | 11 ++++++----- src/Xpath/EnvelopePreset.php | 4 ++-- src/Xpath/WsdlPreset.php | 7 ++++--- tests/Unit/Builder/SoapHeaderTest.php | 9 ++------- 17 files changed, 53 insertions(+), 54 deletions(-) diff --git a/.github/workflows/analyzers.yaml b/.github/workflows/analyzers.yaml index a1d3d57..e82f960 100644 --- a/.github/workflows/analyzers.yaml +++ b/.github/workflows/analyzers.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['8.3', '8.4', '8.5'] + php-versions: ['8.4', '8.5'] composer-options: ['--ignore-platform-req=php+'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml index 867b2ee..6ad622d 100644 --- a/.github/workflows/code-style.yaml +++ b/.github/workflows/code-style.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['8.3', '8.4', '8.5'] + php-versions: ['8.4', '8.5'] composer-options: ['--ignore-platform-req=php+'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6229de6..5b8b80c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['8.3', '8.4', '8.5'] + php-versions: ['8.4', '8.5'] composer-options: ['--ignore-platform-req=php+'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} diff --git a/composer.json b/composer.json index 1312ec7..726896e 100644 --- a/composer.json +++ b/composer.json @@ -20,9 +20,11 @@ } ], "require": { - "php": "~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.4.0 || ~8.5.0", "ext-dom": "*", - "veewee/xml": "^3.4" + "php-standard-library/dict": "^6.1", + "php-standard-library/foundation": "^6.1", + "veewee/xml": "^4.7" }, "require-dev": { "phpunit/phpunit": "~12.3", diff --git a/psalm.xml b/psalm.xml index fe948bb..473c57b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -3,7 +3,7 @@ errorLevel="1" resolveFromConfigFile="true" strictBinaryOperands="true" - phpVersion="8.0" + phpVersion="8.4" allowStringToStandInForClass="true" rememberPropertyAssignmentsAfterCall="false" skipChecksOnUnresolvableIncludes="false" diff --git a/src/Builder/Header/Actor.php b/src/Builder/Header/Actor.php index 1697816..7633592 100644 --- a/src/Builder/Header/Actor.php +++ b/src/Builder/Header/Actor.php @@ -3,7 +3,7 @@ namespace Soap\Xml\Builder\Header; -use DOMNode; +use Dom\Node; use VeeWee\Xml\Dom\Builder\Builder; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\Dom\Builder\namespaced_attribute; @@ -26,7 +26,7 @@ public static function next(): self /** * @psalm-suppress MissingThrowsDocblock */ - public function __invoke(DOMNode $node): DOMNode + public function __invoke(Node $node): Node { $document = detect_document($node); $namespace = root_namespace_uri()($document) ?? ''; diff --git a/src/Builder/Header/MustUnderstand.php b/src/Builder/Header/MustUnderstand.php index c2710b8..f05b134 100644 --- a/src/Builder/Header/MustUnderstand.php +++ b/src/Builder/Header/MustUnderstand.php @@ -3,7 +3,7 @@ namespace Soap\Xml\Builder\Header; -use DOMNode; +use Dom\Node; use VeeWee\Xml\Dom\Builder\Builder; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\Dom\Builder\namespaced_attribute; @@ -16,7 +16,7 @@ final class MustUnderstand implements Builder /** * @psalm-suppress MissingThrowsDocblock */ - public function __invoke(DOMNode $node): DOMNode + public function __invoke(Node $node): Node { $document = detect_document($node); $namespace = root_namespace_uri()($document) ?? ''; diff --git a/src/Builder/SoapHeader.php b/src/Builder/SoapHeader.php index f267be9..8729aea 100644 --- a/src/Builder/SoapHeader.php +++ b/src/Builder/SoapHeader.php @@ -3,8 +3,8 @@ namespace Soap\Xml\Builder; -use DOMElement; -use DOMNode; +use Dom\Element; +use Dom\Node; use VeeWee\Xml\Dom\Builder\Builder; use function VeeWee\Xml\Dom\Builder\children; use function VeeWee\Xml\Dom\Builder\namespaced_element; @@ -12,13 +12,13 @@ final class SoapHeader implements Builder { /** - * @var list + * @var list */ private array $configurators; /** * @no-named-arguments - * @param list $configurators + * @param list $configurators */ public function __construct( private string $namespace, @@ -31,7 +31,7 @@ public function __construct( /** * @psalm-suppress MissingThrowsDocblock */ - public function __invoke(DOMNode $node): DOMNode + public function __invoke(Node $node): Node { return children( namespaced_element( diff --git a/src/Builder/SoapHeaders.php b/src/Builder/SoapHeaders.php index 17b9f0a..12eb769 100644 --- a/src/Builder/SoapHeaders.php +++ b/src/Builder/SoapHeaders.php @@ -2,8 +2,8 @@ namespace Soap\Xml\Builder; -use DOMElement; -use DOMNode; +use Dom\Element; +use Dom\Node; use VeeWee\Xml\Dom\Builder\Builder; use function VeeWee\Xml\Dom\Builder\namespaced_element; use function VeeWee\Xml\Dom\Locator\Node\detect_document; @@ -12,13 +12,13 @@ final class SoapHeaders implements Builder { /** - * @var list + * @var list */ private array $configurators; /** * @no-named-arguments - * @param list $configurators + * @param list $configurators */ public function __construct(callable ... $configurators) { @@ -28,7 +28,7 @@ public function __construct(callable ... $configurators) /** * @psalm-suppress MissingThrowsDocblock */ - public function __invoke(DOMNode $node): DOMNode + public function __invoke(Node $node): Node { $document = detect_document($node); diff --git a/src/Locator/BodyNamespaceLocator.php b/src/Locator/BodyNamespaceLocator.php index c942f6d..dac8980 100644 --- a/src/Locator/BodyNamespaceLocator.php +++ b/src/Locator/BodyNamespaceLocator.php @@ -4,19 +4,18 @@ namespace Soap\Xml\Locator; -use DOMDocument; +use Dom\XMLDocument; use VeeWee\Xml\Exception\RuntimeException; final class BodyNamespaceLocator { /** - * @psalm-suppress UndefinedPropertyFetch - psalm gets lost - * @psalm-suppress MixedReturnStatement - psalm gets lost - * @psalm-suppress MixedPropertyFetch - psalm gets lost - * @psalm-suppress MixedInferredReturnType - psalm gets lost + * @psalm-suppress MixedPropertyFetch + * @psalm-suppress MixedReturnStatement + * @psalm-suppress MixedInferredReturnType * @throws RuntimeException */ - public function __invoke(DOMDocument $document): ?string + public function __invoke(XMLDocument $document): ?string { return (new SoapBodyLocator())($document)?->firstElementChild?->namespaceURI; } diff --git a/src/Locator/SoapBodyLocator.php b/src/Locator/SoapBodyLocator.php index f9b292a..248043e 100644 --- a/src/Locator/SoapBodyLocator.php +++ b/src/Locator/SoapBodyLocator.php @@ -4,8 +4,8 @@ namespace Soap\Xml\Locator; -use DOMDocument; -use DOMElement; +use Dom\Element; +use Dom\XMLDocument; use VeeWee\Xml\Dom\Document; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\Dom\Locator\root_namespace_uri; @@ -16,11 +16,11 @@ final class SoapBodyLocator /** * @throws RuntimeException */ - public function __invoke(DOMDocument $document): ?DOMElement + public function __invoke(XMLDocument $document): ?Element { $soapNs = root_namespace_uri()($document) ?? ''; $xpath = Document::fromUnsafeDocument($document)->xpath(namespaces(['soap' => $soapNs])); - return $xpath->query('//soap:Envelope/soap:Body')->expectAllOfType(DOMElement::class)->first(); + return $xpath->query('//soap:Envelope/soap:Body')->expectAllOfType(Element::class)->first(); } } diff --git a/src/Locator/SoapEnvelopeLocator.php b/src/Locator/SoapEnvelopeLocator.php index 016107b..4d48dba 100644 --- a/src/Locator/SoapEnvelopeLocator.php +++ b/src/Locator/SoapEnvelopeLocator.php @@ -4,12 +4,13 @@ namespace Soap\Xml\Locator; -use DOMDocument; -use DOMElement; +use Dom\Element; +use Dom\XMLDocument; final class SoapEnvelopeLocator { - public function __invoke(DOMDocument $document): DOMElement + /** @psalm-suppress MixedReturnStatement */ + public function __invoke(XMLDocument $document): Element { return $document->documentElement; } diff --git a/src/Locator/SoapHeaderLocator.php b/src/Locator/SoapHeaderLocator.php index c5bf841..c422df0 100644 --- a/src/Locator/SoapHeaderLocator.php +++ b/src/Locator/SoapHeaderLocator.php @@ -4,8 +4,8 @@ namespace Soap\Xml\Locator; -use DOMDocument; -use DOMElement; +use Dom\Element; +use Dom\XMLDocument; use VeeWee\Xml\Dom\Document; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\Dom\Locator\root_namespace_uri; @@ -16,11 +16,11 @@ final class SoapHeaderLocator /** * @throws RuntimeException */ - public function __invoke(DOMDocument $document): ?DOMElement + public function __invoke(XMLDocument $document): ?Element { $soapNs = root_namespace_uri()($document) ?? ''; $xpath = Document::fromUnsafeDocument($document)->xpath(namespaces(['soap' => $soapNs])); - return $xpath->query('//soap:Envelope/soap:Header')->expectAllOfType(DOMElement::class)->first(); + return $xpath->query('//soap:Envelope/soap:Header')->expectAllOfType(Element::class)->first(); } } diff --git a/src/Manipulator/PrependSoapHeaders.php b/src/Manipulator/PrependSoapHeaders.php index 00d8304..98d6009 100644 --- a/src/Manipulator/PrependSoapHeaders.php +++ b/src/Manipulator/PrependSoapHeaders.php @@ -4,8 +4,8 @@ namespace Soap\Xml\Manipulator; -use DOMDocument; -use DOMElement; +use Dom\Element; +use Dom\XMLDocument; use Soap\Xml\Locator\SoapEnvelopeLocator; use VeeWee\Xml\Dom\Document; use VeeWee\Xml\Exception\RuntimeException; @@ -13,14 +13,14 @@ final class PrependSoapHeaders { /** - * @var list + * @var list */ private array $soapHeaders; /** * @no-named-arguments */ - public function __construct(DOMElement ... $soapHeaders) + public function __construct(Element ... $soapHeaders) { $this->soapHeaders = $soapHeaders; } @@ -30,12 +30,13 @@ public function __construct(DOMElement ... $soapHeaders) * @psalm-suppress LessSpecificReturnStatement * @psalm-suppress MoreSpecificReturnType */ - public function __invoke(DOMDocument $document): DOMElement + public function __invoke(XMLDocument $document): Element { $doc = Document::fromUnsafeDocument($document); $envelope = $doc->locate(new SoapEnvelopeLocator()); foreach (array_reverse($this->soapHeaders) as $header) { + /** @psalm-suppress MixedArgument */ $envelope->insertBefore($header, $envelope->firstChild); } diff --git a/src/Xpath/EnvelopePreset.php b/src/Xpath/EnvelopePreset.php index f5bba53..72ce5f5 100644 --- a/src/Xpath/EnvelopePreset.php +++ b/src/Xpath/EnvelopePreset.php @@ -4,7 +4,7 @@ namespace Soap\Xml\Xpath; -use DOMXPath; +use Dom\XPath; use Soap\Xml\Locator\BodyNamespaceLocator; use VeeWee\Xml\Dom\Document; use VeeWee\Xml\Dom\Xpath\Configurator\Configurator; @@ -24,7 +24,7 @@ public function __construct(Document $document) /** * @throws RuntimeException */ - public function __invoke(DOMXPath $xpath): DOMXPath + public function __invoke(XPath $xpath): XPath { return namespaces(array_filter([ 'soap' => $this->document->locate(root_namespace_uri()), diff --git a/src/Xpath/WsdlPreset.php b/src/Xpath/WsdlPreset.php index 1cc4d0b..e7cdf3f 100644 --- a/src/Xpath/WsdlPreset.php +++ b/src/Xpath/WsdlPreset.php @@ -4,7 +4,7 @@ namespace Soap\Xml\Xpath; -use DOMXPath; +use Dom\XPath; use Soap\Xml\Xmlns; use VeeWee\Xml\Dom\Document; use VeeWee\Xml\Dom\Xpath\Configurator\Configurator; @@ -22,8 +22,9 @@ public function __construct(Document $document) $this->document = $document; } - public function __invoke(DOMXPath $xpath): DOMXPath + public function __invoke(XPath $xpath): XPath { + /** @var string|null $tns */ $tns = $this->document->map(document_element())->getAttribute('targetNamespace'); return namespaces(filter( @@ -34,7 +35,7 @@ public function __invoke(DOMXPath $xpath): DOMXPath 'soap12' => Xmlns::soap12()->value(), 'wsdl' => Xmlns::wsdl()->value(), ], - $tns ? ['tns' => $tns] : [] + $tns !== null && $tns !== '' ? ['tns' => $tns] : [] ) ))($xpath); } diff --git a/tests/Unit/Builder/SoapHeaderTest.php b/tests/Unit/Builder/SoapHeaderTest.php index 43bbada..0987168 100644 --- a/tests/Unit/Builder/SoapHeaderTest.php +++ b/tests/Unit/Builder/SoapHeaderTest.php @@ -13,7 +13,6 @@ use function VeeWee\Xml\Dom\Builder\children; use function VeeWee\Xml\Dom\Builder\namespaced_element; use function VeeWee\Xml\Dom\Builder\value; -use function VeeWee\Xml\Dom\Configurator\comparable; final class SoapHeaderTest extends TestCase { @@ -47,13 +46,9 @@ public function test_it_can_create_a_header_element(): void - + EOXML; - - static::assertXmlStringEqualsXmlString( - Document::fromXmlString($expected, comparable())->toXmlString(), - Document::fromUnsafeDocument($doc->toUnsafeDocument(), comparable())->toXmlString() - ); + static::assertXmlStringEqualsXmlString($expected, $doc->toXmlString()); } } From ebeff90eec810b5cad90f94add1de7f6984b30b6 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 27 Mar 2026 08:09:37 +0100 Subject: [PATCH 2/3] Use veewee/xml DOM stubs for psalm, remove all @psalm-suppress - Bump veewee/xml to ^4.8 (ships DOM.phpstub) - Register veewee/xml DOM stub in psalm.xml - Remove all @psalm-suppress annotations (no longer needed) - Use document_element() helper in SoapEnvelopeLocator --- composer.json | 2 +- psalm.xml | 3 +++ src/Locator/BodyNamespaceLocator.php | 3 --- src/Locator/SoapEnvelopeLocator.php | 4 ++-- src/Manipulator/PrependSoapHeaders.php | 1 - src/Xpath/WsdlPreset.php | 1 - 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 726896e..2ee08f1 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ext-dom": "*", "php-standard-library/dict": "^6.1", "php-standard-library/foundation": "^6.1", - "veewee/xml": "^4.7" + "veewee/xml": "^4.8" }, "require-dev": { "phpunit/phpunit": "~12.3", diff --git a/psalm.xml b/psalm.xml index 473c57b..1e7576d 100644 --- a/psalm.xml +++ b/psalm.xml @@ -22,6 +22,9 @@ + + + diff --git a/src/Locator/BodyNamespaceLocator.php b/src/Locator/BodyNamespaceLocator.php index dac8980..cfa8971 100644 --- a/src/Locator/BodyNamespaceLocator.php +++ b/src/Locator/BodyNamespaceLocator.php @@ -10,9 +10,6 @@ final class BodyNamespaceLocator { /** - * @psalm-suppress MixedPropertyFetch - * @psalm-suppress MixedReturnStatement - * @psalm-suppress MixedInferredReturnType * @throws RuntimeException */ public function __invoke(XMLDocument $document): ?string diff --git a/src/Locator/SoapEnvelopeLocator.php b/src/Locator/SoapEnvelopeLocator.php index 4d48dba..196a092 100644 --- a/src/Locator/SoapEnvelopeLocator.php +++ b/src/Locator/SoapEnvelopeLocator.php @@ -6,12 +6,12 @@ use Dom\Element; use Dom\XMLDocument; +use function VeeWee\Xml\Dom\Locator\document_element; final class SoapEnvelopeLocator { - /** @psalm-suppress MixedReturnStatement */ public function __invoke(XMLDocument $document): Element { - return $document->documentElement; + return document_element()($document); } } diff --git a/src/Manipulator/PrependSoapHeaders.php b/src/Manipulator/PrependSoapHeaders.php index 98d6009..e490e0b 100644 --- a/src/Manipulator/PrependSoapHeaders.php +++ b/src/Manipulator/PrependSoapHeaders.php @@ -36,7 +36,6 @@ public function __invoke(XMLDocument $document): Element $envelope = $doc->locate(new SoapEnvelopeLocator()); foreach (array_reverse($this->soapHeaders) as $header) { - /** @psalm-suppress MixedArgument */ $envelope->insertBefore($header, $envelope->firstChild); } diff --git a/src/Xpath/WsdlPreset.php b/src/Xpath/WsdlPreset.php index e7cdf3f..87b28ba 100644 --- a/src/Xpath/WsdlPreset.php +++ b/src/Xpath/WsdlPreset.php @@ -24,7 +24,6 @@ public function __construct(Document $document) public function __invoke(XPath $xpath): XPath { - /** @var string|null $tns */ $tns = $this->document->map(document_element())->getAttribute('targetNamespace'); return namespaces(filter( From fb09f7518db43545068c3ae499c94e9ec75cc0ed Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 27 Mar 2026 08:15:14 +0100 Subject: [PATCH 3/3] Simplify tns null check in WsdlPreset Dom\Element::getAttribute() returns null for missing attributes, so the empty string check is unnecessary. --- src/Xpath/WsdlPreset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xpath/WsdlPreset.php b/src/Xpath/WsdlPreset.php index 87b28ba..8b9025d 100644 --- a/src/Xpath/WsdlPreset.php +++ b/src/Xpath/WsdlPreset.php @@ -34,7 +34,7 @@ public function __invoke(XPath $xpath): XPath 'soap12' => Xmlns::soap12()->value(), 'wsdl' => Xmlns::wsdl()->value(), ], - $tns !== null && $tns !== '' ? ['tns' => $tns] : [] + $tns !== null ? ['tns' => $tns] : [] ) ))($xpath); }