Updated definition of ComplexType and use classmap option of SoapServer

Please to refer to the documentation for the changes:
http://besim.pl/SoapBundle/tutorial/complex_type.html
This commit is contained in:
Francis Besset 2011-08-24 23:36:49 +02:00
parent 51a36dfb87
commit c154463b33
12 changed files with 117 additions and 291 deletions

View File

@ -54,9 +54,12 @@ class SoapWebServiceController extends ContainerAware
public function callAction($webservice) public function callAction($webservice)
{ {
$webServiceContext = $this->getWebServiceContext($webservice); $webServiceContext = $this->getWebServiceContext($webservice);
$this->serviceBinder = $webServiceContext->getServiceBinder(); $this->serviceBinder = $webServiceContext->getServiceBinder();
//$this->serviceBinder->fixXmlRequestContent();
$this->soapRequest = SoapRequest::createFromHttpRequest($this->container->get('request')); $this->soapRequest = SoapRequest::createFromHttpRequest($this->container->get('request'));
//$this->soapRequest->setContent($this->serviceBinder->fixXmlRequestContent($this->soapRequest->getContent()));
$this->soapServer = $webServiceContext->getServerFactory()->create($this->soapRequest, $this->soapResponse); $this->soapServer = $webServiceContext->getServerFactory()->create($this->soapRequest, $this->soapResponse);
$this->soapServer->setObject($this); $this->soapServer->setObject($this);

View File

@ -23,14 +23,13 @@ Controller
/** /**
* @Soap\Method("getUser") * @Soap\Method("getUser")
* @Soap\Param("name", phpType = "string") * @Soap\Param("name", phpType = "string")
* * @Soap\Result(phpType = "My\App\Entity\User")
* Specify \My\App\Entity\User phpType
* Warning: Do not forget the first backslash
* @Soap\Result(phpType = "\My\App\Entity\User")
*/ */
public function getUserAction($name) public function getUserAction($name)
{ {
$user = $this->container->getDoctrine()->getRepository('MyApp:User')->findOneByName($name); $user = $this->container->getDoctrine()->getRepository('MyApp:User')->findOneBy(array(
'name' => $name,
);
if (!$user) { if (!$user) {
throw new \SoapFault('USER_NOT_FOUND', sprintf('The user with the name "%s" can not be found', $name)); throw new \SoapFault('USER_NOT_FOUND', sprintf('The user with the name "%s" can not be found', $name));
@ -43,7 +42,7 @@ Controller
User class User class
---------- ----------
You can expose public property and public method (getter and setter). You can expose only the properties (public, protected or private) of a complex type.
.. code-block:: php .. code-block:: php
@ -54,67 +53,34 @@ You can expose public property and public method (getter and setter).
class User class User
{ {
/** /**
* @Soap\PropertyComplexType("string") * @Soap\ComplexType("string")
*/ */
public $firstname; public $firstname;
/** /**
* @Soap\PropertyComplexType("string") * @Soap\ComplexType("string")
*/ */
public $lastname; public $lastname;
/**
* @Soap\ComplexType("int", nillable=true)
*/
private $id; private $id;
/**
* @Soap\ComplexType("string")
*/
private $username; private $username;
/**
* @Soap\ComplexType("string")
*/
private $email; private $email;
/**
* @Soap\MethodComplexType("int", name="user_id", nillable=true)
*/
public function getId()
{
return $this->id;
} }
/** ComplexType
* @Soap\MethodComplexType("string", setter="setUsername") -----------
*/
public function getUsername()
{
return $this->username;
}
/** `ComplexType` accepts the following options:
* @Soap\MethodComplexType("string", setter="setEmail")
*/
public function getEmail()
{
return $this->email;
}
public function setUsername($username) * nillable: To specify that the value can be null
{
$this->username = $username;
}
public function setEmail($email)
{
$this->email = $email;
}
}
PropertyComplexType
-------------------
`PropertyComplexType` accepts the following options:
* **name**: To override the original name of the property
* **nillable**: To specify that the value can be null
MethodComplexType
-------------------
`MethodComplexType` accepts the following options:
* **name**: To override the original name of the property
* **nillable**: To specify that the value can be null
* **setter**: The set method name value. *Mandatory if the complex type is passed as a parameter to a function.*

View File

@ -14,6 +14,8 @@ use BeSimple\SoapBundle\ServiceDefinition\Method;
use BeSimple\SoapBundle\ServiceDefinition\Strategy\MethodComplexType; use BeSimple\SoapBundle\ServiceDefinition\Strategy\MethodComplexType;
use BeSimple\SoapBundle\ServiceDefinition\Strategy\PropertyComplexType; use BeSimple\SoapBundle\ServiceDefinition\Strategy\PropertyComplexType;
use Zend\Soap\Wsdl;
/** /**
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
@ -21,15 +23,18 @@ use BeSimple\SoapBundle\ServiceDefinition\Strategy\PropertyComplexType;
class RpcLiteralRequestMessageBinder implements MessageBinderInterface class RpcLiteralRequestMessageBinder implements MessageBinderInterface
{ {
private $messageRefs = array(); private $messageRefs = array();
private $definitionComplexTypes;
public function processMessage(Method $messageDefinition, $message, array $definitionComplexTypes = array()) public function processMessage(Method $messageDefinition, $message, array $definitionComplexTypes = array())
{ {
$this->definitionComplexTypes = $definitionComplexTypes;
$result = array(); $result = array();
$i = 0; $i = 0;
foreach ($messageDefinition->getArguments() as $argument) { foreach ($messageDefinition->getArguments() as $argument) {
if (isset($message[$i])) { if (isset($message[$i])) {
$result[$argument->getName()] = $this->processType($argument->getType()->getPhpType(), $message[$i], $definitionComplexTypes); $result[$argument->getName()] = $this->processType($argument->getType()->getPhpType(), $message[$i]);
} }
$i++; $i++;
@ -38,27 +43,27 @@ class RpcLiteralRequestMessageBinder implements MessageBinderInterface
return $result; return $result;
} }
protected function processType($phpType, $message, array $definitionComplexTypes) protected function processType($phpType, $message)
{ {
$isArray = false;
if (preg_match('/^([^\[]+)\[\]$/', $phpType, $match)) { if (preg_match('/^([^\[]+)\[\]$/', $phpType, $match)) {
$isArray = true; $isArray = true;
$type = $match[1]; $phpType = $match[1];
} else {
$isArray = false;
$type = $phpType;
} }
if (isset($definitionComplexTypes[$type])) { // @TODO Fix array reference
if (isset($this->definitionComplexTypes[$phpType])) {
if ($isArray) { if ($isArray) {
$array = array(); $array = array();
foreach ($message->item as $complexType) { foreach ($message->item as $complexType) {
$array[] = $this->getInstanceOfType($type, $complexType, $definitionComplexTypes); $array[] = $this->checkComplexType($phpType, $complexType);
} }
$message = $array; $message = $array;
} else { } else {
$message = $this->getInstanceOfType($type, $message, $definitionComplexTypes); $message = $this->checkComplexType($phpType, $message);
} }
} elseif ($isArray) { } elseif ($isArray) {
$message = $message->item; $message = $message->item;
@ -67,36 +72,30 @@ class RpcLiteralRequestMessageBinder implements MessageBinderInterface
return $message; return $message;
} }
private function getInstanceOfType($phpType, $message, array $definitionComplexTypes) protected function checkComplexType($phpType, $message)
{ {
$hash = spl_object_hash($message); $hash = spl_object_hash($message);
if (isset($this->messageRefs[$hash])) { if (isset($this->messageRefs[$hash])) {
return $this->messageRefs[$hash]; return $message;
} }
$this->messageRefs[$hash] = $r = new \ReflectionClass($message);
$instanceType = new $phpType(); foreach ($this->definitionComplexTypes[$phpType] as $type) {
$p = $r->getProperty($type->getName());
foreach ($definitionComplexTypes[$phpType] as $type) { if ($p->isPublic()) {
$value = $this->processType($type->getValue(), $message->{$type->getName()}, $definitionComplexTypes); $value = $message->{$type->getName()};
if (null === $value && $type->isNillable()) {
continue;
}
if ($type instanceof PropertyComplexType) {
$instanceType->{$type->getOriginalName()} = $value;
} elseif ($type instanceof MethodComplexType) {
if (!$type->getSetter()) {
throw new \LogicException(sprintf('"setter" option must be specified to hydrate "%s::%s()"', $phpType, $type->getOriginalName()));
}
$instanceType->{$type->getSetter()}($value);
} else { } else {
throw new \InvalidArgumentException(); $p->setAccessible(true);
$value = $p->getValue($message);
}
$value = $this->processType($type->getValue(), $value);
if (!$type->isNillable() && null === $value) {
throw new \SoapFault('SOAP_ERROR_COMPLEX_TYPE', sprintf('"%s:%s" cannot be null.', ucfirst(Wsdl::translateType($phpType)), $type->getName()));
} }
} }
return $instanceType; return $this->messageRefs[$hash] = $message;
} }
} }

View File

@ -14,6 +14,8 @@ use BeSimple\SoapBundle\ServiceDefinition\Method;
use BeSimple\SoapBundle\ServiceDefinition\Strategy\PropertyComplexType; use BeSimple\SoapBundle\ServiceDefinition\Strategy\PropertyComplexType;
use BeSimple\SoapBundle\ServiceDefinition\Strategy\MethodComplexType; use BeSimple\SoapBundle\ServiceDefinition\Strategy\MethodComplexType;
use Zend\Soap\Wsdl;
/** /**
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
@ -21,45 +23,42 @@ use BeSimple\SoapBundle\ServiceDefinition\Strategy\MethodComplexType;
class RpcLiteralResponseMessageBinder implements MessageBinderInterface class RpcLiteralResponseMessageBinder implements MessageBinderInterface
{ {
private $messageRefs = array(); private $messageRefs = array();
private $definitionComplexTypes;
public function processMessage(Method $messageDefinition, $message, array $definitionComplexTypes = array()) public function processMessage(Method $messageDefinition, $message, array $definitionComplexTypes = array())
{ {
$return = $messageDefinition->getReturn(); $this->definitionComplexTypes = $definitionComplexTypes;
$class = $return->getPhpType();
$message = $this->processType($messageDefinition->getReturn()->getPhpType(), $message, $definitionComplexTypes); return $this->processType($messageDefinition->getReturn()->getPhpType(), $message);
return $message;
} }
private function processType($phpType, $message, array $definitionComplexTypes) private function processType($phpType, $message)
{ {
$isArray = false;
if (preg_match('/^([^\[]+)\[\]$/', $phpType, $match)) { if (preg_match('/^([^\[]+)\[\]$/', $phpType, $match)) {
$isArray = true; $isArray = true;
$type = $match[1]; $phpType = $match[1];
} else {
$isArray = false;
$type = $phpType;
} }
if (isset($definitionComplexTypes[$type])) { if (isset($this->definitionComplexTypes[$phpType])) {
if ($isArray) { if ($isArray) {
$array = array(); $array = array();
foreach ($message as $complexType) { foreach ($message as $complexType) {
$array[] = $this->getInstanceOfStdClass($type, $complexType, $definitionComplexTypes); $array[] = $this->checkComplexType($phpType, $complexType);
} }
$message = $array; $message = $array;
} else { } else {
$message = $this->getInstanceOfStdClass($type, $message, $definitionComplexTypes); $message = $this->checkComplexType($phpType, $message);
} }
} }
return $message; return $message;
} }
private function getInstanceOfStdClass($phpType, $message, $definitionComplexTypes) private function checkComplexType($phpType, $message)
{ {
$hash = spl_object_hash($message); $hash = spl_object_hash($message);
if (isset($this->messageRefs[$hash])) { if (isset($this->messageRefs[$hash])) {
@ -71,26 +70,27 @@ class RpcLiteralResponseMessageBinder implements MessageBinderInterface
$class = substr($class, 1); $class = substr($class, 1);
} }
if (get_class($message) !== $class) { if (!$message instanceof $class) {
throw new \InvalidArgumentException(); throw new \InvalidArgumentException(sprintf('The instance class must be "%s", "%s" given.', get_class($message), $class));
} }
$stdClass = new \stdClass(); $r = new \ReflectionClass($message);
$this->messageRefs[$hash] = $stdClass; foreach ($this->definitionComplexTypes[$class] as $type) {
$p = $r->getProperty($type->getName());
foreach ($definitionComplexTypes[$phpType] as $type) { if ($p->isPublic()) {
$value = $message->{$type->getName()};
if ($type instanceof PropertyComplexType) {
$value = $message->{$type->getOriginalName()};
} elseif ($type instanceof MethodComplexType) {
$value = $message->{$type->getOriginalName()}();
} else { } else {
throw new \InvalidArgumentException(); $p->setAccessible(true);
$value = $p->getValue($message);
} }
$stdClass->{$type->getName()} = $this->processType($type->getValue(), $value, $definitionComplexTypes); $value = $this->processType($type->getValue(), $value);
if (!$type->isNillable() && null === $value) {
throw new \InvalidArgumentException(sprintf('"%s::%s" cannot be null.', $class, $type->getName()));
}
} }
return $stdClass; return $this->messageRefs[$hash] = $message;
} }
} }

View File

@ -13,7 +13,7 @@ namespace BeSimple\SoapBundle\ServiceDefinition\Annotation;
/** /**
* @Annotation * @Annotation
*/ */
class PropertyComplexType extends Configuration class ComplexType extends Configuration
{ {
private $name; private $name;
private $value; private $value;
@ -51,6 +51,6 @@ class PropertyComplexType extends Configuration
public function getAliasName() public function getAliasName()
{ {
return 'propertycomplextype'; return 'complextype';
} }
} }

View File

@ -1,67 +0,0 @@
<?php
/*
* This file is part of the BeSimpleSoapBundle.
*
* (c) Christian Kerl <christian-kerl@web.de>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapBundle\ServiceDefinition\Annotation;
/**
* @Annotation
*/
class MethodComplexType extends Configuration
{
private $name;
private $value;
private $setter;
private $isNillable = false;
public function getName()
{
return $this->name;
}
public function getValue()
{
return $this->value;
}
public function getSetter()
{
return $this->setter;
}
public function isNillable()
{
return $this->isNillable;
}
public function setName($name)
{
$this->name = $name;
}
public function setValue($value)
{
$this->value = $value;
}
public function setSetter($setter)
{
$this->setter = $setter;
}
public function setNillable($isNillable)
{
$this->isNillable = (bool) $isNillable;
}
public function getAliasName()
{
return 'methodcomplextype';
}
}

View File

@ -8,15 +8,14 @@
* with this source code in the file LICENSE. * with this source code in the file LICENSE.
*/ */
namespace BeSimple\SoapBundle\ServiceDefinition\Strategy; namespace BeSimple\SoapBundle\ServiceDefinition;
/** /**
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
*/ */
abstract class BaseComplexType class ComplexType
{ {
private $name; private $name;
private $originalName;
private $value; private $value;
private $isNillable = false; private $isNillable = false;
@ -25,11 +24,6 @@ abstract class BaseComplexType
return $this->name; return $this->name;
} }
public function getOriginalName()
{
return $this->originalName ?: $this->name;
}
public function getValue() public function getValue()
{ {
return $this->value; return $this->value;
@ -45,11 +39,6 @@ abstract class BaseComplexType
$this->name = $name; $this->name = $name;
} }
public function setOriginalName($originalName)
{
$this->originalName = $originalName;
}
public function setValue($value) public function setValue($value)
{ {
$this->value = $value; $this->value = $value;

View File

@ -10,9 +10,7 @@
namespace BeSimple\SoapBundle\ServiceDefinition\Loader; namespace BeSimple\SoapBundle\ServiceDefinition\Loader;
use BeSimple\SoapBundle\ServiceDefinition\Annotation\ComplexType; use BeSimple\SoapBundle\ServiceDefinition\ComplexType;
use BeSimple\SoapBundle\ServiceDefinition\Strategy\PropertyComplexType;
use BeSimple\SoapBundle\ServiceDefinition\Strategy\MethodComplexType;
use BeSimple\SoapBundle\Util\Collection; use BeSimple\SoapBundle\Util\Collection;
/** /**
@ -24,8 +22,7 @@ use BeSimple\SoapBundle\Util\Collection;
*/ */
class AnnotationComplexTypeLoader extends AnnotationClassLoader class AnnotationComplexTypeLoader extends AnnotationClassLoader
{ {
private $propertyComplexTypeClass = 'BeSimple\SoapBundle\ServiceDefinition\Annotation\PropertyComplexType'; private $complexTypeClass = 'BeSimple\SoapBundle\ServiceDefinition\Annotation\ComplexType';
private $methodComplexTypeClass = 'BeSimple\SoapBundle\ServiceDefinition\Annotation\MethodComplexType';
/** /**
* Loads a ServiceDefinition from annotations from a class. * Loads a ServiceDefinition from annotations from a class.
@ -44,50 +41,19 @@ class AnnotationComplexTypeLoader extends AnnotationClassLoader
} }
$class = new \ReflectionClass($class); $class = new \ReflectionClass($class);
$collection = new Collection('getName'); $collection = new Collection('getName', 'BeSimple\SoapBundle\ServiceDefinition\ComplexType');
foreach ($class->getProperties() as $property) { foreach ($class->getProperties() as $property) {
if ($property->isPublic()) { $complexType = $this->reader->getPropertyAnnotation($property, $this->complexTypeClass);
$complexType = $this->reader->getPropertyAnnotation($property, $this->propertyComplexTypeClass);
if ($complexType) { if ($complexType) {
$propertyComplexType = new PropertyComplexType(); $propertyComplexType = new ComplexType();
$propertyComplexType->setValue($complexType->getValue()); $propertyComplexType->setValue($complexType->getValue());
$propertyComplexType->setNillable($complexType->isNillable()); $propertyComplexType->setNillable($complexType->isNillable());
if (!$complexType->getName()) {
$propertyComplexType->setName($property->getName()); $propertyComplexType->setName($property->getName());
} else {
$propertyComplexType->setName($complexType->getName());
$propertyComplexType->setOriginalName($property->getName());
}
$collection->add($propertyComplexType); $collection->add($propertyComplexType);
} }
} }
}
foreach ($class->getMethods() as $method) {
if ($method->isPublic()) {
$complexType = $this->reader->getMethodAnnotation($method, $this->methodComplexTypeClass);
if ($complexType) {
$methodComplexType = new MethodComplexType();
$methodComplexType->setValue($complexType->getValue());
$methodComplexType->setSetter($complexType->getSetter());
$methodComplexType->setNillable($complexType->isNillable());
if (!$complexType->getName()) {
$methodComplexType->setName($property->getName());
} else {
$methodComplexType->setName($complexType->getName());
$methodComplexType->setOriginalName($method->getName());
}
$collection->add($methodComplexType);
}
}
}
return $collection; return $collection;
} }

View File

@ -1,29 +0,0 @@
<?php
/*
* This file is part of the BeSimpleSoapBundle.
*
* (c) Christian Kerl <christian-kerl@web.de>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapBundle\ServiceDefinition\Strategy;
/**
* @author Francis Besset <francis.besset@gmail.com>
*/
class MethodComplexType extends BaseComplexType
{
private $setter;
public function getSetter()
{
return $this->setter;
}
public function setSetter($setter)
{
$this->setter = $setter;
}
}

View File

@ -1,18 +0,0 @@
<?php
/*
* This file is part of the BeSimpleSoapBundle.
*
* (c) Christian Kerl <christian-kerl@web.de>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapBundle\ServiceDefinition\Strategy;
/**
* @author Francis Besset <francis.besset@gmail.com>
*/
class PropertyComplexType extends BaseComplexType
{
}

View File

@ -11,6 +11,7 @@
namespace BeSimple\SoapBundle\Soap; namespace BeSimple\SoapBundle\Soap;
use BeSimple\SoapBundle\Converter\ConverterRepository; use BeSimple\SoapBundle\Converter\ConverterRepository;
use Zend\Soap\Wsdl;
/** /**
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
@ -25,7 +26,7 @@ class SoapServerFactory
public function __construct($wsdlFile, array $classmap, ConverterRepository $converters, $debug = false) public function __construct($wsdlFile, array $classmap, ConverterRepository $converters, $debug = false)
{ {
$this->wsdlFile = $wsdlFile; $this->wsdlFile = $wsdlFile;
$this->classmap = $classmap; $this->classmap = $this->fixSoapServerClassmap($classmap);
$this->converters = $converters; $this->converters = $converters;
$this->debug = $debug; $this->debug = $debug;
} }
@ -62,4 +63,15 @@ class SoapServerFactory
return $typemap; return $typemap;
} }
private function fixSoapServerClassmap($classmap)
{
$classmapFixed = array();
foreach ($classmap as $class => $definition) {
$classmapFixed[Wsdl::translateType($class)] = $class;
}
return $classmapFixed;
}
} }

View File

@ -99,7 +99,12 @@ class WebServiceContext
public function getServerFactory() public function getServerFactory()
{ {
if (null === $this->serverFactory) { if (null === $this->serverFactory) {
$this->serverFactory = new SoapServerFactory($this->getWsdlFile(), array(), $this->converterRepository, $this->options['debug']); $this->serverFactory = new SoapServerFactory(
$this->getWsdlFile(),
$this->serviceDefinition->getDefinitionComplexTypes(),
$this->converterRepository,
$this->options['debug']
);
} }
return $this->serverFactory; return $this->serverFactory;