Details
-
Type:
Improvement
-
Status:
Open
-
Priority:
Minor
-
Resolution: Unresolved
-
Affects Version/s: 1.8.0
-
Fix Version/s: None
-
Component/s: Zend_Soap_Server
-
Labels:None
Description
_For more information about "document-literal-wrapped WSDL" please see ZF-6349_
As it is, PHP's SOAP extension can be used with a WSDL document making use of the "wrapped parameters" style (used by .NET and others). However, unwrapping is not done automatically. Consider the following method:
/** * This methods returns Hello $firstname $lastname. * @param string $firstname First name * @param string $lastname Last name * @return string */ public function helloYou($firstname, $lastname) { return "Hello {$firstname} {$lastname}"; }
A typical document-literal-wrapped WSDL document would wrap the two arguments into a "parameters" object declared by the message part, and whose complexType is defined in the types schema as a sequence of these parameters. Basically, after going through SoapServer, the helloYou() method would actually be called with only 1 argument: a stdClass object whose properties are firstname and lastname. Additionally, wrapping the return value is not automatic either, and one would have to return an array with $methodname.'Result' as the key and the actual return value as its value.
That is to say, the function above would have to be rewritten like this:
/** * This methods returns Hello $firstname $lastname. * @param string $firstname First name * @param string $lastname Last name * @return string */ public function helloYou($parameters) { return array('helloYouResult' => "Hello {$parameters->firstname} {$parameters->lastname}"); }
Beyond the fact this hack becomes quickly unreadable and completely depends on how the WSDL is made (whereas it should be transparent), it also poses a major problem when the WSDL is auto-generated from the same class by Zend_Soap_AutoDiscover (provided the ZF-6349 are applied as well as its dependencies). The function parameters and their description in the docblock are inconsistent. And this would throw an exception when doing reflection in Zend_Soap_AutoDiscover. BAD!
So, after this long explanation, I have the beginning of a solution but it's not integrated at all with Zend.. that is to say, the Zend_Soap_Server user must know he/she needs it and have the appropriate class, while ideally this should all be transparent.
The idea is to have a proxy class between SoapServer and the actual service class. This proxy is be able to intercept calls via the __call() magic method, to pre-process arguments and the return value appropriately (wrap/unwrap). Instead of using setClass() on Zend_Soap_Server, the user would have to do the following:
$proxy = new TestService_Proxy('TestService', array(), array('wrappedParts' => true)); $server->setObject($proxy);
The TestService_Proxy class (well, yes, it was for a test service.. don't mind the name
) is the following (inspired by Zend_Soap_Client):
<?php
class TestService_Proxy
{
protected $_className;
protected $_classInstance = null;
protected $_wrappedParts = false;
/**
* TestService_Proxy creates an intermediate (proxy) class between the SOAP server
* and the actual handling class, allowing pre-processing of function arguments and return values.
*
* @param string $className name of the handling class to proxy.
* @param array $classArgs arguments used to instantiate the handling class.
* @param array $options proxy options.
*/
public function __construct($className, $classArgs = array(), $options = array())
{
$class = new ReflectionClass($className);
$constructor = $class->getConstructor();
if ($constructor === null) {
$this->_classInstance = $class->newInstance();
} else {
$this->_classInstance = $class->newInstanceArgs($classArgs);
}
$this->_className = $className;
$this->_setOptions($options);
}
protected function _setOptions($options)
{
foreach ($options as $key => $value) {
switch ($key) {
case 'wrappedParts':
$this->_wrappedParts = $value;
break;
default:
break;
}
}
}
protected function _getOptions()
{
$options = array();
$options['wrappedParts'] = $this->_wrappedParts;
return $options;
}
protected function _preProcessArguments($name, $arguments)
{
if ($this->_wrappedParts && count($arguments) == 1 && is_object($arguments[0])) {
return get_object_vars($arguments[0]);
} else {
return $arguments;
}
}
protected function _preProcessResult($name, $result)
{
if ($this->_wrappedParts) {
return array($name.'Result' => $result);
} else {
return $result;
}
}
public function __call($name, $arguments)
{
$result = call_user_func_array(array($this->_classInstance, $name), $this->_preProcessArguments($name, $arguments));
return $this->_preProcessResult($name, $result);
}
}
Now, this works pretty well and lets you write your service class without having to take the WSDL style into account.
The problem, as I said, is it's completely NOT integrated with the rest of Zend. I would appreciate feedback and help on this, as I don't really know what approach to take to make use of it transparently in Zend_Soap_Server...
Issue Links
| This issue is related to: | ||||
| ZF-6349 | Zend_Soap_AutoDiscover does not generate interoperable document-literal WSDL |
|
|
|
As far as I can see it is currently impossible to let .NET clients interact with a SOAP service without applying the above solution. Therefor I do not understand why this issue has a minor priority.
Although the given solution works, I would make more sense to create a Zend_Soap_Server_DotNet class instead of creating a proxy class for a service. This would also be in line with the Zend_Soap_Client_DotNet class. Of course this Zend_Soap_Server_DotNet could create an internal wrapper around the service. One would have to overload the setClass and setObject of PHP's SoapServer class in order to make it work I think.