Skip to content

SOAP XML broken since PHP 8.3.9 when using classmap constructor option #15252

Closed

Description

@brainfoolong

Description

A bit of a weird issue, hope you can follow.

I am on a windows machine, downloading following php packages:

https://windows.php.net/downloads/releases/archives/php-8.3.8-Win32-vs16-x64.zip
https://windows.php.net/downloads/releases/archives/php-8.3.9-Win32-vs16-x64.zip

When running the "test.php" from attached ZIP, i get different outputs, using no php.ini and just with soap extension enabled:
Test-Script: test.zip (You need the files from zip, it contains the WSDL and XSD files required.

PHP 8.3.8

D:\php-8.3.8-Win32-vs16-x64\php.exe -dextension=D:\php-8.3.8-Win32-vs16-x64\ext\php_soap.dll D:\test\test.php
PHPVERSION: 8.3.8
XML: <?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:ns2="http://schemas.microsoft.com/exchange/services/2006/messages"><SOAP-ENV:Body><ns2:DeleteItem><ns2:ItemIds><ns1:ItemId Id="AAMkAGJjMzNlMDA4LTU5NzMtNDZmMS1hN2QwLWE5NDJhOTg4ZTE3ZABGAAAAAADor6LtxqzcTZY8k3FzncNoBwBqStc3k+YrRrwI06yPhA+DAAAAAAEPAABqStc3k+YrRrwI06yPhA+DAASvw7+bAAA=" ChangeKey="CQAAABYAAABqStc3k+YrRrwI06yPhA+DAASv1Zxc"/></ns2:ItemIds></ns2:DeleteItem></SOAP-ENV:Body></SOAP-ENV:Envelope>

PHP 8.3.9 or even PHP 8.3.10

D:\php-8.3.9-Win32-vs16-x64\php.exe -dextension=D:\php-8.3.9-Win32-vs16-x64\ext\php_soap.dll D:\test\test.php
PHPVERSION: 8.3.9
XML: <?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://schemas.microsoft.com/exchange/services/2006/messages"><SOAP-ENV:Body><ns2:DeleteItem><ns2:ItemIds xsi:type="ns1:ItemIdType"/></ns2:DeleteItem></SOAP-ENV:Body></SOAP-ENV:Envelope>

Testscript (Notice you need the other files from zip attached)

<?php

namespace Testing\Soap;

$classMap = [
    'ItemIdType' => '\\Testing\\Soap\\ItemIdType',
    'DeleteItemType' => '\\Testing\\Soap\\DeleteItemType',
];

class ItemIdType{
    public $ChangeKey;
    public $Id;
    public $ItemId;
}

class DeleteItemType
{
    public $AffectedTaskOccurrences;
    public $DeleteType;
    public $ItemIds;
    public $SendMeetingCancellations;
    public $SuppressReadReceipts;
}

class ClientSoap extends \SoapClient
{

    public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string
    {
        echo "PHPVERSION: " . PHP_VERSION . "\n";
        echo "XML: $request";
        return $request;
    }

}

$client = new ClientSoap(__DIR__ . "/services.wsdl", [
    'classmap' => $classMap
]);
$deleteItem = new ItemIdType();
$deleteItem->Id = "AAMkAGJjMzNlMDA4LTU5NzMtNDZmMS1hN2QwLWE5NDJhOTg4ZTE3ZABGAAAAAADor6LtxqzcTZY8k3FzncNoBwBqStc3k+YrRrwI06yPhA+DAAAAAAEPAABqStc3k+YrRrwI06yPhA+DAASvw7+bAAA=";
$deleteItem->ChangeKey = "CQAAABYAAABqStc3k+YrRrwI06yPhA+DAASv1Zxc";
$request = new DeleteItemType();
$request->ItemIds = new ItemIdType();
$request->ItemIds->ItemId[] = $deleteItem;

$client->DeleteItem($request);

You will notice that the output is different, PHP 8.3.9+ missing a significant part of the message and instead inserts a "xmlns:xsi" namespace for whatever reason.

The error comes from the SOAP extension. When you just link PHP 8.3.9 to the .dll file from PHP 8.3.8 it just works also as expected.

This behaviour resulting in SOAP is broken in this specific case starting from PHP 8.3.9 - We saw it on our automated tests after upgrade to PHP 8.3.9 from PHP 8.3.8

PHP Version

PHP 8.3.9 or higher

Operating System

Windows

Activity

  1. changed the title [-]SOAP XML broken from since PHP 8.3.9 when using classmap constructor option[/-] [+]SOAP XML broken since PHP 8.3.9 when using classmap constructor option[/+] on Aug 6, 2024
  2. self-assigned this
    on Aug 6, 2024
  3. Girgias commented on Aug 6, 2024

    @Girgias
    Member

    This may be related to some refactoring I did. I won't have time for a couple more days, so Niels might beat me to this.

  4. cmb69 commented on Aug 6, 2024

    @cmb69
    Member

    The behavioral change is caused by d11a3c6.

  5. ndossche commented on Aug 6, 2024

    @ndossche
    Member

    I'll have a look later today. I think I realize what the problem is.

  6. removed their assignment
    on Aug 6, 2024
  7. ndossche commented on Aug 6, 2024

    @ndossche
    Member

    Turns out your classmap isn't actually doing anything for the serialization, e.g. if you change the classmap to the following, then your code sample will work in all versions:

    <?php
    $classMap = [
        'ItemIdType' => 'foobar',
        'DeleteItemType' => 'barbaz',
    ];

    Because the leading backslash in fully qualified names is now handled since my commit, the class mapping in your code now actually does something. (I suppose we should warn when the ce is unable to be fetched so we have decent error handling to catch this, but that aside...)

    So therefore I started looking at your xsd file and code, and noticed how you were setting $request->ItemIds = new ItemIdType();, but that doesn't make sense to me when I look at the xsd. Shouldn't this be something like $request->ItemIds = new NonEmptyArrayOfBaseItemIdsType(); instead? This seems to match https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxwscore/062d9e7e-8e7e-4f96-9d36-3dcfe4a7e577 as well.

    I tried with the following code:

    <?php
    
    namespace Testing\Soap;
    
    $classMap = [
        'ItemIdType' => '\\Testing\\Soap\\ItemIdType',
        'DeleteItemType' => '\\Testing\\Soap\\DeleteItemType',
    ];
    
    class ItemIdType{
        public $ChangeKey;
        public $Id;
    }
    
    class NonEmptyArrayOfBaseItemIdsType {
        public $ItemId;
    }
    
    class DeleteItemType
    {
        public $AffectedTaskOccurrences;
        public $DeleteType;
        public $ItemIds;
        public $SendMeetingCancellations;
        public $SuppressReadReceipts;
    }
    
    class ClientSoap extends \SoapClient
    {
    
        public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string
        {
            echo "PHPVERSION: " . PHP_VERSION . "\n";
            echo "XML: $request";
            return $request;
        }
    
    }
    
    $client = new ClientSoap(__DIR__ . "/services.wsdl", [
        'classmap' => $classMap
    ]);
    $deleteItem = new ItemIdType();
    $deleteItem->Id = "AAMkAGJjMzNlMDA4LTU5NzMtNDZmMS1hN2QwLWE5NDJhOTg4ZTE3ZABGAAAAAADor6LtxqzcTZY8k3FzncNoBwBqStc3k+YrRrwI06yPhA+DAAAAAAEPAABqStc3k+YrRrwI06yPhA+DAASvw7+bAAA=";
    $deleteItem->ChangeKey = "CQAAABYAAABqStc3k+YrRrwI06yPhA+DAASv1Zxc";
    $request = new DeleteItemType();
    $request->ItemIds = new NonEmptyArrayOfBaseItemIdsType();
    $request->ItemIds->ItemId[] = $deleteItem;
    
    $client->DeleteItem($request);

    And this works as expected. So I'm wondering whether this is actually a bug in your test code. I may be wrong though, my soap is rusty.

  8. brainfoolong commented on Aug 7, 2024

    @brainfoolong
    Author

    Thanks for investigation. To be honest, i have no idea about what that classmap and soap internals actually does. Soap is a blackbox for me that i never liked.

    This all comes from an external library we use (PHP EWS to talk with Exchange Servers) .

    I just boiled it down to this minimal reproducable example after lots of hours of debugging why this suddenly dont work anymore. The errors that came from the exchange servers also dont help much, only "Id malformed" was returned, jikes.

    It was even harder because this partial code is not called often (Almost all other things we need in this EWS soap library still work) so it was not directly obvious that this is caused by the PHP patch version upgrade from 8.3.8 to 8.3.9. We first investigated in the exchange server, if something have changed there.

    So, TLDR: It's not something we can easily change in production, it's the internals of the used library and as of now, we cant upgrade PHP because it will break our production environment in this case.

    I dont know what that the best way to go is here, but my general understanding of the PHP release cycle is that "patch" versions shouldnt break anything that already exist (Even if it is an obvious fix for something that never worked as expected). For PHP 8.4 this break would be perfectly fine i guess.

    Let me know your thoughts and what we actually should do in this case.

    Do you basically say, we can remove the whole "classmap" parameter and it should just work fine? This library has over 100 things in the classmap, it is impossible for me to know if this will break other things as well.

  9. schonhoff commented on Aug 7, 2024

    @schonhoff

    Hello,

    after patching our production server we run into a problem as well. After we debugged for hours our code, we tried to locally downgrade our PHP version to 8.2.20 it worked fine again. Looking at the PHP Release notes we found the classmap fix which caused the error on our side.

    Our problem is that a namespace is used now which wasn't used before. Trying to send a request with the added namespace we got an error that the type isn't correct. The added namespace is used because the declaration of the node is in two .xsd which are imported into another .xsd. Before the change the latest .xsd schema was used. Now, the first .xsd schema is used.

    We are using an .wsdl / .xsd which we aren't the owner so we cannot make any changes to that. I can't go any further into detail because of an NDA. I will try to get more information from an employee and provide it here asap.

  10. ndossche commented on Aug 7, 2024

    @ndossche
    Member

    This all comes from an external library we use (PHP EWS to talk with Exchange Servers) .

    Ouch, okay, so this is not easily fixable at the code's side.

    I dont know what that the best way to go is here, but my general understanding of the PHP release cycle is that "patch" versions shouldnt break anything that already exist (Even if it is an obvious fix for something that never worked as expected). For PHP 8.4 this break would be perfectly fine i guess.

    Patch versions shouldn't break anything indeed, so I'll revert this. And I'll think about what to do with the original fix, but keeping code that used to work working is more important.

    Do you basically say, we can remove the whole "classmap" parameter and it should just work fine? This library has over 100 things in the classmap, it is impossible for me to know if this will break other things as well.

    All entries that start with a \ could be removed and the code will work as it used to. Basically the schema definition and the code doesn't match which is what causes the issue in the first place.

  11. ndossche commented on Aug 7, 2024

    @ndossche
    Member

    @schonhoff @brainfoolong I'm going to revert this so that the existing code works again. This is a similar situation to ext-dom a while ago where I couldn't fix some bugs because people relied on them.

  12. ndossche commented on Aug 7, 2024

    @ndossche
    Member

    This will be fixed in the releases PHP 8.2.23 and PHP 8.3.11.
    I'll reopen the original bug report.
    I'll close this one, let me know if anything comes up.

  13. schonhoff commented on Aug 7, 2024

    @schonhoff

    Thank you! We will try to work on fixing the "bugs" on our side, maybe it will be working for a future PHP version.

  14. brainfoolong commented on Aug 7, 2024

    @brainfoolong
    Author

    @nielsdos Thanks for the fast response and support.

  15. brainfoolong commented on Aug 7, 2024

    @brainfoolong
    Author

    @nielsdos Short follow up on your info "All entries that start with a \ could be removed and the code will work as it used to". This is not true. I did so just for testing purposes and everything fails after i did that.

    Basically, the return values will just return "stdclass" in my cases when classmap entries are removed. With the classmap, with all the leading slash classes, the results of soap will return proper class instances.

    Just to let you know that even with leading slash, this does "something" already, even in PHP 8.3.8 and before.

  16. ndossche commented on Aug 7, 2024

    @ndossche
    Member

    @brainfoolong Right, sorry for the confusion. I was very unclear in what I wanted to say.
    Indeed without the classmap the return value will be stdClass. Removing the entries from the classmap only fixes the serialization. This happens because then the standard encoder is used by the soap extension, which means that violations against the XSD won't cause an invalid serialization.
    Basically to properly fix it, you'd have to make sure that your object structure follows the XSD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions