Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions ext/soap/php_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ static xmlNodePtr to_xml_duration(encodeTypePtr type, zval *data, int style, xml

static zval *to_zval_object(zval *ret, encodeTypePtr type, xmlNodePtr data);
static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data);
static zval *to_zval_datetime(zval *ret, encodeTypePtr type, xmlNodePtr data);

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

{{XSD_DATETIME, XSD_DATETIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_datetime},
{{XSD_TIME, XSD_TIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_time},
{{XSD_DATE, XSD_DATE_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_date},
{{XSD_DATETIME, XSD_DATETIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_datetime, to_xml_datetime},
{{XSD_TIME, XSD_TIME_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_datetime, to_xml_time},
{{XSD_DATE, XSD_DATE_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_datetime, to_xml_date},
{{XSD_GYEARMONTH, XSD_GYEARMONTH_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_gyearmonth},
{{XSD_GYEAR, XSD_GYEAR_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_gyear},
{{XSD_GMONTHDAY, XSD_GMONTHDAY_STRING, XSD_NAMESPACE, NULL, NULL, NULL}, to_zval_stringc, to_xml_gmonthday},
Expand Down Expand Up @@ -1616,6 +1617,27 @@ static zval *to_zval_object(zval *ret, encodeTypePtr type, xmlNodePtr data)
return to_zval_object_ex(ret, type, data, NULL);
}

static zval *to_zval_datetime(zval *ret, encodeTypePtr type, xmlNodePtr data)
{
to_zval_stringc(ret, type, data);

if (!(SOAP_GLOBAL(features) & SOAP_USE_DATETIME_OBJECT) || Z_TYPE_P(ret) != IS_STRING) {
return ret;
}

zend_string *str = zend_string_copy(Z_STR_P(ret));
zval_ptr_dtor_str(ret);
php_date_instantiate(php_date_get_immutable_ce(), ret);
if (!php_date_initialize(Z_PHPDATE_P(ret), ZSTR_VAL(str), ZSTR_LEN(str), NULL, NULL, 0)) {
zval_ptr_dtor(ret);
ZVAL_STR(ret, str);
} else {
zend_string_release(str);
}

return ret;
}


static int model_to_xml_object(xmlNodePtr node, sdlContentModelPtr model, zval *object, int style, int strict)
{
Expand Down Expand Up @@ -2816,7 +2838,11 @@ static xmlNodePtr guess_xml_convert(encodeTypePtr type, zval *data, int style, x
xmlNodePtr ret;

if (data) {
enc = get_conversion(Z_TYPE_P(data));
if (Z_TYPE_P(data) == IS_OBJECT && instanceof_function_slow(Z_OBJCE_P(data), php_date_get_interface_ce())) {
enc = get_conversion(XSD_DATETIME);
} else {
enc = get_conversion(Z_TYPE_P(data));
}
} else {
enc = get_conversion(IS_NULL);
}
Expand Down
1 change: 1 addition & 0 deletions ext/soap/php_soap.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ struct _soapService {
#define SOAP_SINGLE_ELEMENT_ARRAYS (1<<0)
#define SOAP_WAIT_ONE_WAY_CALLS (1<<1)
#define SOAP_USE_XSI_ARRAY_TYPE (1<<2)
#define SOAP_USE_DATETIME_OBJECT (1<<3)

#define WSDL_CACHE_NONE 0x0
#define WSDL_CACHE_DISK 0x1
Expand Down
5 changes: 5 additions & 0 deletions ext/soap/soap.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ final class Sdl
* @cvalue SOAP_USE_XSI_ARRAY_TYPE
*/
const SOAP_USE_XSI_ARRAY_TYPE = UNKNOWN;
/**
* @var int
* @cvalue SOAP_USE_DATETIME_OBJECT
*/
const SOAP_USE_DATETIME_OBJECT = UNKNOWN;

/**
* @var int
Expand Down
3 changes: 2 additions & 1 deletion ext/soap/soap_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions ext/soap/tests/gh21981.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--TEST--
GH-21981 (Returning a bare DateTimeInterface from a SoapServer should not require SoapVar)
--EXTENSIONS--
soap
--FILE--
<?php
function getCurrentDate() {
return new DateTime("2026-05-09T12:34:56.123456+00:00");
}

function getCurrentDateImmutable() {
return new DateTimeImmutable("2026-05-09T12:34:56.123456+00:00");
}

$request_wsdl = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://localhost"
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:getCurrentDate/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;

echo "--- WSDL with unresolved part type (xsd:datetime typo) ---\n";
$server = new SoapServer(__DIR__.'/gh21981.wsdl', ['cache_wsdl' => WSDL_CACHE_NONE]);
$server->addFunction('getCurrentDate');
$server->handle($request_wsdl);

$request_nowsdl = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<ns1:getCurrentDateImmutable xmlns:ns1="http://testuri.org" />
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;

echo "--- Non-WSDL server, DateTimeImmutable return ---\n";
$server = new SoapServer(null, ['uri' => 'http://testuri.org']);
$server->addFunction('getCurrentDateImmutable');
$server->handle($request_nowsdl);
?>
--EXPECT--
--- WSDL with unresolved part type (xsd:datetime typo) ---
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:xmethods-delayed-quotes" 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:getCurrentDateResponse><currentDate xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</currentDate></ns1:getCurrentDateResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
--- Non-WSDL server, DateTimeImmutable return ---
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://testuri.org" 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:getCurrentDateImmutableResponse><return xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</return></ns1:getCurrentDateImmutableResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
42 changes: 42 additions & 0 deletions ext/soap/tests/gh21981.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="GetCurrentDate"
targetNamespace="http://localhost"
xmlns:tns="http://localhost"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

<wsdl:message name="GetCurrentDateRequest" />
<wsdl:message name="GetCurrentDateResponse">
<wsdl:part name="currentDate" type="xsd:datetime" />
</wsdl:message>

<wsdl:portType name="GetCurrentDatePortType">
<wsdl:operation name="getCurrentDate">
<wsdl:input message="tns:GetCurrentDateRequest" />
<wsdl:output message="tns:GetCurrentDateResponse" />
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="GetCurrentDateBinding" type="tns:GetCurrentDatePortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="getCurrentDate">
<soap:operation soapAction="http://localhost#getCurrentDate" />
<wsdl:input>
<soap:body use="encoded" namespace="urn:xmethods-delayed-quotes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</wsdl:input>
<wsdl:output>
<soap:body use="encoded" namespace="urn:xmethods-delayed-quotes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="GetCurrentDateService">
<wsdl:port name="GetCurrentDatePortType" binding="tns:GetCurrentDateBinding">
<soap:address location="http://localhost/server.php" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
91 changes: 91 additions & 0 deletions ext/soap/tests/gh21981_client.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
--TEST--
GH-21981 (SoapClient: SOAP_USE_DATETIME_OBJECT decodes xsd:dateTime/date/time into DateTimeImmutable)
--EXTENSIONS--
soap
--INI--
soap.wsdl_cache_enabled=0
--FILE--
<?php
class StubClient extends SoapClient {
public string $canned;
public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false, ?string $uriParserClass = null): ?string {
return $this->canned;
}
}

function envelope(string $body): string {
return <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<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">
<SOAP-ENV:Body>
<ns1:fooResponse>{$body}</ns1:fooResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;
}

function show($value): void {
if (is_object($value)) {
printf("%s(%s)" . PHP_EOL, get_class($value), $value->format('Y-m-d\\TH:i:s.uP'));
} else {
var_dump($value);
}
}

$opts_off = ['location' => 'test://', 'uri' => 'urn:test'];
$opts_on = ['location' => 'test://', 'uri' => 'urn:test', 'features' => SOAP_USE_DATETIME_OBJECT];

echo "--- flag OFF: xsd:dateTime stays a string ---" . PHP_EOL;
$c = new StubClient(null, $opts_off);
$c->canned = envelope('<r xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</r>');
show($c->foo());

echo "--- flag ON: xsd:dateTime -> DateTimeImmutable ---" . PHP_EOL;
$c = new StubClient(null, $opts_on);
$c->canned = envelope('<r xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</r>');
show($c->foo());

echo "--- flag ON: xsd:dateTime with offset ---" . PHP_EOL;
$c = new StubClient(null, $opts_on);
$c->canned = envelope('<r xsi:type="xsd:dateTime">2026-05-09T12:34:56+02:00</r>');
show($c->foo());

echo "--- flag ON: xsd:date -> DateTimeImmutable ---" . PHP_EOL;
$c = new StubClient(null, $opts_on);
$c->canned = envelope('<r xsi:type="xsd:date">2026-05-09</r>');
show($c->foo());

echo "--- flag ON: xsd:time -> DateTimeImmutable ---" . PHP_EOL;
$c = new StubClient(null, $opts_on);
$c->canned = envelope('<r xsi:type="xsd:time">12:34:56Z</r>');
show($c->foo());

echo "--- flag ON: malformed dateTime -> graceful string fallback ---" . PHP_EOL;
$c = new StubClient(null, $opts_on);
$c->canned = envelope('<r xsi:type="xsd:dateTime">not-a-date</r>');
show($c->foo());

echo "--- flag ON: xsi:nil -> NULL ---" . PHP_EOL;
$c = new StubClient(null, $opts_on);
$c->canned = envelope('<r xsi:type="xsd:dateTime" xsi:nil="true"/>');
show($c->foo());
?>
--EXPECTF--
--- flag OFF: xsd:dateTime stays a string ---
string(27) "2026-05-09T12:34:56.123456Z"
--- flag ON: xsd:dateTime -> DateTimeImmutable ---
DateTimeImmutable(2026-05-09T12:34:56.123456+00:00)
--- flag ON: xsd:dateTime with offset ---
DateTimeImmutable(2026-05-09T12:34:56.000000+02:00)
--- flag ON: xsd:date -> DateTimeImmutable ---
DateTimeImmutable(2026-05-09T00:00:00.000000%s)
--- flag ON: xsd:time -> DateTimeImmutable ---
DateTimeImmutable(%s12:34:56.000000+00:00)
--- flag ON: malformed dateTime -> graceful string fallback ---
string(10) "not-a-date"
--- flag ON: xsi:nil -> NULL ---
NULL
47 changes: 47 additions & 0 deletions ext/soap/tests/gh21981_server.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
GH-21981 (SoapServer: SOAP_USE_DATETIME_OBJECT decodes incoming xsd:dateTime arguments into DateTimeImmutable)
--EXTENSIONS--
soap
--INI--
soap.wsdl_cache_enabled=0
--FILE--
<?php
function consume($d) {
if (is_object($d)) {
return get_class($d) . '(' . $d->format('Y-m-d\\TH:i:s.uP') . ')';
}
return gettype($d) . '(' . var_export($d, true) . ')';
}

$request = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<ns1:consume xmlns:ns1="urn:test">
<when xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456+02:00</when>
</ns1:consume>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;

echo "--- flag OFF: handler receives a string ---" . PHP_EOL;
$server = new SoapServer(null, ['uri' => 'urn:test']);
$server->addFunction('consume');
$server->handle($request);

echo "--- flag ON: handler receives a DateTimeImmutable ---" . PHP_EOL;
$server = new SoapServer(null, ['uri' => 'urn:test', 'features' => SOAP_USE_DATETIME_OBJECT]);
$server->addFunction('consume');
$server->handle($request);
?>
--EXPECT--
--- flag OFF: handler receives a string ---
<?xml version="1.0" encoding="UTF-8"?>
<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>
--- flag ON: handler receives a DateTimeImmutable ---
<?xml version="1.0" encoding="UTF-8"?>
<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>
49 changes: 49 additions & 0 deletions ext/soap/tests/gh21981_typemap.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
GH-21981 (User typemap from_xml still wins over SOAP_USE_DATETIME_OBJECT)
--EXTENSIONS--
soap
--INI--
soap.wsdl_cache_enabled=0
--FILE--
<?php
class StubClient extends SoapClient {
public string $canned;
public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false, ?string $uriParserClass = null): ?string {
return $this->canned;
}
}

function from_xml_dt(string $xml): string {
return 'typemap-handled:' . $xml;
}

$canned = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<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">
<SOAP-ENV:Body>
<ns1:fooResponse><r xsi:type="xsd:dateTime">2026-05-09T12:34:56.123456Z</r></ns1:fooResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;

$opts = [
'location' => 'test://',
'uri' => 'urn:test',
'features' => SOAP_USE_DATETIME_OBJECT,
'typemap' => [[
'type_ns' => 'http://www.w3.org/2001/XMLSchema',
'type_name' => 'dateTime',
'from_xml' => 'from_xml_dt',
]],
];

$c = new StubClient(null, $opts);
$c->canned = $canned;
var_dump($c->foo());
?>
--EXPECTF--
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"
Loading