I get this SoapFault I dont understand. Calling the function below, codewordStemExists(), should create a SoapClient which connects to a SoapServer that is up and running (no errors that I can found has been reported from the server side).
private static function initClient() {
ini_set("soap.wsdl_cache_enabled", "0");
$classmap = array(
'CodewordStemExists' => 'CodewordStemExists',
'CodewordStemExistsResponse' => 'CodewordStemExistsResponse',
);
$client = new \SoapClient("http://..../service.wsdl", array(
"trace" => true,
"exceptions" => true,
"classmap" => $classmap
));
return $client;
}
public static function codewordStemExists($stem) {
$client = self::initClient();
try {
$req = new CodewordStemExists();
$req->username = "....";
$req->password = "....";
$req->codewordStem = $stem;
$res = $client->codewordStemExists($req);
return (bool)$res->result;
}
catch (\SoapFault $e) {
var_dump($client->__getLastResponse());
}
/** The result from var_dump: */
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://....">
<SOAP-ENV:Body>
<ns1:CodewordStemExistsResponse><ns1:result>false</ns1:result>
</ns1:CodewordStemExistsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The SoapFault:
Class 'CodewordStemExistsResponse' not found
CodewordStemExistsResponse is required at bootstrapping, it is possible to instantiate it at any time.
Anyone seen this before? Thanks.
Check if this needs proper namespacing, e.g. \vendor\CodewordStemExistsResponse.
Related
Overview
I'm trying to put a SOAP server inside a Symfony controller as described here: http://symfony.com/doc/current/controller/soap_web_service.html.
I'm using PHP2WSDL to generate the WSDL (after having failed to create a valid one by hand!). I have a service class with a bunch of methods that will be exposed via SOAP. And the Symfony controller will shovel the POST data into the SoapServer instance and barf out the yukky XML response back to the client.
But I'm pretty sure that SoapServer is not calling the methods on my service class. I can see from SoapServer::getFunctions that all the PHP2WSDL #soap annotated methods have been registered by my SoapServer instance, but nothing my methods are supposed to do seems to be happening; no logging, not even throw new \Exception('##!?!'); on the first line.
The actual symptom I have is that, after the SoapClient tries to __soapCall a method (in the other application), it expects to have received some SoapHeaders (which my method might be setting; I just don't know) but the response headers are empty.
Code
Commented lines are things I've been using to try and debug.
Symfony controller
class DefaultController extends Controller
{
const SERVICE_URI = 'http://example.com/api';
public function wsdlAction()
{
$log = new Logger('api');
$response = new Response();
$response->headers->set('Content-Type', 'text/xml');
$response->setContent($this->makeWsdl());
// $log->debug(sprintf('[%s] WSDL response', __METHOD__));
return $response;
}
public function operationAction(Request $request)
{
$log = new Logger('api');
try {
$wsdlFile = tmpfile();
$wsdlPath = stream_get_meta_data($wsdlFile)['uri'];
file_put_contents($wsdlPath, $this->makeWsdl());
} catch (Exception $e) {
$log->error(sprintf('[%s] Couldn\'t create the temporary WSDL file for SoapServer: "%s"', __METHOD__, $e->getMessage()));
throw $e;
}
// $c = file_get_contents($wsdlPath);
// $log->debug(sprintf('[%s] Created WSDL temporary file in %s: %s', __METHOD__, $wsdlPath, $c));
$server = new SoapServer(
$wsdlPath,
[
'soap_version' => SOAP_1_1,
'encoding' => 'UTF-8',
]
);
// $log->debug(sprintf('[%s] Created server', __METHOD__));
$server->setObject(new MyService($server, $log));
// $log->debug(sprintf('[%s] Set server object', __METHOD__));
// foreach ($server->getFunctions() as $f) {
// $log->debug(sprintf('[%s] SOAP function: %s', __METHOD__, (string) $f));
// }
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=UTF-8');
// $log->debug(sprintf('[%s] Create new response', __METHOD__));
ob_start();
// $log->debug(sprintf('[%s] Calling server->handle(%s)', __METHOD__, $request->getContent()));
$server->handle($request->getContent());
// $log->debug(sprintf('[%s] Setting response content', __METHOD__));
$response->setContent(ob_get_clean());
// $log->debug(sprintf('[%s] operate response', __METHOD__));
return $response;
}
private function makeWsdl()
{
$wsdlGenerator = new PHP2WSDL\PHPClass2WSDL(MyService::class, self::SERVICE_URI);
$wsdlGenerator->generateWSDL(true);
return $wsdlGenerator->dump();
}
}
This controller mostly "works". I mean, you can call the route for the WSDL and you get the WSDL. And you can call the route for the operations and it does all the work up to $server->handle($request->getContent()); where it throws an exception.
MyService
class MyService
{
private $server;
private $log;
public function __construct(SoapServer $server, Logger $log)
{
$this->server = $server;
$this->log = $log;
// $this->log->debug(sprintf('[%s] __construct called', __METHOD__));
}
/**
* #soap
*/
public function Login(array $parameters = [])
{
// throw new \Exception('Login throws immediately');
// $this->log->debug(sprintf('[%s] Login called: %s', __METHOD__, json_encode($parameters)));
$this->server->addSoapHeader(new SoapHeader(
'http://example.com/api',
'AuthHeader',
['Token' => 'fake-token']
));
return true;
}
}
The WSDL is generated from this class by PHP2WSDL which seems to be working perfectly.
The external application that calls my controller is throwing an exception because it's expecting to get this AuthHeader SOAP header in the SOAP response that comes from my DefaultController. But it isn't finding one. But whether or not my attempt to use addSoapHeader here is correct, I can't see that this Login method is even being called.
The external application
$this->client = new SoapClient('http://example.com/api/wsdl', [
'trace' => 1,
'encoding' => 'UTF-8',
'soap_version' => SOAP_1_1,
'cache_wsdl' => WSDL_CACHE_NONE,
'location' => 'http://example.com/api',
]);
...
$response = $this->client->__soapCall(
'Login',
['username' => '', 'password' => ''],
[],
null,
$outputHeaders
);
...
if ($outputHeaders['AuthHeader']) {
...
This is the code that ultimately throws the exception when $outputHeaders['AuthHeader'] is not set. From here, I can dump the SOAP requests and responses. Here's the request:
<?xml version="1.0" encoding="UTF-8"?>
<envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/api/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" soap-env:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/">
<body>
<login>
<parameters xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">username</key>
<value xsi:type="xsd:string"></value>
</item>
<item>
<key xsi:type="xsd:string">password</key>
<value xsi:type="xsd:string"></value>
</item>
</parameters>
</login>
</body>
</envelope>
And the response, of course, is empty; as are the output headers.
This code is the "legacy" bit that I can't change. For a bit more background: it calls a real, third-party SOAP service and what I'm trying to do at the moment is build a mock of that service that can be used for testing.
I am trying to use this webservice
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ABRSearchByName xmlns="http://abr.business.gov.au/ABRXMLSearch/">
<externalNameSearch>
<authenticationGUID>string</authenticationGUID>
<name>string</name>
<filters>
<nameType>
<tradingName>string</tradingName>
<legalName>string</legalName>
</nameType>
<postcode>string</postcode>
<stateCode>
<QLD>string</QLD>
<NT>string</NT>
<SA>string</SA>
<WA>string</WA>
<VIC>string</VIC>
<ACT>string</ACT>
<TAS>string</TAS>
<NSW>string</NSW>
</stateCode>
</filters>
</externalNameSearch>
<authenticationGuid>string</authenticationGuid>
</ABRSearchByName>
</soap:Body>
</soap:Envelope>
below is my class definition
class abnlookup extends SoapClient{
private $guid = "00000000";
public function __construct($guid)
{
$this->guid = $guid;
$params = array(
'soap_version' => SOAP_1_1,
'exceptions' => true,
'trace' => 1,
'cache_wsdl' => WSDL_CACHE_NONE
);
parent::__construct('http://abr.business.gov.au/abrxmlsearch/ABRXMLSearch.asmx?WSDL', $params);
}
public function searchByName($name){
$params = new stdClass();
$params->name= $name;
$params->nameType= 'Y' ;
$params->postcode= 'Y';
$params->stateCode= 'N' ;
$params->authenticationGuid = $this->guid;
return $this->ABRSearchByName($params);
}
call and create class object
$abnlookup = new abnlookup($abn_guid);
$result = $abnlookup->searchByName($name);
and recieve this msg
Server was unable to process request. ---> Object reference not set to an instance of an object.second
The $params structure you are passing to ABRSearchByName is not correct. According to WSDL this structure has two fields (as shown by __getTypes()):
struct ABRSearchByName {
ExternalRequestNameSearch externalNameSearch;
string authenticationGuid;
}
Create your $params with these two fields and the problem should be resolved.
How can I create the following part as part of a soap request?
<RequestDetails xsi:type="PostcodeRequest">
<Postcode>SW1A 1AA</Postcode>
</RequestDetails>
I am creating the soap request using arrays
$aPostcode = array('Postcode'=>'SW1A 1AA')
$aPostcodeRequest = array('PostcodeRequest' => $aPostcode);
$GetLineCharacteristicsRequest = array('RequestDetails' => aPostcodeRequest);
I didn't find a way to achieve it using arrays, but I could do it with classes. The code:
try {
$options = [
'trace'=> 1,
'location' => 'http://localhost/pruebas/soap-server-nowsdl.php',
'uri' => 'http://localhost/pruebas'
];
class PostCodeRequest {
function __construct($pc)
{
$this->Postcode = $pc;
}
}
$client = new SOAPClient(null, $options);
$pc = new PostcodeRequest('SW1A 1AA');
$postCodeRequest = new SoapVar($pc, SOAP_ENC_OBJECT, 'PostCodeRequest', 'http://soapinterop.org/xsd');
$response = $client->hola(new SoapParam($postCodeRequest, 'RequestDetails'));
header('Content-type:text/xml');
echo $client->__getLastRequest();
}
catch (SoapFault $e) {
echo $e;
}
Will give this as request:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/pruebas" 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/" xmlns:ns2="http://soapinterop.org/xsd" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:hola>
<RequestDetails xsi:type="ns2:PostCodeRequest">
<Postcode xsi:type="xsd:string">SW1A 1AA</Postcode>
</RequestDetails>
</ns1:hola>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Of course, this is assuming you have a "hola" function in your SOAP server. Replace it with whatever you're calling.
This solution is based in the example of the SoapVar constructor.
I found this question on here:
PHP Soap Issue: Server was unable to process request. ---> Object reference not set to an instance of an object
I have a similar issue, only the WSDL is private, so I figured I'd try and get a basic timezone SOAP Client working.
The solution in the other question isn't possible for me to use with the private WSDL.
$response = $client->getTimeZoneTime(array('timezone'=>'ZULU'));
Really what I need is a way of taking a multidimensional PHP array and putting it into the SOAP formed XML document, without it going crazy and producing stuff like, for this example, this:-
<key>GetTimeZoneTime</key>
<item>ZULU</item>
Here's my PHP:
try {
$WSDL = 'http://www.nanonull.com/TimeService/TimeService.asmx?WSDL';
$client = new SoapClient($WSDL,
array(
"trace" => 1,
"exceptions" => 1,
"soap_version" => SOAP_1_1
));
$xml = '<GetTimeZoneTime><timezone>ZULU</timezone></GetTimeZoneTime>';
$xmlvar = new SoapVar(
$xml,
XSD_ANYXML
);
$response = $client->getTimeZoneTime($xmlvar);
echo "<pre>\n";
echo "Request :\n".htmlspecialchars($client->__getLastRequest()) ."\n";
echo "Response:\n".htmlspecialchars($client->__getLastResponse())."\n";
echo "</pre>";
} catch (SoapFault $exception) {
echo "<pre>\n";
echo "Request :\n".htmlspecialchars($client->__getLastRequest()) ."\n";
echo "Response:\n".htmlspecialchars($client->__getLastResponse())."\n";
echo $exception;
echo "</pre>";
}
This is the request it produces:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.Nanonull.com/TimeService/">
<SOAP-ENV:Body>
<GetTimeZoneTime>
<timezone>ZULU</timezone>
</GetTimeZoneTime>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
And the SOAP Fault is:
Server was unable to process request. ---> Object reference not set to an instance of an object.
What's the correct way of turning a multidimensional PHP array into the appropriate format for a SOAP request?
What does the SOAP fault returned actually mean?
Edit: After some searching around elsewhere I thought I'd try the approach of creating a PHP class to mirror the variables on the server. This doesn't work either.
class TimeZone {
public function __construct ()
{
$this->timezone = 'ZULU';
}
}
$WSDL = 'http://www.nanonull.com/TimeService/TimeService.asmx?WSDL';
$client = new SoapClient($WSDL,
array(
"trace" => 1,
"exceptions" => 1,
"soap_version" => SOAP_1_1
));
$xmlvar = new SoapVar(new TimeZone(), SOAP_ENC_OBJECT, "TimeZone");
$response = $client->getTimeZoneTime($xmlvar);
For the Timezone one, adding the classmap parameter made it work:
$client = new SoapClient($WSDL,
array(
"trace" => 1,
"exceptions" => 1,
"soap_version" => SOAP_1_1,
"classmap" => array('timezone' => 'TimeZone')
));
$obj = new TimeZone();
$response = $client->getTimeZoneTime($obj);
echo "<h1>".$response->getTimeZoneTimeResult."</h1>";
For the main problem I'm having, it warrants a new question.
I may be wrong, but I gather the meaning of the error message to be twofold:
The object passed into the soap call may not be an object at all.
The object passed into the soap call may be an object, but if all of its attributes do not match what the server expects it will return that error.
Given that the following client.php creates this request XML:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://soap.dev/" 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:Test>
<RequestId xsi:type="xsd:int">1</RequestId>
<PartnerId xsi:type="xsd:int">99</PartnerId>
</ns1:Test>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
How do I access the names of the parameters? (RequestId and PartnerId) inside a server.php? The names are clearly there in the payload, but on server side only the values are received (1 and 99)
Sample code follows:
client.php
<?php
$client_params = array(
'location' => 'http://soap.dev/server.php',
'uri' => 'http://soap.dev/',
'trace' => 1
);
$client = new SoapClient(null, $client_params);
try {
$res = $client->Test(
new SoapParam(1, 'RequestId'),
new SoapParam(99, 'PartnerId')
);
} catch (Exception $Ex) {
print $Ex->getMessage();
}
var_dump($client->__getLastRequest());
var_dump($client->__getLastResponse());
server.php
class receiver {
public function __call ($name, $params)
{
$args = func_get_args();
// here $params equals to array(1, 99)
// I want the names as well.
return var_export($args, 1);
}
}
$server_options = array('uri' => 'http://soap.dev/');
$server = new SoapServer(null, $server_options);
$server->setClass('receiver');
$server->handle();
Please note that I can not really change the incoming request format.
Also, I am aware that I could give the names back to parameters by creating a Test function with $RequestId and $PartnerId parameters.
But what I really want to is to get name/value pairs out of incoming request.
So far the only idea I have is to simply parse the XML and this cannot be right.
Back when I had this problem I finally decided to go with the proxy function idea - Test function with parameters named ($RequestId and $PartnerId) to give names back to parameters. Its adequate solution, but certainly not the best.
I have not yet lost the hope for finding a better solution though, and here is my best idea so far
<?php
class Receiver {
private $data;
public function Test ()
{
return var_export($this->data, 1);
}
public function int ($xml)
{
// receives the following string
// <PartnerId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:int">99</PartnerId>
$element = simplexml_load_string($xml);
$this->data[$element->getName()] = (string)$element;
}
}
$Receiver = new Receiver();
$int = array(
'type_name' => 'int'
, 'type_ns' => 'http://www.w3.org/2001/XMLSchema'
, 'from_xml' => array($Receiver, 'int')
);
$server_options = array('uri' => 'http://www.w3.org/2001/XMLSchema', 'typemap' => array($int), 'actor' => 'http://www.w3.org/2001/XMLSchema');
$server = new SoapServer(null, $server_options);
$server->setObject($Receiver);
$server->handle();
It still means parsing XML manually, but one element at a time which is a bit more sane that parsing entire incoming SOAP message.