3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
12 use Zend\Server\Reflection
;
13 use Zend\Soap\AutoDiscover\DiscoveryStrategy\DiscoveryStrategyInterface
as DiscoveryStrategy
;
14 use Zend\Soap\AutoDiscover\DiscoveryStrategy\ReflectionDiscovery
;
15 use Zend\Soap\Exception
;
17 use Zend\Soap\Wsdl\ComplexTypeStrategy\ComplexTypeStrategyInterface
as ComplexTypeStrategy
;
25 protected $serviceName;
30 protected $reflection = null;
33 * Service function names
36 protected $functions = array();
50 * Url where the WSDL file will be available at.
56 * soap:body operation style options
59 protected $operationBodyStyle = array(
61 'encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/"
65 * soap:operation style
68 protected $bindingStyle = array(
70 'transport' => 'http://schemas.xmlsoap.org/soap/http'
74 * Name of the class to handle the WSDL creation.
77 protected $wsdlClass = 'Zend\Soap\Wsdl';
80 * Class Map of PHP to WSDL types.
83 protected $classMap = array();
86 * Discovery strategy for types and other method details.
87 * @var DiscoveryStrategy
89 protected $discoveryStrategy;
94 * @param null|ComplexTypeStrategy $strategy
95 * @param null|string|Uri\Uri $endpointUri
96 * @param null|string $wsdlClass
97 * @param null|array $classMap
99 public function __construct(
100 ComplexTypeStrategy
$strategy = null,
103 array $classMap = array()
105 $this->reflection
= new Reflection();
106 $this->setDiscoveryStrategy(new ReflectionDiscovery());
108 if (null !== $strategy) {
109 $this->setComplexTypeStrategy($strategy);
111 if (null !== $endpointUri) {
112 $this->setUri($endpointUri);
114 if (null !== $wsdlClass) {
115 $this->setWsdlClass($wsdlClass);
117 $this->setClassMap($classMap);
121 * Set the discovery strategy for method type and other information.
123 * @param DiscoveryStrategy $discoveryStrategy
126 public function setDiscoveryStrategy(DiscoveryStrategy
$discoveryStrategy)
128 $this->discoveryStrategy
= $discoveryStrategy;
133 * Get the discovery strategy.
135 * @return DiscoveryStrategy
137 public function getDiscoveryStrategy()
139 return $this->discoveryStrategy
;
143 * Get the class map of php to wsdl mappings.
147 public function getClassMap()
149 return $this->classMap
;
153 * Set the class map of php to wsdl mappings.
155 * @param array $classmap
157 * @throws Exception\InvalidArgumentException
159 public function setClassMap($classMap)
161 if (!is_array($classMap)) {
162 throw new Exception\
InvalidArgumentException(sprintf(
163 '%s expects an array; received "%s"',
165 (is_object($classMap) ?
get_class($classMap) : gettype($classMap))
169 $this->classMap
= $classMap;
176 * @param string $serviceName
178 * @throws Exception\InvalidArgumentException
180 public function setServiceName($serviceName)
184 // first character must be letter or underscore {@see http://www.w3.org/TR/wsdl#_document-n}
185 $i = preg_match('/^[a-z\_]/ims', $serviceName, $matches);
187 throw new Exception\
InvalidArgumentException('Service Name must start with letter or _');
190 $this->serviceName
= $serviceName;
198 * @throws Exception\RuntimeException
200 public function getServiceName()
202 if (!$this->serviceName
) {
204 return $this->reflection
->reflectClass($this->class)->getShortName();
206 throw new Exception\
RuntimeException('No service name given. Call AutoDiscover::setServiceName().');
209 return $this->serviceName
;
214 * Set the location at which the WSDL file will be available.
216 * @param Uri\Uri|string $uri
218 * @throws Exception\InvalidArgumentException
220 public function setUri($uri)
222 if (!is_string($uri) && !($uri instanceof Uri\Uri
)) {
223 throw new Exception\
InvalidArgumentException(
224 'Argument to \Zend\Soap\AutoDiscover::setUri should be string or \Zend\Uri\Uri instance.'
229 $uri = htmlspecialchars($uri, ENT_QUOTES
, 'UTF-8', false);
232 throw new Exception\
InvalidArgumentException('Uri contains invalid characters or is empty');
240 * Return the current Uri that the SOAP WSDL Service will be located at.
243 * @throws Exception\RuntimeException
245 public function getUri()
247 if ($this->uri
=== null) {
248 throw new Exception\
RuntimeException(
249 'Missing uri. You have to explicitly configure the Endpoint Uri by calling AutoDiscover::setUri().'
252 if (is_string($this->uri
)) {
253 $this->uri
= Uri\UriFactory
::factory($this->uri
);
259 * Set the name of the WSDL handling class.
261 * @param string $wsdlClass
263 * @throws Exception\InvalidArgumentException
265 public function setWsdlClass($wsdlClass)
267 if (!is_string($wsdlClass) && !is_subclass_of($wsdlClass, '\Zend\Soap\Wsdl')) {
268 throw new Exception\
InvalidArgumentException(
269 'No \Zend\Soap\Wsdl subclass given to Zend\Soap\AutoDiscover::setWsdlClass as string.'
273 $this->wsdlClass
= $wsdlClass;
278 * Return the name of the WSDL handling class.
282 public function getWsdlClass()
284 return $this->wsdlClass
;
288 * Set options for all the binding operations soap:body elements.
290 * By default the options are set to 'use' => 'encoded' and
291 * 'encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/".
293 * @param array $operationStyle
295 * @throws Exception\InvalidArgumentException
297 public function setOperationBodyStyle(array $operationStyle = array())
299 if (!isset($operationStyle['use'])) {
300 throw new Exception\
InvalidArgumentException('Key "use" is required in Operation soap:body style.');
302 $this->operationBodyStyle
= $operationStyle;
307 * Set Binding soap:binding style.
309 * By default 'style' is 'rpc' and 'transport' is 'http://schemas.xmlsoap.org/soap/http'.
311 * @param array $bindingStyle
314 public function setBindingStyle(array $bindingStyle = array())
316 if (isset($bindingStyle['style'])) {
317 $this->bindingStyle
['style'] = $bindingStyle['style'];
319 if (isset($bindingStyle['transport'])) {
320 $this->bindingStyle
['transport'] = $bindingStyle['transport'];
326 * Set the strategy that handles functions and classes that are added AFTER this call.
328 * @param ComplexTypeStrategy $strategy
331 public function setComplexTypeStrategy(ComplexTypeStrategy
$strategy)
333 $this->strategy
= $strategy;
338 * Set the Class the SOAP server will use
340 * @param string $class Class Name
343 public function setClass($class)
345 $this->class = $class;
350 * Add a Single or Multiple Functions to the WSDL
352 * @param string $function Function Name
354 * @throws Exception\InvalidArgumentException
356 public function addFunction($function)
358 if (is_array($function)) {
359 foreach($function as $row) {
360 $this->addFunction($row);
362 } elseif (is_string($function)) {
363 if (function_exists($function)) {
364 $this->functions
[] = $function;
366 throw new Exception\
InvalidArgumentException(
367 'Argument to Zend\Soap\AutoDiscover::addFunction should be a valid function name.'
372 throw new Exception\
InvalidArgumentException(
373 'Argument to Zend\Soap\AutoDiscover::addFunction should be string or array of strings.'
380 * Generate the WSDL for a service class.
384 protected function _generateClass()
386 return $this->_generateWsdl($this->reflection
->reflectClass($this->class)->getMethods());
390 * Generate the WSDL for a set of functions.
394 protected function _generateFunctions()
397 foreach (array_unique($this->functions
) as $func) {
398 $methods[] = $this->reflection
->reflectFunction($func);
400 return $this->_generateWsdl($methods);
404 * Generate the WSDL for a set of reflection method instances.
406 * @param array $reflectionMethods
409 protected function _generateWsdl(array $reflectionMethods)
411 $uri = $this->getUri();
413 $serviceName = $this->getServiceName();
415 $wsdl = new $this->wsdlClass($serviceName, $uri, $this->strategy
, $this->classMap
);
417 // The wsdl:types element must precede all other elements (WS-I Basic Profile 1.1 R2023)
418 $wsdl->addSchemaTypeSection();
420 $port = $wsdl->addPortType($serviceName . 'Port');
421 $binding = $wsdl->addBinding($serviceName . 'Binding', Wsdl
::TYPES_NS
. ':' . $serviceName . 'Port');
423 $wsdl->addSoapBinding($binding, $this->bindingStyle
['style'], $this->bindingStyle
['transport']);
424 $wsdl->addService($serviceName . 'Service', $serviceName . 'Port', Wsdl
::TYPES_NS
. ':' . $serviceName . 'Binding', $uri);
426 foreach ($reflectionMethods as $method) {
427 $this->_addFunctionToWsdl($method, $wsdl, $port, $binding);
434 * Add a function to the WSDL document.
436 * @param $function Reflection\AbstractFunction function to add
437 * @param $wsdl Wsdl WSDL document
438 * @param $port \DOMElement wsdl:portType
439 * @param $binding \DOMElement wsdl:binding
440 * @throws Exception\InvalidArgumentException
442 protected function _addFunctionToWsdl($function, $wsdl, $port, $binding)
444 $uri = $this->getUri();
446 // We only support one prototype: the one with the maximum number of arguments
448 $maxNumArgumentsOfPrototype = -1;
449 foreach ($function->getPrototypes() as $tmpPrototype) {
450 $numParams = count($tmpPrototype->getParameters());
451 if ($numParams > $maxNumArgumentsOfPrototype) {
452 $maxNumArgumentsOfPrototype = $numParams;
453 $prototype = $tmpPrototype;
456 if ($prototype === null) {
457 throw new Exception\
InvalidArgumentException(sprintf(
458 'No prototypes could be found for the "%s" function',
463 $functionName = $wsdl->translateType($function->getName());
465 // Add the input message (parameters)
467 if ($this->bindingStyle
['style'] == 'document') {
468 // Document style: wrap all parameters in a sequence element
470 foreach ($prototype->getParameters() as $param) {
471 $sequenceElement = array(
472 'name' => $param->getName(),
473 'type' => $wsdl->getType($this->discoveryStrategy
->getFunctionParameterType($param))
475 if ($param->isOptional()) {
476 $sequenceElement['nillable'] = 'true';
478 $sequence[] = $sequenceElement;
482 'name' => $functionName,
483 'sequence' => $sequence
486 // Add the wrapper element part, which must be named 'parameters'
487 $args['parameters'] = array('element' => $wsdl->addElement($element));
490 // RPC style: add each parameter as a typed part
491 foreach ($prototype->getParameters() as $param) {
492 $args[$param->getName()] = array(
493 'type' => $wsdl->getType($this->discoveryStrategy
->getFunctionParameterType($param))
497 $wsdl->addMessage($functionName . 'In', $args);
499 $isOneWayMessage = $this->discoveryStrategy
->isFunctionOneWay($function, $prototype);
501 if ($isOneWayMessage == false) {
502 // Add the output message (return value)
504 if ($this->bindingStyle
['style'] == 'document') {
505 // Document style: wrap the return value in a sequence element
507 if ($prototype->getReturnType() != "void") {
509 'name' => $functionName . 'Result',
510 'type' => $wsdl->getType($this->discoveryStrategy
->getFunctionReturnType($function, $prototype))
515 'name' => $functionName . 'Response',
516 'sequence' => $sequence
519 // Add the wrapper element part, which must be named 'parameters'
520 $args['parameters'] = array('element' => $wsdl->addElement($element));
522 } elseif ($prototype->getReturnType() != "void") {
523 // RPC style: add the return value as a typed part
524 $args['return'] = array(
525 'type' => $wsdl->getType($this->discoveryStrategy
->getFunctionReturnType($function, $prototype))
529 $wsdl->addMessage($functionName . 'Out', $args);
532 // Add the portType operation
533 if ($isOneWayMessage == false) {
534 $portOperation = $wsdl->addPortOperation(
537 Wsdl
::TYPES_NS
. ':' . $functionName . 'In', Wsdl
::TYPES_NS
. ':' . $functionName . 'Out'
540 $portOperation = $wsdl->addPortOperation(
543 Wsdl
::TYPES_NS
. ':' . $functionName . 'In', false
546 $desc = $this->discoveryStrategy
->getFunctionDocumentation($function);
548 if (strlen($desc) > 0) {
549 $wsdl->addDocumentation($portOperation, $desc);
552 // When using the RPC style, make sure the operation style includes a 'namespace'
553 // attribute (WS-I Basic Profile 1.1 R2717)
554 $operationBodyStyle = $this->operationBodyStyle
;
555 if ($this->bindingStyle
['style'] == 'rpc' && !isset($operationBodyStyle['namespace'])) {
556 $operationBodyStyle['namespace'] = '' . $uri;
559 // Add the binding operation
560 if ($isOneWayMessage == false) {
561 $operation = $wsdl->addBindingOperation($binding, $functionName, $operationBodyStyle, $operationBodyStyle);
563 $operation = $wsdl->addBindingOperation($binding, $functionName, $operationBodyStyle);
565 $wsdl->addSoapOperation($operation, $uri . '#' . $functionName);
569 * Generate the WSDL file from the configured input.
572 * @throws Exception\RuntimeException
574 public function generate()
576 if ($this->class && $this->functions
) {
577 throw new Exception\
RuntimeException('Can either dump functions or a class as a service, not both.');
581 $wsdl = $this->_generateClass();
583 $wsdl = $this->_generateFunctions();
590 * Proxy to WSDL dump function
592 * @param string $filename
594 * @throws Exception\RuntimeException
596 public function dump($filename)
598 return $this->generate()->dump($filename);
602 * Proxy to WSDL toXml() function
605 * @throws Exception\RuntimeException
607 public function toXml()
609 return $this->generate()->toXml();
613 * Handle WSDL document.
615 public function handle()
617 header('Content-Type: text/xml');