From 6b8b70507c3c1bf3a4e55eafed6f977b9d6b2971 Mon Sep 17 00:00:00 2001 From: Markus Thielen Date: Thu, 16 Jan 2025 12:23:51 +0100 Subject: [PATCH] With nullable typed readonly properties (from PHP 8.1+) we face situations where properties are not set after deserialization (Typed property...must not be accessed before initialization). This subscriber helps to handle these cases by adding properties to the payload so that they end up being null. --- src/CXml/Model/ItemDetail.php | 2 +- .../Message/PunchOutOrderMessageHeader.php | 2 +- src/CXml/Model/MultilanguageString.php | 2 +- src/CXml/Model/Request/OrderRequestHeader.php | 18 +++---- tests/CXmlTest/Model/SerializerTest.php | 50 +++++++++++++++++++ 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/CXml/Model/ItemDetail.php b/src/CXml/Model/ItemDetail.php index 71eb5fb..fa41d84 100644 --- a/src/CXml/Model/ItemDetail.php +++ b/src/CXml/Model/ItemDetail.php @@ -48,7 +48,7 @@ protected function __construct( private readonly MoneyWrapper $unitPrice, #[Serializer\SerializedName('PriceBasisQuantity')] #[Serializer\XmlElement(cdata: false)] - private readonly ?PriceBasisQuantity $priceBasisQuantity, + private readonly ?PriceBasisQuantity $priceBasisQuantity = null, ) { } diff --git a/src/CXml/Model/Message/PunchOutOrderMessageHeader.php b/src/CXml/Model/Message/PunchOutOrderMessageHeader.php index 2ca28f8..eeeaa8c 100644 --- a/src/CXml/Model/Message/PunchOutOrderMessageHeader.php +++ b/src/CXml/Model/Message/PunchOutOrderMessageHeader.php @@ -23,7 +23,7 @@ class PunchOutOrderMessageHeader final public const OPERATION_INSPECT = 'inspect'; #[Serializer\XmlAttribute] - private readonly ?string $operationAllowed; + private ?string $operationAllowed = null; /* cant be 'readonly' bc must be initialized with null -> jms deserialization */ #[Serializer\SerializedName('ShipTo')] private ?ShipTo $shipTo = null; diff --git a/src/CXml/Model/MultilanguageString.php b/src/CXml/Model/MultilanguageString.php index 6d41bb4..e4462d8 100644 --- a/src/CXml/Model/MultilanguageString.php +++ b/src/CXml/Model/MultilanguageString.php @@ -10,7 +10,7 @@ class MultilanguageString { public function __construct( #[Serializer\XmlValue(cdata: false)] - private readonly ?string $value, + private readonly ?string $value = null, #[Serializer\XmlAttribute] private readonly ?string $type = null, #[Serializer\XmlAttribute(namespace: 'http://www.w3.org/XML/1998/namespace')] diff --git a/src/CXml/Model/Request/OrderRequestHeader.php b/src/CXml/Model/Request/OrderRequestHeader.php index f601963..2ac534f 100644 --- a/src/CXml/Model/Request/OrderRequestHeader.php +++ b/src/CXml/Model/Request/OrderRequestHeader.php @@ -43,6 +43,10 @@ class OrderRequestHeader #[Serializer\Type('array')] private array $businessPartners; + #[Serializer\XmlElement] + #[Serializer\SerializedName('ShipTo')] + private ?ShipTo $shipTo = null; /* cant be 'readonly' bc must be initialized with null -> jms deserialization */ + protected function __construct( #[Serializer\XmlAttribute] #[Serializer\SerializedName('orderID')] @@ -50,9 +54,7 @@ protected function __construct( #[Serializer\XmlAttribute] #[Serializer\SerializedName('orderDate')] private readonly DateTimeInterface $orderDate, - #[Serializer\XmlElement] - #[Serializer\SerializedName('ShipTo')] - private readonly ?ShipTo $shipTo, + ?ShipTo $shipTo, /* cant be 'readonly' bc must be initialized with null -> jms deserialization */ #[Serializer\XmlElement] #[Serializer\SerializedName('BillTo')] private readonly BillTo $billTo, @@ -67,15 +69,11 @@ protected function __construct( #[Serializer\XmlList(entry: 'Contact', inline: true)] private ?array $contacts = null, ) { - if (null === $contacts) { - return; - } + $this->shipTo = $shipTo; - if ([] === $contacts) { - return; + if (null !== $contacts) { + Assertion::allIsInstanceOf($contacts, Contact::class); } - - Assertion::allIsInstanceOf($contacts, Contact::class); } public static function create( diff --git a/tests/CXmlTest/Model/SerializerTest.php b/tests/CXmlTest/Model/SerializerTest.php index 786f534..8dcc3ee 100644 --- a/tests/CXmlTest/Model/SerializerTest.php +++ b/tests/CXmlTest/Model/SerializerTest.php @@ -410,4 +410,54 @@ public function testDeserializeOneRowXml(): void $this->assertXmlStringEqualsXmlString($xml, $resultingXml); } + + public function testDeserializeNullProperty(): void + { + $xml = + ' + +
+ + + admin@acme.com + + + + + 012345678 + + + + + sysadmin@buyer.com + abracadabra + + Network Hub 1.1 + +
+ + + + + 0.00 + + +
+ name +
+
+
+
+
+
'; + + $cxml = Serializer::create()->deserialize($xml); + + /** @var OrderRequest $orderRequest */ + $orderRequest = $cxml->getRequest()->getPayload(); + + // Error: Typed property CXml\Model\Request\OrderRequestHeader::$shipTo must not be accessed before initialization + $shipTo = $orderRequest->getOrderRequestHeader()->getShipTo(); + $this->assertNull($shipTo); + } }