php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80895 Unexpected XML due to memory optimization in SOAP
Submitted: 2021-03-22 18:28 UTC Modified: 2021-08-18 11:16 UTC
From: vasilevrvv at gmail dot com Assigned: cmb (profile)
Status: Duplicate Package: SOAP related
PHP Version: 8.0.3 OS: Linux
Private report: No CVE-ID: None
 [2021-03-22 18:28 UTC] vasilevrvv at gmail dot com
Description:
------------
The problem is that when copying an array (not in pass by reference case), ref elements are created in XML.

!!! This is due to memory optimization in PHP, since in fact the array is copied only when it changes !!!

We get unexpected results:

The problem is solved by adding the line

$returnAddressee['address'] = $returnAddressee['address'];

as first cycle line.

At this point, the actual copying of the data took place. Reference was broken.

After add we get correct result.

This is unexpected behavior. Behavior should not depend on the internal "magic" of the PHP, it should be logical. Either always pass the same data by reference, or if the data would have been explicitly passed by reference (perfectably)

Test script:
---------------
private function test(Request\Shipment $shipment, array $returnAddressee): array
{
    $items = [];
    foreach ($shipment->parcels as $k => $parcel) {
        $items[] = array(
        'receiverAddressee' => $this->createReceiverAddressee($shipment->consignee),
        'returnAddressee' => $returnAddressee
        );
    }
    
    return $items;
}

Unfortunately, I cannot give a complete script for testing, since it depends on the WSDL service.

Expected result:
----------------
<item_list>
    <item service="CA">
        <receiverAddressee>
            <address postcode="13240" deliverypoint="La Solana" country="RU"
                street="Calle Empedrada 45" />
        </receiverAddressee>
        <returnAddressee>
            <address postcode="123123" deliverypoint="Test" country="EE" street="Test 22" />
        </returnAddressee>
    </item>
    <item service="CA">
        <receiverAddressee>
            <address postcode="13240" deliverypoint="La Solana" country="RU"
                street="Calle Empedrada 45" />
        </receiverAddressee>
        <returnAddressee>
            <address postcode="123123" deliverypoint="Test" country="EE" street="Test 22" />
        </returnAddressee>
    </item>
</item_list>

Actual result:
--------------
<item_list>
    <item service="CA">
        <receiverAddressee>
            <address postcode="13240" deliverypoint="La Solana" country="RU"
                street="Calle Empedrada 45" />
        </receiverAddressee>
        <returnAddressee>
            <address postcode="123123" deliverypoint="Test" country="EE" street="Test 22"
                id="ref1" />
        </returnAddressee>
    </item>
    <item service="CA">
        <receiverAddressee>
            <address postcode="13240" deliverypoint="La Solana" country="RU"
                street="Calle Empedrada 45" />
        </receiverAddressee>
        <returnAddressee>
            <address href="#ref1" />
        </returnAddressee>
    </item>
</item_list>

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-03-24 11:42 UTC] cmb@php.net
-Status: Open +Status: Feedback -Assigned To: +Assigned To: cmb
 [2021-03-24 11:42 UTC] cmb@php.net
Copy on write and references are different concepts.  While the
former is transparent (aka "magic"), the latter is not, and it
seems to me that $returnAddressee contains references.  You can
verify that with

    var_dump($returnAddresse);

Cf. <https://3v4l.org/iaqnA>
 [2021-03-24 12:55 UTC] vasilevrvv at gmail dot com
No, $returnAddressee do not contain references. Its just json_decode() result.

Inside php since version 7.x, arrays are always stored by links, they are copied only when you try to change them. this is internal logic that is not visible to the end programmer

The problem is solved by the fact that I just copy the same value into it. PHP inside takes this as an attempt to change and then copies the array in memory.

Fix code:

$returnAddressee['address'] = $returnAddressee['address'];

Helps not only it, but any change in the array, of any other field.

I think that after the release of these optimizations in PHP, this was simply not taken into account in the SOAP module

var_dump:

array(4) {
  ["person_name"]=>
  string(29) "NAME"
  ["phone"]=>
  string(11) "+372123123123"
  ["email"]=>
  string(16) "sales@testcompany.com"
  ["address"]=>
  array(4) {
    ["postcode"]=>
    string(5) "12312"
    ["deliverypoint"]=>
    string(5) "Narva"
    ["country"]=>
    string(2) "EE"
    ["street"]=>
    string(13) "Test 21"
  }
}
 [2021-03-24 13:14 UTC] vasilevrvv at gmail dot com
For example, let's change not the address, but another variable, this also helped. And add call xdebug_debug_zval before and after call:

xdebug_debug_zval('returnAddressee');
$returnAddressee['person_name'] = $returnAddressee['person_name'];
xdebug_debug_zval('returnAddressee');

Before:

(refcount=4, is_ref=0)
array (size=4)
  'person_name' => (refcount=1, is_ref=0)string ',,,,,,

Here refcount=4

Call:

$returnAddressee['person_name'] = $returnAddressee['person_name'];

After

(refcount=1, is_ref=0)
array (size=4)
  'person_name' => (refcount=2, is_ref=0)string '.....

Here refcount=1

When the array changes, it becomes another array, this is what causes the problem
 [2021-03-24 13:31 UTC] vasilevrvv at gmail dot com
each pass of the foreach is the same zval, before trying to change it. and the soap library takes this as a reference, but it is not a reference.
 [2021-03-24 13:57 UTC] vasilevrvv at gmail dot com
-Status: Feedback +Status: Closed
 [2021-03-24 13:57 UTC] vasilevrvv at gmail dot com
Hmm, in php5, the behavior is generally differen ti need to research
 [2021-03-24 14:26 UTC] cmb@php.net
Well, apparently ext/soap indeed not only encodes references as
multi-reference values, but also zvals with refcount > 1.  The
latter doesn't look right to me for arrays, because these are
value types in PHP, but not necessarily in other languages.
 [2021-03-24 14:33 UTC] vasilevrvv at gmail dot com
-Status: Closed +Status: Assigned
 [2021-03-24 14:33 UTC] vasilevrvv at gmail dot com
I think this is not obvious behavior. To avoid references - I have to clone the object in the code. Maybe it's better to change this logic?
 [2021-03-24 14:38 UTC] vasilevrvv at gmail dot com
Oh, not an object, an array, I'm sorry.
 [2021-03-24 16:14 UTC] cmb@php.net
-Status: Assigned +Status: Open -Assigned To: cmb +Assigned To:
 [2021-03-24 16:14 UTC] cmb@php.net
> Maybe it's better to change this logic?

Maybe, but I'm not sure.  I don't see a problem when both client
and server are implemented in PHP, but perhaps there are issues if
the server is implemented in, for instance, Java.  I'll leave that
for some SOAP expert to decide.
 [2021-03-24 16:20 UTC] vasilevrvv at gmail dot com
We are faced with the problem that the server does not support these refs, and there is no way to disable them, except for copying arrays.

I believe that this is not correct, that my code depends on the logic of the zval.

Yes, thank you
 [2021-03-24 16:29 UTC] vasilevrvv at gmail dot com
Maybe it would be enough to do it as not required option
 [2021-08-18 11:16 UTC] cmb@php.net
-Status: Open +Status: Duplicate -Assigned To: +Assigned To: cmb
 [2021-08-18 11:16 UTC] cmb@php.net
> We are faced with the problem that the server does not support
> these refs,

Ah, so this is a duplicate of bug #42652.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 01:01:30 2024 UTC