Skip to content

Commit 3406fa6

Browse files
committed
Add SOAP_USE_DATETIME_OBJECT feature flag.
Opt-in, decodes xsd:dateTime/date/time to DateTimeImmutable on both SoapClient and SoapServer. Typemap entries still take precedence.
1 parent d3d2f98 commit 3406fa6

7 files changed

Lines changed: 220 additions & 4 deletions

File tree

ext/soap/php_encoding.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ static xmlNodePtr to_xml_duration(encodeTypePtr type, zval *data, int style, xml
7070

7171
static zval *to_zval_object(zval *ret, encodeTypePtr type, xmlNodePtr data);
7272
static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data);
73+
static zval *to_zval_datetime(zval *ret, encodeTypePtr type, xmlNodePtr data);
7374

7475
static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
7576
static xmlNodePtr to_xml_array(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);
@@ -140,9 +141,9 @@ encode defaultEncoding[] = {
140141
{{XSD_FLOAT, XSD_FLOAT_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_double, to_xml_double},
141142
{{XSD_DOUBLE, XSD_DOUBLE_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_double, to_xml_double},
142143

143-
{{XSD_DATETIME, XSD_DATETIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_datetime},
144-
{{XSD_TIME, XSD_TIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_time},
145-
{{XSD_DATE, XSD_DATE_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_date},
144+
{{XSD_DATETIME, XSD_DATETIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_datetime, to_xml_datetime},
145+
{{XSD_TIME, XSD_TIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_datetime, to_xml_time},
146+
{{XSD_DATE, XSD_DATE_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_datetime, to_xml_date},
146147
{{XSD_GYEARMONTH, XSD_GYEARMONTH_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_gyearmonth},
147148
{{XSD_GYEAR, XSD_GYEAR_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_gyear},
148149
{{XSD_GMONTHDAY, XSD_GMONTHDAY_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_gmonthday},
@@ -1616,6 +1617,27 @@ static zval *to_zval_object(zval *ret, encodeTypePtr type, xmlNodePtr data)
16161617
return to_zval_object_ex(ret, type, data, NULL);
16171618
}
16181619

1620+
static zval *to_zval_datetime(zval *ret, encodeTypePtr type, xmlNodePtr data)
1621+
{
1622+
to_zval_stringc(ret, type, data);
1623+
1624+
if (!(SOAP_GLOBAL(features) & SOAP_USE_DATETIME_OBJECT)) {
1625+
return to_zval_stringc(ret, type, data);
1626+
}
1627+
1628+
zend_string *str = zend_string_copy(Z_STR_P(ret));
1629+
zval_ptr_dtor_str(ret);
1630+
php_date_instantiate(php_date_get_immutable_ce(), ret);
1631+
if (!php_date_initialize(Z_PHPDATE_P(ret), ZSTR_VAL(str), ZSTR_LEN(str), NULL, NULL, 0)) {
1632+
zval_ptr_dtor(ret);
1633+
ZVAL_STR(ret, str);
1634+
} else {
1635+
zend_string_release(str);
1636+
}
1637+
1638+
return ret;
1639+
}
1640+
16191641

16201642
static int model_to_xml_object(xmlNodePtr node, sdlContentModelPtr model, zval *object, int style, int strict)
16211643
{

ext/soap/php_soap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ struct _soapService {
136136
#define SOAP_SINGLE_ELEMENT_ARRAYS (1<<0)
137137
#define SOAP_WAIT_ONE_WAY_CALLS (1<<1)
138138
#define SOAP_USE_XSI_ARRAY_TYPE (1<<2)
139+
#define SOAP_USE_DATETIME_OBJECT (1<<3)
139140

140141
#define WSDL_CACHE_NONE 0x0
141142
#define WSDL_CACHE_DISK 0x1

ext/soap/soap.stub.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ final class Sdl
400400
* @cvalue SOAP_USE_XSI_ARRAY_TYPE
401401
*/
402402
const SOAP_USE_XSI_ARRAY_TYPE = UNKNOWN;
403+
/**
404+
* @var int
405+
* @cvalue SOAP_USE_DATETIME_OBJECT
406+
*/
407+
const SOAP_USE_DATETIME_OBJECT = UNKNOWN;
403408

404409
/**
405410
* @var int

ext/soap/soap_arginfo.h

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/soap/tests/gh21981_client.phpt

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--TEST--
2+
GH-21981 (SoapClient: SOAP_USE_DATETIME_OBJECT decodes xsd:dateTime/date/time into DateTimeImmutable)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--FILE--
8+
<?php
9+
class StubClient extends SoapClient {
10+
public string $canned;
11+
public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false, ?string $uriParserClass = null): ?string {
12+
return $this->canned;
13+
}
14+
}
15+
16+
function envelope(string $body): string {
17+
return <<<XML
18+
<?xml version="1.0" encoding="UTF-8"?>
19+
<SOAP-ENV:Envelope
20+
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
21+
xmlns:ns1="urn:test"
22+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
23+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
24+
<SOAP-ENV:Body>
25+
<ns1:fooResponse>{$body}</ns1:fooResponse>
26+
</SOAP-ENV:Body>
27+
</SOAP-ENV:Envelope>
28+
XML;
29+
}
30+
31+
function show($value): void {
32+
if (is_object($value)) {
33+
printf("%s(%s)" . PHP_EOL, get_class($value), $value->format('Y-m-d\\TH:i:s.uP'));
34+
} else {
35+
var_dump($value);
36+
}
37+
}
38+
39+
$opts_off = ['location' => 'test://', 'uri' => 'urn:test'];
40+
$opts_on = ['location' => 'test://', 'uri' => 'urn:test', 'features' => SOAP_USE_DATETIME_OBJECT];
41+
42+
echo "--- flag OFF: xsd:dateTime stays a string ---" . PHP_EOL;
43+
$c = new StubClient(null, $opts_off);
44+
$c->canned = envelope('<r xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</r>');
45+
show($c->foo());
46+
47+
echo "--- flag ON: xsd:dateTime -> DateTimeImmutable ---" . PHP_EOL;
48+
$c = new StubClient(null, $opts_on);
49+
$c->canned = envelope('<r xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</r>');
50+
show($c->foo());
51+
52+
echo "--- flag ON: xsd:dateTime with offset ---" . PHP_EOL;
53+
$c = new StubClient(null, $opts_on);
54+
$c->canned = envelope('<r xsi:type="xsd:dateTime">2026-05-09T12:34:56+02:00</r>');
55+
show($c->foo());
56+
57+
echo "--- flag ON: xsd:date -> DateTimeImmutable ---" . PHP_EOL;
58+
$c = new StubClient(null, $opts_on);
59+
$c->canned = envelope('<r xsi:type="xsd:date">2026-05-09</r>');
60+
show($c->foo());
61+
62+
echo "--- flag ON: xsd:time -> DateTimeImmutable ---" . PHP_EOL;
63+
$c = new StubClient(null, $opts_on);
64+
$c->canned = envelope('<r xsi:type="xsd:time">12:34:56Z</r>');
65+
show($c->foo());
66+
67+
echo "--- flag ON: malformed dateTime -> graceful string fallback ---" . PHP_EOL;
68+
$c = new StubClient(null, $opts_on);
69+
$c->canned = envelope('<r xsi:type="xsd:dateTime">not-a-date</r>');
70+
show($c->foo());
71+
72+
echo "--- flag ON: xsi:nil -> NULL ---" . PHP_EOL;
73+
$c = new StubClient(null, $opts_on);
74+
$c->canned = envelope('<r xsi:type="xsd:dateTime" xsi:nil="true"/>');
75+
show($c->foo());
76+
?>
77+
--EXPECTF--
78+
--- flag OFF: xsd:dateTime stays a string ---
79+
string(27) "2026-05-09T12:34:56.123456Z"
80+
--- flag ON: xsd:dateTime -> DateTimeImmutable ---
81+
DateTimeImmutable(2026-05-09T12:34:56.123456+00:00)
82+
--- flag ON: xsd:dateTime with offset ---
83+
DateTimeImmutable(2026-05-09T12:34:56.000000+02:00)
84+
--- flag ON: xsd:date -> DateTimeImmutable ---
85+
DateTimeImmutable(2026-05-09T00:00:00.000000%s)
86+
--- flag ON: xsd:time -> DateTimeImmutable ---
87+
DateTimeImmutable(%s12:34:56.000000+00:00)
88+
--- flag ON: malformed dateTime -> graceful string fallback ---
89+
string(10) "not-a-date"
90+
--- flag ON: xsi:nil -> NULL ---
91+
NULL

ext/soap/tests/gh21981_server.phpt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
GH-21981 (SoapServer: SOAP_USE_DATETIME_OBJECT decodes incoming xsd:dateTime arguments into DateTimeImmutable)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--FILE--
8+
<?php
9+
function consume($d) {
10+
if (is_object($d)) {
11+
return get_class($d) . '(' . $d->format('Y-m-d\\TH:i:s.uP') . ')';
12+
}
13+
return gettype($d) . '(' . var_export($d, true) . ')';
14+
}
15+
16+
$request = <<<XML
17+
<?xml version="1.0" encoding="UTF-8"?>
18+
<SOAP-ENV:Envelope
19+
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
20+
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
21+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
22+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
23+
<SOAP-ENV:Body>
24+
<ns1:consume xmlns:ns1="urn:test">
25+
<when xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456+02:00</when>
26+
</ns1:consume>
27+
</SOAP-ENV:Body>
28+
</SOAP-ENV:Envelope>
29+
XML;
30+
31+
echo "--- flag OFF: handler receives a string ---" . PHP_EOL;
32+
$server = new SoapServer(null, ['uri' => 'urn:test']);
33+
$server->addFunction('consume');
34+
$server->handle($request);
35+
36+
echo "--- flag ON: handler receives a DateTimeImmutable ---" . PHP_EOL;
37+
$server = new SoapServer(null, ['uri' => 'urn:test', 'features' => SOAP_USE_DATETIME_OBJECT]);
38+
$server->addFunction('consume');
39+
$server->handle($request);
40+
?>
41+
--EXPECT--
42+
--- flag OFF: handler receives a string ---
43+
<?xml version="1.0" encoding="UTF-8"?>
44+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:consumeResponse><return xsi:type="xsd:string">string('2026-05-09T12:34:56.123456+02:00')</return></ns1:consumeResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
45+
--- flag ON: handler receives a DateTimeImmutable ---
46+
<?xml version="1.0" encoding="UTF-8"?>
47+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:consumeResponse><return xsi:type="xsd:string">DateTimeImmutable(2026-05-09T12:34:56.123456+02:00)</return></ns1:consumeResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--TEST--
2+
GH-21981 (User typemap from_xml still wins over SOAP_USE_DATETIME_OBJECT)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--FILE--
8+
<?php
9+
class StubClient extends SoapClient {
10+
public string $canned;
11+
public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false, ?string $uriParserClass = null): ?string {
12+
return $this->canned;
13+
}
14+
}
15+
16+
function from_xml_dt(string $xml): string {
17+
return 'typemap-handled:' . $xml;
18+
}
19+
20+
$canned = <<<XML
21+
<?xml version="1.0" encoding="UTF-8"?>
22+
<SOAP-ENV:Envelope
23+
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
24+
xmlns:ns1="urn:test"
25+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
26+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
27+
<SOAP-ENV:Body>
28+
<ns1:fooResponse><r xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</r></ns1:fooResponse>
29+
</SOAP-ENV:Body>
30+
</SOAP-ENV:Envelope>
31+
XML;
32+
33+
$opts = [
34+
'location' => 'test://',
35+
'uri' => 'urn:test',
36+
'features' => SOAP_USE_DATETIME_OBJECT,
37+
'typemap' => [[
38+
'type_ns' => 'http://www.w3.org/2001/XMLSchema',
39+
'type_name' => 'dateTime',
40+
'from_xml' => 'from_xml_dt',
41+
]],
42+
];
43+
44+
$c = new StubClient(null, $opts);
45+
$c->canned = $canned;
46+
var_dump($c->foo());
47+
?>
48+
--EXPECTF--
49+
string(%d) "typemap-handled:<r xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:dateTime"%A>2026-05-09T12:34:56.123456Z</r>%A"

0 commit comments

Comments
 (0)