Doing some SOAP calls to a 3rd party application. They provide this soap header as an example of what the application expects. How can I create a SOAP header like this in PHP?
<SOAP-ENV:Header>
<NS1:Security xsi:type="NS2:Security" xmlns:NS1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:NS2="urn:dkWSValueObjects">
<NS2:UsernameToken xsi:type="NS2:TUsernameToken">
<Username xsi:type="xsd:string">XXXX</Username>
<Password xsi:type="xsd:string">XXX</Password>
</NS2:UsernameToken>
</NS1:Security>
</SOAP-ENV:Header>
I do what i think is a correct call and keep getting in return that no headers were sent.
Here is a sample from my code.
class SOAPStruct
{
function __construct($user, $pass)
{
$this->Username = $user;
$this->Password = $pass;
}
}
$client = new SoapClient("http://www.example.com/service");
$auth = new SOAPStruct("username", "password");
$header = new SoapHeader("http://example.com/service", "TUsernameToken", $auth);
$client->__setSoapHeaders(array($header));
$client->__soapCall("GetSubscriptionGroupTypes", array(), NULL, $header)
And this is the SOAP header i get back. (its more but i stripped info away that might be sensitive)
<SOAP-ENV:Header>
<ns2:TUsernameToken>
<Username>username</Username>
<Password>password</Password>
</ns2:TUsernameToken>
</SOAP-ENV:Header>
SOAP header handling in PHP is actually not very flexible and I'd go as far as saying that especially the use two namespaces within the header will make it impossible to inject the header simply by using a SoapHeader-construct of some type.
I think the best way to handle this one is to shape the XML request yourself by overriding SoapClient::__doRequest() in a custom class that extends SoapClient.
class My_SoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$xmlRequest = new DOMDocument('1.0');
$xmlRequest->loadXML($request);
/*
* Do your processing using DOM
* e.g. insert security header and so on
*/
$request = $xmlRequest->saveXML();
return parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
Please see SoapClient::__doRequest for further information and some examples.
Related
I'm creating a SOAP request via PHP native SoapClient class. When I dump the request using SoapClient::__getLastRequest I'll get:
<?xml version="1.0" encoding="UTF-8"?>\n
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:microsoft-dynamics-schemas/page/itemb2cws"><SOAP-ENV:Body><ns1:ReadMultiple><ns1:filter/><ns1:setSize>100</ns1:setSize></ns1:ReadMultiple></SOAP-ENV:Body></SOAP-ENV:Envelope>
Is there any way, how to remove the line feed or any other whitespace between XML elements?
Our client is using some old version of ERP Microsoft Dynamics Nav and the server falls when gets whitespace in request.
Solved
Via https://stackoverflow.com/questions/14363147/php-soapclient-malformed-xml#:~:text=override%20the%20SoapClient::__doRequest
Just extend native PHP SoapClient and override the SoapClient::__doRequest() method.
Example:
<?php
declare(strict_types=1);
namespace App\Soap;
use SoapClient;
class Client extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$request = $this->filter($request);
parent::__doRequest($request, $location, $action, $version, $one_way);
}
private function filter(string $request): string
{
return preg_replace( '#\R+#', '', $request);
}
}
And you're done.
I need send a XML with this format:
<soapenv:Body>
<loc:amount>
<loc:user>user_name</loc:user>
<loc:charge>
<description>description_text</description>
</loc:charge>
</loc:amount>
I have generate the XML body with soapVar, but I cant change "ns1" tag.
Source:
$var = '<loc:amount> <loc:user>user_name</loc:user> <loc:charge> <description>description_text</description> </loc:charge> </loc:amount>';
$params[0] = new SoapVar($var,XSD_ANYXML,'amount','http://mydomain.cm');
And the header result is this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://mydomain.cm" xmlns:ns2="http://header_domain.cm">
I need this result:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:loc="http://mydomain.cm" xmlns:ns2="http://header_domain.cm">
Is possible to change 'ns1' tag to 'loc'??
Thanks
Namespace prefix names are insignificant; it is only through their binding to a namespace value that they derive meaning. No conformant XML processor will care about the specific namespace prefix names; you should not either.
I've such an issue with UPnP Devices, which use "u" as default namespace-prefix and I need to change it from ns1. As I also found no better solution, I'll post my solution here.
class UpnpSoapClient extends SoapClient {
public function __doRequest($request, $location, $action, $version, $oneWay = 0)
{
// Some fucking UPnP Devices need the fucking u as namespace-prefix ... WTF?
if (preg_match('/xmlns:ns1=/', $request)) {
$request = preg_replace('/xmlns:ns1=/', 'xmlns:u=', $request, 1);
$request = preg_replace('/ns1:/', 'u:', $request);
}
return parent::__doRequest($request, $location, $action, $version);
}
}
I am trying to simply send RAW xml to a webservice via PHP and SoapClient. The problem is when I encode my XML it changes the order of elements in the XML that is converted to an associative array.
// Initialize the Soap Client:
$this->_transactionServicesClient = new SoapClient($soapWSDLUrl);
How or what would be the best way to send the following XML as a string to my SoapClient?
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.micros.com/pos/les/TransactionServices">
<SOAP-ENV:Body>
<ns1:PostTransaction>
<ns1:REQ>
<ns1:RequestHeader>
<ns1:InterfaceVersion>3.0.7</ns1:InterfaceVersion>
<ns1:ClientName>TRANS_SERVICES</ns1:ClientName>
</ns1:RequestHeader>
<ns1:CheckDetailEntries>
<ns1:MenuItem>
<ns1:ReferenceEntry>Pizza4</ns1:ReferenceEntry>
<ns1:Count>1</ns1:Count>
<ns1:Price>10.00</ns1:Price>
<ns1:ItemNumber>112001</ns1:ItemNumber>
<ns1:PriceLevel>1</ns1:PriceLevel>
<ns1:Seat xsi:nil="true"/>
</ns1:MenuItem>
</ns1:CheckDetailEntries>
<ns1:CheckHeaderRequest>
<ns1:CheckId>03:21:05.050505</ns1:CheckId>
<ns1:GuestCount>1</ns1:GuestCount>
<ns1:GuestInformation>
<ns1:ID>001</ns1:ID>
<ns1:FirstName>xxx</ns1:FirstName>
<ns1:LastName>xxx</ns1:LastName>
<ns1:Address1>xxx Rd</ns1:Address1>
<ns1:Address2>xx</ns1:Address2>
<ns1:Address3>xx</ns1:Address3>
<ns1:PhoneNum>xx</ns1:PhoneNum>
<ns1:UserText1>None</ns1:UserText1>
<ns1:UserText2>None</ns1:UserText2>
<ns1:UserText3>None</ns1:UserText3>
<ns1:GUID></ns1:GUID></ns1:GuestInformation>
</ns1:CheckHeaderRequest>
<ns1:OrderTypeNumber>1</ns1:OrderTypeNumber>
</ns1:REQ>
</ns1:PostTransaction>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Update/Resolution: Here is the code I used to extend the SOAP Client and send my raw Soap Envelope: My answer below
Update/Resolution: Here is the code I used to extend the SOAP Client and send my raw Soap Envelope
Here is how I extended SoapClient:
<?php
class MySoapClient extends SoapClient {
function __construct($wsdl, $options) {
parent::__construct($wsdl, $options);
$this->server = new SoapServer($wsdl, $options);
}
public function __doRequest($request, $location, $action, $version)
{
$result = parent::__doRequest($request, $location, $action, $version);
return $result;
}
function __myDoRequest($array,$op) {
$request = $array;
$location = 'http://xxxxx:xxxx/TransactionServices/TransactionServices6.asmx';
$action = 'http://www.micros.com/pos/les/TransactionServices/'.$op;
$version = '1';
$result =$this->__doRequest($request, $location, $action, $version);
return $result;
}
}
// To invoke my new custom method with my Soap Envelope already prepared.
$soapClient = new MySoapClient("http://xxxx:xxxx/TransactionServices/TransactionServices6.asmx?WSDL", array("trace" => 1));
$PostTransaction = $soapClient->__myDoRequest($orderRequest,$op);
?>
Also posted on pastie.org: http://pastie.org/3687935 before I turned this into the answer.
For testing purposes, you can subclass SoapClient and override the __doRequest method - it receives the generated XML and you can pre-process it.
But better find what's going wrong with the XML encoding. You can use SoapVar and SoapParam instances to specify the exact way given object has to be represented. Those saved my life when namespaces had to be given
It is many questions there about PHP and SOAP. But I am not found answer on my situation.
So. I use PHP SoapClient and WSDL for it. Object sends this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.site.com"><SOAP-ENV:Body>
But I need this:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body>
Question. How I can do this with standard PHP class SoapClient?
Thank you.
I search answer in php.net
<?php
class MSSoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version) {
$namespace = "http://tempuri.com";
$request = preg_replace('/<ns1:(\w+)/', '<$1 xmlns="'.$namespace.'"', $request, 1);
$request = preg_replace('/<ns1:(\w+)/', '<$1', $request);
$request = str_replace(array('/ns1:', 'xmlns:ns1="'.$namespace.'"'), array('/', ''), $request);
// parent call
return parent::__doRequest($request, $location, $action, $version);
}
}
$client = new MSSoapClient(...);
?>
This code change Envelope in request. And need for ASP SOAP server.
Use SoapVar::typeNamespace for set your xmlns:xsi
something like this :
new SoapVar($data, SOAP_ENC_OBJECT, "Type", "http://www.w3.org/2001/XMLSchema");
source : https://www.php.net/manual/fr/soapvar.construct
I'd like to create soapVars with attributes like this:
<tag attr="xxx">yyy</tag>
Is this possible with the SoapVar constructor, but without using XSD_ANYXML and raw xml strings?
The best way to do it is:
<?php
$tag['_'] = 'yyy';
$tag['attr'] = 'xxx';
$tagVar = new SoapVar($tag, SOAP_ENC_OBJECT);
?>
the result would be:
<tag attr="xxx">yyy</tag>
After spending many hours searching for a solution, I only found this workaround. Works in my case.
/**
* A SoapClient derived class that sets the namespace correctly in the input parameters
*/
class SoapClientNS extends SoapClient {
// return xml request
function __doRequest($request, $location, $action, $version, $one_way = NULL) {
//Replace each <Typename> with <ns1:Typename> or
$request = str_replace('RequestBase', 'ns1:RequestBase', $request);
return parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
$client = new SoapClientNS($wsdlURL);
$client->getAllBooks(array('RequestBase' => array('code' => 'AAAA', 'password' => '234234fdf')));
The request XML was something like this:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://www.travco.co.uk/trlink/xsd/country/request">
<env:Body>
<ns1:getAllBooks>
<RequestBase code="AAAA" password="234234fdf"/>
</ns1:getAllBooks>
</env:Body>
</env:Envelope>
pcmind's answer did not work for me, and also not if you try it in PhpFiddle.
I stumbled on this article, which basically creates xml with XMLWriter:
http://eosrei.net/articles/2012/01/php-soap-xml-attributes-namespaces-xmlwriter
This totally works for my case.