Alright, this problem is driving me up the wall. I'm unsuccessfully trying to connect to a web service using PHP and SOAP. I can't figure out what's wrong, and what's more this is a brand new service and their "documentation" is POOR. So I have no idea if the problem isn't actually on their end, but I don't have enough experience with SOAP to be able to know that for sure. I pray someone can help me.
I have been able to connect to the service by putting the XML directly into SOAP UI, but whenever I try to use the SoapClient it breaks down. The XML structure I am looking to send looks like
<Envelope xmlns:ns1="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://a.uri" xmlns:ns3="http://tempuri.org/">
<Body>
<GetAuthorization>
<ns1:registrationObj ns1:type="ns2:RegistrationAuthorization">
<ns2:Company>####</ns2:Company>
<ns2:Computer>####</ns2:Computer>
<ns2:Facility>####</ns2:Facility>
</ns1:registrationObj>
</GetAuthorization>
</Body>
</Envelope>
I have tried approaches too numerous to list. Using __soapCall, and $client->method(), SoapVar, and SoapParam. On the whole I find the documentation for PHP's SoapClient to be a bit sparse. But I can't even get the structure of the call to match what I want it to (dumped via __getLastRequest())
One thing I have noticed is the client keeps dropping the first element of my array (on those instances when I try to pass the parameters in as a plain array. So:
$params = array("Company" => "abc",
"Computer" => "def",
"Facility" => "ghi");
$result = $soap_client->__soapCall('GetAuthorization',$params);
returns
<env:Body>
<ns1:GetAuthorization/>
<param1>def</param1>
<param2>ghi</param2>
</env:Body>
note how in this instance the GetAuthorization self closed AND dropped the first array element. I have experienced both separately as well (and it is worth noting that I have gotten the paramaters to be named correctly also. I have gone through so many iterations I can't remember what attempts yield which results. Nonetheless, SOAP is NOT behaving like I would expect it to. It fails to encapsulate data properly and/or drops random elements.
$parameters =
array("ra" => new SoapVar(array(
"CompanyId" => new SoapVar("####", SOAP_ENC_OBJECT, 'guid', 'http://schemas.microsoft.com/2003/10/Serialization/', 'CompanyId', 'http://schemas.datacontract.org/x/y/z.xx'),
"ComputerId" => new SoapVar("{####}", SOAP_ENC_OBJECT, 'string', 'http://www.w3.org/2001/XMLSchema', 'ComputerId', 'http://schemas.datacontract.org/x/y/z.xx'),
"FacilityId" => new SoapVar("####", SOAP_ENC_OBJECT, 'guid', 'http://schemas.microsoft.com/2003/10/Serialization/', 'FacilityId', 'http://schemas.datacontract.org/x/y/z.xx')
), SOAP_ENC_OBJECT, 'RegistrationAuthorization', 'http://schemas.datacontract.org/x/y/z.xx', 'ra', "http://schemas.datacontract.org/x/y/z.xx"
)));
$result = $auth_client->GetAuthorization($parameters);
Is the structure I was trying to push through originally (before I simplified it to try to figure out what was wrong), figuring that since I need so much control over the namespacing of the parameters I would need to. BUT that just makes the request with a self-closed element.
Can someone PLEASE tell me how to structure the call to yield the appropriate XML structure? Is it possible this is on the Service's end and there is something wrong with the WSDL? (I'm not sure exactly what the WSDL is responsible for on the back end.)
I do apologize for the vagueness of the question, but I feel so lost I'm not even sure of the right one to ask. :-(
It should work:
<?php
$client = new \SoapClient(null, array(
'uri' => 'http://localhost/stack/21150482/',
'location' => 'http://localhost/stack/21150482/server.php',
'trace' => true
));
try {
$company = new \SoapVar('XXXXX', XSD_STRING, null, null, 'Company', 'http://a.uri');
$computer = new \SoapVar('XXXXX', XSD_STRING, null, null, 'Computer', 'http://a.uri');
$facility = new \SoapVar('XXXXX', XSD_STRING, null, null, 'Facility', 'http://a.uri');
$registrationObj = new \SoapVar(
array($company,$computer,$facility),
SOAP_ENC_OBJECT,
'RegistrationAuthorization',
'http://a.uri',
'registrationObj',
'http://www.w3.org/2001/XMLSchema-instance'
);
$client->GetAuthorization($registrationObj);
} catch (\Exception $e) {
var_dump($e->getMessage());
}
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($client->__getLastRequest());
print '<pre>';
print htmlspecialchars($dom->saveXML());
My result is:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/stack/21150482/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://a.uri" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:GetAuthorization>
<xsi:registrationObj xsi:type="ns2:RegistrationAuthorization">
<ns2:Company xsi:type="xsd:string">XXXXX</ns2:Company>
<ns2:Computer xsi:type="xsd:string">XXXXX</ns2:Computer>
<ns2:Facility xsi:type="xsd:string">XXXXX</ns2:Facility>
</xsi:registrationObj>
</ns1:GetAuthorization>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Different approach I have used is:
<?php
//note the "(object)" casting
$params = (object)[
'registrationObj' => (object)[
'Company' => 'abc',
'Computer' => 'def',
'Facility' => 'ghi'
]
];
$soap_client = new SoapClient('localhost/soap');
//direct call of the method GetAuthorization (without __soapCall)
$return = $soap_client->GetAuthorization($params);
Should work for PHP >= 5.4
Related
In PHP, we have SoapVar and SoapParam classes. I've been confused long enough, since there is no decent documentation of them on php.net.
Today I was surprised to find out that those lines will produce the exact same result in the XML input:
$soapVar = new SoapVar((object) ['Foo' => 'bar'], null, null, null, 'TagName');
$soapParam = new SoapParam((object) ['Foo' => 'bar'], 'TagName');
In most SoapClient tutorials, I have seen snippets with SoapParam inside SoapVar whenever someone wanted to set custom xsi:type:
$response = $soapClient->DoSomething(
new SoapParam(
new SoapVar(
(object) ['Foo' => 'bar'],
null,
'TypeName'
),
'TagName'
)
);
That is really unintuitive, since SoapVar and SoapParam class names don't say much. The same result can be achieved by more elegant:
$response = $soapClient->DoSomething(
new SoapVar(
(object) ['Foo' => 'bar'],
null,
'TypeName',
null,
'TagName'
)
);
What is the purpose of SoapParam then? Is it just more simple version of SoapVar? It seems like these two are confused and misunderstood a lot. Is there any extra behavior attached to SoapParam?
You are right the manual is a little vague explaining the exact difference in theory and I was surprised that till now none had pointed some detailed info or explanation into their differences.
By the manual
SoapParam:
Represents parameter to a SOAP call.
SoapVal:
A class representing a variable or object for use with SOAP services.
I went a little down and the main difference I could find was the data parameter in their representative constructors:
for SoapParam::SoapParam ( mixed $data , string $name )
where $data =
The data to pass or return. This parameter can be passed directly as
PHP value, but in this case it will be named as paramN and the SOAP
service may not understand it.
while on the other hand we have SoapVar::SoapVar ( mixed $data , string $encoding [, string $type_name [, string $type_namespace [, string $node_name [, string $node_namespace ]]]] ) where $data =
The data to pass or return.
By taking these two into consideration, the example in the PHP Manual, i believe points for good the main difference in the argument passing and what you can do with one that can not be done with the other.
In the case of SoapParam
<?php
$client = new SoapClient(null,array('location' => "http://localhost/soap.php",
'uri' => "http://test-uri/"));
$client->SomeFunction(new SoapParam($a, "a"),
new SoapParam($b, "b"),
new SoapParam($c, "c"));
?>
where $a, $b and $c are PHP variables send directly without passing them through a structure / class. The SaopParam constructor is getting the parameter $data (PHP Variable) and the String representative of the parameter name.
On the other side SoapVar
<?php
class SOAPStruct {
function SOAPStruct($s, $i, $f)
{
$this->varString = $s;
$this->varInt = $i;
$this->varFloat = $f;
}
}
$client = new SoapClient(null, array('location' => "http://localhost/soap.php",
'uri' => "http://test-uri/"));
$struct = new SOAPStruct('arg', 34, 325.325);
$soapstruct = new SoapVar($struct, SOAP_ENC_OBJECT, "SOAPStruct", "http://soapinterop.org/xsd");
$client->echoStruct(new SoapParam($soapstruct, "inputStruct"));
?>
As you can see, the arguments/variables are passed through the per-defined class and after an object of that class is passed as argument. That Class is a representative of the SOAP Structure (that SOAP can recognize / thous the risk with SoapParam).
Now the almost confusing part...although there are a few differences in the constructor why the same behavior on your example?
$soapVar = new SoapVar((object) ['Foo' => 'bar'], null, null, null, 'TagName');
$soapParam = new SoapParam((object) ['Foo' => 'bar'], 'TagName');
I am not surprised, as mentioned above the data either a SOAP Structure or a PHP Variable of a CORRECT Soap Structure is the same :) thanks to Object Oriented we pass the Object produced by a PHP Class. In the SoapVar the construction of the object has more options, thus allowing the user to adapt a more "safe" request towards the SOAP call. While on SoapParam the object can be constructed by passing any object and next its name.
Your (object) ['Foo' => 'bar'] is very simple and has no need for XML Schema Encoding thus the SOAP call receiving it I believe can very easily understand it. It has no need for the
encoding
The encoding ID, one of the XSD_... constants.
that is the second parameter in the SoapVar constructor.
Finally since they both accept an object (any object) in place of the $data variable to my understanding the SoapVar just gives the possibility to build a more safer request to be understood by the SOAP call through the extra arguments in the constructor. The SoapParam is just a badass and allows you to send anything you like at the risk of the SOAP refusing it.... it's like almost wants to act as JSON # RESTful :P not caring about the type nor structure of the data it sends :)
References:
SoapParam Class
SoapParam Constructor
SoapVar Class
SoapVar Constructor
I'm currently writing some tests for my API, and I'm curious to know if there is a better way to deal with this as I feel like this is the "hacky" way of doing things.
Code example below:
public function testListingOfAllUsers()
{
$users = $this->createUsers();
$client = $this->createClient();
$client->request("GET", "/users/");
$response = $client->getResponse();
$content = $response->getContent();
$decodedContent = json_decode($content);
$this->assertTrue($response->isOk());
$this->assertInternalType("array", $decodedContent->data);
$this->assertCount(count($users), $decodedContent->data);
foreach ($decodedContent->data as $data) {
$this->assertObjectHasAttribute("attributes", $data);
$this->assertEquals("users", $data->type);
}
}
I'm wondering if there's something better I can do to test my API matches the JSON API specification. Enlighten me! I'm pretty certain PHPUnit isn't my answer here.
First of all, I don't believe that programatically asserting a certain JSON structure as you're doing right now is bad practice per se. However, I do agree that it might get cumbersome at some point and could be solved more efficiently.
I was having the same issue a while ago and ended up writing a new Composer package (helmich/phpunit-json-assert, which is available as open source) that uses JSON schemata and JSONPath expressions for verifying the structure of a given JSON document.
Using a JSON schema, your example test case could be written as follows:
public function testListingOfAllUsers()
{
$users = $this->createUsers();
$client = $this->createClient();
$client->request("GET", "/users/");
$response = $client->getResponse();
$content = $response->getContent();
$decodedContent = json_decode($content);
$this->assertTrue($response->isOk());
$this->assertJsonDocumentMatchesSchema($decodedContent, [
'type' => 'array',
'items' => [
'type' => 'object',
'required' => ['attributes', 'type'],
'properties' => [
'attributes' => ['type' => 'object'],
'type' => ['type' => 'string', 'enum' => ['user']]
]
]
]);
}
Although a little more verbose (with regards to lines-of-code), I've come to appreciate JSON schemas for this use case, as it's a widely adopted standard and (imho) easier to read that a wall of assert* statements. You could also extract the schema definitions from your unit tests into separate files and do other stuff with them; for example auto-generating documentation (Swagger also uses a subset of JSON schema) or run-time validation.
I am getting fault string code like -Action specified in EbxmlMessage does not exist
Can any one help me what I am missing.
$OTA_HotelAvailRQ = new stdClass;
$OTA_HotelAvailRQ->AvailRequestSegment = new stdClass;
$OTA_HotelAvailRQ->AvailRequestSegment->ReturnHostCommand = true;
$OTA_HotelAvailRQ->AvailRequestSegment->TimeStamp = '2015-11-12T11:22:06';
$OTA_HotelAvailRQ->AvailRequestSegment->Version = '2.2.1';
$client = new SoapClient("http://webservices.sabre.com/wsdl/tpfc/OTA_HotelAvailLLS2.2.1RQ.wsdl", [
"trace" => 1,
"exceptions" => 0,
"cache_wsdl" => 0
]);
$client->__setSoapHeaders(new SoapHeader('NAMESPACE','Auth', [
'UserName'=>'',
'Password'=>'',
'SystemId'=> [
'_'=>'DATA',
'Param'=>'PARAM'
]
], false));
print_r( $client->OTA_HotelAvailRQ($OTA_HotelAvailRQ));
echo "<p>Request :".htmlspecialchars($client->__getLastRequest()) ."</p>";
echo "<p>Response:".htmlspecialchars($client->__getLastResponse())."</p>";
I don't see what is the Action you are using the MessageHeader but the correct one is not OTA_HotelAvailRQ but OTA_HotelAvailLLSRQ
EDIT:
Let me start by saying that the username should only be used on the SessionCreate in order to obtain a new session, from then on you will need to use the BinarySecurityToken that is returned. That will be valid for the same environment until the session expires or it's closed with the SessionClose. Lets imagine that I have that in variable $bst.
What I have done below is not something that I could test, so there could be something wrong, plus, my php knowledge is quite limited (keep that in mind) :)
So, you need 2 main elements in the soap header, messageHeader and Security. Security being the simplest one lets start with that.
Security:
$auth = array(
'BinarySecurityToken'=>$bst
);
$authHeader = new SoapHeader('http://schemas.xmlsoap.org/ws/2002/12/secext','Security',$auth,false);
MessageHeader:
$partyId=array(
'PartyId'=>''
);
$messageData=array(
'Timestamp'=>date('Y-m-d')
);
$messageHeader=array(
'ConversationId'=>'',
'From'=>$partyId,
'To'=>$partyId,
'CPAId'=>'',
'Service'=>'OTA_HotelAvailLLSRQ',
'Action'=>'OTA_HotelAvailLLSRQ',
'MessageData'=>$messageData
);
$messageHeaderHeader = new SoapHeader('http://www.ebxml.org/namespaces/messageHeader','MessageHeader',$messageHeader,false);
Finally join them and set it:
$headers=array();
$headers[]=$authHeader;
$headers[]=$messageHeaderHeader;
$soap_client->__setSoapHeaders($headers);
I'm trying to do a SOAP call in PHP, it works normally, but i'm with a doubt: How do I can pass arguments to XML creating new nodes according to an array of product's quantity? See this...
That's my XML in SoapUI (with the parts that are important: ITEMSITM > TITEMSITM. The first TITEMSITM is with the fields, the others its the same thing):
<soapenv:Header/>
<soapenv:Body>
<ns:MANUTENCAOSITM>
<ns:SITM>
<ns:CABECALHOSITM>
...
</ns:CABECALHOSITM>
<ns:ITEMSITM>
<!--Zero or more repetitions:-->
<ns:TITEMSITM>
<ns:CODIGOPRODUTO>0000265</ns:CODIGOPRODUTO>
<ns:DESCRICAOPRODUTO>REQ.CAT.0,410 POLI (PL10)</ns:DESCRICAOPRODUTO>
<ns:PERCENTUALDESCONTO>-1.03</ns:PERCENTUALDESCONTO>
<ns:PESOUNITARIO>0.41</ns:PESOUNITARIO>
<ns:PRECOBONIFICADO>10</ns:PRECOBONIFICADO>
<ns:PRECOTABELA>9.700</ns:PRECOTABELA>
<ns:PRECOUNITARIO>9.6</ns:PRECOUNITARIO>
<ns:QUANTIDADEBONIFICADA>20</ns:QUANTIDADEBONIFICADA>
<ns:QUANTIDADEVENDA>200</ns:QUANTIDADEVENDA>
<ns:SALDOBONIFICADO>0</ns:SALDOBONIFICADO>
<ns:TOTALBRUTO>1940.000</ns:TOTALBRUTO>
<ns:TOTALLIQUIDO>1920.000</ns:TOTALLIQUIDO>
<ns:TOTALPESO>82.000</ns:TOTALPESO>
<ns:VALORBONIFICADO>9.700</ns:VALORBONIFICADO>
<ns:VALORLIQUIDO>8.9550</ns:VALORLIQUIDO>
</ns:TITEMSITM>
<ns:TITEMSITM>
...
</ns:TITEMSITM>
<ns:TITEMSITM>
...
</ns:TITEMSITM>
</ns:ITEMSITM>
<ns:RODAPESITM>
<ns:CRESCIMENTOANTERIOR>?</ns:CRESCIMENTOANTERIOR>
<ns:TOTALINVESTIMENTO>0.1303</ns:TOTALINVESTIMENTO>
</ns:RODAPESITM>
</ns:SITM>
<ns:TIPOOPERACAO>3</ns:TIPOOPERACAO>
</ns:MANUTENCAOSITM>
</soapenv:Body>
I need to repeat this node (TITEMSITM) for each product in PHP, but it doesn't work, it just store the last item, like this code below that I try to do, but with no success.
$arguments = array(
'SITM' => array(
'CABECALHOSITM' => $pars1,
'ITEMSITM' => array(
'TITEMSITM' => $parsItem[0],
'TITEMSITM' => $parsItem[1],
'TITEMSITM' => $parsItem[2]
// ...
),
'RODAPESITM' => $pars2
),
'TIPOOPERACAO' => $pars3
);
$inserirItens = $cliente->MANUTENCAOSITM($arguments);
The code above calls with no problems, but when I print_r or var_dump the $arguments, I see that the repetition of the TITEMSITM sends only one product. I think it's simple, but I'm not getting. Can someone help me, please?
References:
PHP SoapClient - Multiple attributes with the same key
SoapClient: how to pass multiple elements with same name?
Here is the code style I used:
$wsdl = 'https://your.api/path?wsdl';
$client = new SoapClient($wsdl);
$multipleSearchValues = [1, 2, 3, 4];
$queryData = ['yourFieldName' => $multipleSearchValues];
$results = $client->YourApiMethod($queryData);
print_r($results);
$ITEMSITM = new stdClass();
foreach ($parsItem as $item) {
$ITEMSITM->TITEMSITM[] = $item;
}
The reasons this should work is because it will closely emulate data structures of your WSDL.
I'm trying to integrate a SOAP service into our app. For the UpdateCart method, their documentation states that it requires four properties. However, in their WSDL, which can be found here https://mews.mouser.com/cartservice.asmx?WSDL, it shows that it only requires a single property. Normally I would call the method via a SoapClient like so:
$soapClient->UpdateCart(array('xmlCartMessage' => $value));
Following their documentation, I should call it like so:
$soapClient->UpdateCart(array(
'CartGUID' => $value1,
'Requestor' => $value2,
'MouserPartNumber' => $value3,
'Quantity' => $value4
));
However, it doesn't work. It gives me the following error:
"faultstring":"Server was unable to process request. ---> String reference not set to an instance of a String.\nParameter name: s","faultcode":"soap:Server","detail":""
Their documentation states that the request for UpdateCart should look like this:
<?xml version="1.0" encoding="utf-8"?>
<CartMessage CartGUID="" Requestor=“ADI” xmlns="http://tempuri.org/XMLSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CartItem MouserPartNumber="595-430F157IPM" Quantity="37" TransactionID="1" CountryCode=""/>
</CartMessage>
I noticed that the parameters are encoded as attributes rather than tags with values. How am I supposed to do it using SoapClient?
You can try using $soapClient->__doRequest() to submit the request. For example, assuming $soapClient is initialised and that their example request is valid, try something like:
$request = '<?xml version="1.0" encoding="utf-8"?>
<CartMessage CartGUID="" Requestor=“ADI” xmlns="http://tempuri.org/XMLSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CartItem MouserPartNumber="595-430F157IPM" Quantity="37" TransactionID="1" CountryCode=""/>
</CartMessage>
';
$location = "https://mews.mouser.com/cartservice.asmx?WSDL";
$action = "http://tempuri.org/UpdateCart";
$result = $soapClient->__doRequest($request, $location, $action, 1);
$location and $action will need to be set based on the their WSDL.
See: http://www.php.net/manual/en/soapclient.dorequest.php
If you want to create a SOAP call like:
<Element Name="John" Group="USER">DATA</Attribute>
You should try something like this:
array('Element ' => array('_' => 'DATA', 'Name'=>'John', 'Group'=>'USER'));
So in Your case I assume it should be:
$CartItem = array('CartItem' => array('_' => '', 'CartGUID'=>$value1, 'Requestor'=>$value2));
$CartMessage = array('CartItem' => array('_' => $CartItem, 'MouserPartNumber'=>$value3, 'Quantity'=>$value4));
$soapClient->UpdateCart($CartMessage);