3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
19 use Zend\Server\Server
as ZendServerServer
;
20 use Zend\Stdlib\ArrayUtils
;
22 class Server
implements ZendServerServer
31 * Class registered with this server
40 protected $server = null;
42 * Arguments to pass to {@link $class} constructor
45 protected $classArgs = array();
48 * Array of SOAP type => PHP class pairings for handling return/incoming values
60 * Registered fault exceptions
63 protected $faultExceptions = array();
66 * Container for caught exception during business code execution
69 protected $caughtException = null;
72 * SOAP Server Features
78 * Functions registered with this server; may be either an array or the SOAP_FUNCTIONS_ALL constant
81 protected $functions = array();
84 * Object registered with this server
89 * Informs if the soap server is in debug mode
92 protected $debug = false;
95 * Persistence mode; should be one of the SOAP persistence constants
98 protected $persistence;
113 * Flag: whether or not {@link handle()} should return a response instead of automatically emitting it.
116 protected $returnResponse = false;
119 * SOAP version to use; SOAP_1_2 by default, to allow processing of headers
122 protected $soapVersion = SOAP_1_2
;
125 * Array of type mappings
131 * URI namespace for SOAP server
137 * URI or path to WSDL
143 * WSDL Caching Options of SOAP Server
146 protected $wsdlCache;
151 * Sets display_errors INI setting to off (prevent client errors due to bad
152 * XML in response). Registers {@link handlePhpErrors()} as error handler
155 * If $wsdl is provided, it is passed on to {@link setWSDL()}; if any
156 * options are specified, they are passed on to {@link setOptions()}.
158 * @param string $wsdl
159 * @param array $options
160 * @throws Exception\ExtensionNotLoadedException
162 public function __construct($wsdl = null, array $options = null)
164 if (!extension_loaded('soap')) {
165 throw new Exception\
ExtensionNotLoadedException('SOAP extension is not loaded.');
168 if (null !== $wsdl) {
169 $this->setWSDL($wsdl);
172 if (null !== $options) {
173 $this->setOptions($options);
180 * Allows setting options as an associative array of option => value pairs.
182 * @param array|\Traversable $options
185 public function setOptions($options)
187 if ($options instanceof Traversable
) {
188 $options = ArrayUtils
::iteratorToArray($options);
191 foreach ($options as $key => $value) {
192 switch (strtolower($key)) {
194 $this->setActor($value);
199 $this->setClassmap($value);
204 $this->setTypemap($value);
208 $this->setEncoding($value);
213 $this->setSoapVersion($value);
217 $this->setUri($value);
221 $this->setWSDL($value);
225 $this->setWSDLCache($value);
229 $this->setSoapFeatures($value);
241 * Return array of options suitable for using with SoapServer constructor
245 public function getOptions()
248 if (null !== $this->actor
) {
249 $options['actor'] = $this->getActor();
252 if (null !== $this->classmap
) {
253 $options['classmap'] = $this->getClassmap();
256 if (null !== $this->typemap
) {
257 $options['typemap'] = $this->getTypemap();
260 if (null !== $this->encoding
) {
261 $options['encoding'] = $this->getEncoding();
264 if (null !== $this->soapVersion
) {
265 $options['soap_version'] = $this->getSoapVersion();
268 if (null !== $this->uri
) {
269 $options['uri'] = $this->getUri();
272 if (null !== $this->features
) {
273 $options['features'] = $this->getSoapFeatures();
276 if (null !== $this->wsdlCache
) {
277 $options['cache_wsdl'] = $this->getWSDLCache();
286 * @param string $encoding
288 * @throws Exception\InvalidArgumentException with invalid encoding argument
290 public function setEncoding($encoding)
292 if (!is_string($encoding)) {
293 throw new Exception\
InvalidArgumentException('Invalid encoding specified');
296 $this->encoding
= $encoding;
305 public function getEncoding()
307 return $this->encoding
;
313 * @param int $version One of the SOAP_1_1 or SOAP_1_2 constants
315 * @throws Exception\InvalidArgumentException with invalid soap version argument
317 public function setSoapVersion($version)
319 if (!in_array($version, array(SOAP_1_1
, SOAP_1_2
))) {
320 throw new Exception\
InvalidArgumentException('Invalid soap version specified');
323 $this->soapVersion
= $version;
332 public function getSoapVersion()
334 return $this->soapVersion
;
338 * Check for valid URN
342 * @throws Exception\InvalidArgumentException on invalid URN
344 public function validateUrn($urn)
346 $scheme = parse_url($urn, PHP_URL_SCHEME
);
347 if ($scheme === false ||
$scheme === null) {
348 throw new Exception\
InvalidArgumentException('Invalid URN');
357 * Actor is the actor URI for the server.
359 * @param string $actor
362 public function setActor($actor)
364 $this->validateUrn($actor);
365 $this->actor
= $actor;
374 public function getActor()
382 * URI in SoapServer is actually the target namespace, not a URI; $uri must begin with 'urn:'.
387 public function setUri($uri)
389 $this->validateUrn($uri);
399 public function getUri()
407 * @param array $classmap
409 * @throws Exception\InvalidArgumentException for any invalid class in the class map
411 public function setClassmap($classmap)
413 if (!is_array($classmap)) {
414 throw new Exception\
InvalidArgumentException('Classmap must be an array');
416 foreach ($classmap as $class) {
417 if (!class_exists($class)) {
418 throw new Exception\
InvalidArgumentException('Invalid class in class map');
422 $this->classmap
= $classmap;
431 public function getClassmap()
433 return $this->classmap
;
437 * Set typemap with xml to php type mappings with appropriate validation.
439 * @param array $typeMap
441 * @throws Exception\InvalidArgumentException
443 public function setTypemap($typeMap)
445 if (!is_array($typeMap)) {
446 throw new Exception\
InvalidArgumentException('Typemap must be an array');
449 foreach ($typeMap as $type) {
450 if (!is_callable($type['from_xml'])) {
451 throw new Exception\
InvalidArgumentException(sprintf(
452 'Invalid from_xml callback for type: %s',
456 if (!is_callable($type['to_xml'])) {
457 throw new Exception\
InvalidArgumentException('Invalid to_xml callback for type: ' . $type['type_name']);
461 $this->typemap
= $typeMap;
470 public function getTypemap()
472 return $this->typemap
;
478 * @param string $wsdl URI or path to a WSDL
481 public function setWSDL($wsdl)
492 public function getWSDL()
498 * Set the SOAP Feature options.
500 * @param string|int $feature
503 public function setSoapFeatures($feature)
505 $this->features
= $feature;
510 * Return current SOAP Features options
514 public function getSoapFeatures()
516 return $this->features
;
520 * Set the SOAP WSDL Caching Options
522 * @param string|int|bool $options
525 public function setWSDLCache($options)
527 $this->wsdlCache
= $options;
532 * Get current SOAP WSDL Caching option
534 public function getWSDLCache()
536 return $this->wsdlCache
;
540 * Attach a function as a server method
542 * @param array|string $function Function name, array of function names to attach,
543 * or SOAP_FUNCTIONS_ALL to attach all functions
544 * @param string $namespace Ignored
546 * @throws Exception\InvalidArgumentException on invalid functions
548 public function addFunction($function, $namespace = '')
550 // Bail early if set to SOAP_FUNCTIONS_ALL
551 if ($this->functions
== SOAP_FUNCTIONS_ALL
) {
555 if (is_array($function)) {
556 foreach ($function as $func) {
557 if (is_string($func) && function_exists($func)) {
558 $this->functions
[] = $func;
560 throw new Exception\
InvalidArgumentException('One or more invalid functions specified in array');
563 } elseif (is_string($function) && function_exists($function)) {
564 $this->functions
[] = $function;
565 } elseif ($function == SOAP_FUNCTIONS_ALL
) {
566 $this->functions
= SOAP_FUNCTIONS_ALL
;
568 throw new Exception\
InvalidArgumentException('Invalid function specified');
571 if (is_array($this->functions
)) {
572 $this->functions
= array_unique($this->functions
);
579 * Attach a class to a server
581 * Accepts a class name to use when handling requests. Any additional
582 * arguments will be passed to that class' constructor when instantiated.
584 * See {@link setObject()} to set pre-configured object instances as request handlers.
586 * @param string|object $class Class name or object instance which executes
587 * SOAP Requests at endpoint.
588 * @param string $namespace
589 * @param null|array $argv
591 * @throws Exception\InvalidArgumentException if called more than once, or if class does not exist
593 public function setClass($class, $namespace = '', $argv = null)
595 if (isset($this->class)) {
596 throw new Exception\
InvalidArgumentException(
597 'A class has already been registered with this soap server instance'
601 if (is_object($class)) {
602 return $this->setObject($class);
605 if (!is_string($class)) {
606 throw new Exception\
InvalidArgumentException(sprintf(
607 'Invalid class argument (%s)',
612 if (!class_exists($class)) {
613 throw new Exception\
InvalidArgumentException(sprintf(
614 'Class "%s" does not exist',
619 $this->class = $class;
620 if (2 < func_num_args()) {
621 $argv = func_get_args();
622 $this->classArgs
= array_slice($argv, 2);
629 * Attach an object to a server
631 * Accepts an instantiated object to use when handling requests.
633 * @param object $object
635 * @throws Exception\InvalidArgumentException
637 public function setObject($object)
639 if (!is_object($object)) {
640 throw new Exception\
InvalidArgumentException(sprintf(
641 'Invalid object argument (%s)',
646 if (isset($this->object)) {
647 throw new Exception\
InvalidArgumentException(
648 'An object has already been registered with this soap server instance'
652 $this->object = $object;
657 * Return a server definition array
659 * Returns a list of all functions registered with {@link addFunction()},
660 * merged with all public methods of the class set with {@link setClass()}
665 public function getFunctions()
667 $functions = array();
668 if (null !== $this->class) {
669 $functions = get_class_methods($this->class);
670 } elseif (null !== $this->object) {
671 $functions = get_class_methods($this->object);
674 return array_merge((array) $this->functions
, $functions);
678 * Unimplemented: Load server definition
680 * @param array $definition
681 * @throws Exception\RuntimeException Unimplemented
683 public function loadFunctions($definition)
685 throw new Exception\
RuntimeException('Unimplemented method.');
689 * Set server persistence
691 * @param int $mode SOAP_PERSISTENCE_SESSION or SOAP_PERSISTENCE_REQUEST constants
693 * @throws Exception\InvalidArgumentException
695 public function setPersistence($mode)
697 if (!in_array($mode, array(SOAP_PERSISTENCE_SESSION
, SOAP_PERSISTENCE_REQUEST
))) {
698 throw new Exception\
InvalidArgumentException('Invalid persistence mode specified');
701 $this->persistence
= $mode;
706 * Get server persistence
710 public function getPersistence()
712 return $this->persistence
;
718 * $request may be any of:
719 * - DOMDocument; if so, then cast to XML
720 * - DOMNode; if so, then grab owner document and cast to XML
721 * - SimpleXMLElement; if so, then cast to XML
722 * - stdClass; if so, calls __toString() and verifies XML
723 * - string; if so, verifies XML
725 * @param DOMDocument|DOMNode|SimpleXMLElement|\stdClass|string $request
727 * @throws Exception\InvalidArgumentException
729 protected function _setRequest($request)
733 if ($request instanceof DOMDocument
) {
734 $xml = $request->saveXML();
735 } elseif ($request instanceof DOMNode
) {
736 $xml = $request->ownerDocument
->saveXML();
737 } elseif ($request instanceof SimpleXMLElement
) {
738 $xml = $request->asXML();
739 } elseif (is_object($request) ||
is_string($request)) {
740 if (is_object($request)) {
741 $xml = $request->__toString();
747 $loadEntities = libxml_disable_entity_loader(true);
749 $dom = new DOMDocument();
750 $loadStatus = $dom->loadXML($xml);
752 libxml_disable_entity_loader($loadEntities);
754 // @todo check libxml errors ? validate document ?
755 if (strlen($xml) == 0 ||
!$loadStatus) {
756 throw new Exception\
InvalidArgumentException('Invalid XML');
759 foreach ($dom->childNodes
as $child) {
760 if ($child->nodeType
=== XML_DOCUMENT_TYPE_NODE
) {
761 throw new Exception\
InvalidArgumentException('Invalid XML: Detected use of illegal DOCTYPE');
766 $this->request
= $xml;
771 * Retrieve request XML
775 public function getLastRequest()
777 return $this->request
;
781 * Set return response flag
783 * If true, {@link handle()} will return the response instead of
784 * automatically sending it back to the requesting client.
786 * The response is always available via {@link getResponse()}.
791 public function setReturnResponse($flag = true)
793 $this->returnResponse
= (bool) $flag;
798 * Retrieve return response flag
802 public function getReturnResponse()
804 return $this->returnResponse
;
812 public function getResponse()
814 return $this->response
;
818 * Get SoapServer object
820 * Uses {@link $wsdl} and return value of {@link getOptions()} to instantiate
821 * SoapServer object, and then registers any functions or class with it, as
822 * well as persistence.
826 public function getSoap()
828 if ($this->server
instanceof SoapServer
) {
829 return $this->server
;
832 $options = $this->getOptions();
833 $server = new SoapServer($this->wsdl
, $options);
835 if (!empty($this->functions
)) {
836 $server->addFunction($this->functions
);
839 if (!empty($this->class)) {
840 $args = $this->classArgs
;
841 array_unshift($args, $this->class);
842 call_user_func_array(array($server, 'setClass'), $args);
845 if (!empty($this->object)) {
846 $server->setObject($this->object);
849 if (null !== $this->persistence
) {
850 $server->setPersistence($this->persistence
);
853 $this->server
= $server;
854 return $this->server
;
858 * Proxy for _getSoap method
860 * @return SoapServer the soapServer instance
861 public function getSoap()
863 return $this->_getSoap();
870 * Instantiates SoapServer object with options set in object, and
871 * dispatches its handle() method.
873 * $request may be any of:
874 * - DOMDocument; if so, then cast to XML
875 * - DOMNode; if so, then grab owner document and cast to XML
876 * - SimpleXMLElement; if so, then cast to XML
877 * - stdClass; if so, calls __toString() and verifies XML
878 * - string; if so, verifies XML
880 * If no request is passed, pulls request using php:://input (for
881 * cross-platform compatibility purposes).
883 * @param DOMDocument|DOMNode|SimpleXMLElement|\stdClass|string $request Optional request
884 * @return void|string
886 public function handle($request = null)
888 if (null === $request) {
889 $request = file_get_contents('php://input');
892 // Set Server error handler
893 $displayErrorsOriginalState = $this->_initializeSoapErrorContext();
895 $setRequestException = null;
897 $this->_setRequest($request);
898 } catch (\Exception
$e) {
899 $setRequestException = $e;
902 $soap = $this->getSoap();
905 $this->response
= '';
907 if ($setRequestException instanceof \Exception
) {
908 // Create SOAP fault message if we've caught a request exception
909 $fault = $this->fault($setRequestException->getMessage(), 'Sender');
913 $soap->handle($this->request
);
914 } catch (\Exception
$e) {
915 $fault = $this->fault($e);
917 $this->response
= ob_get_clean();
920 // Restore original error handler
921 restore_error_handler();
922 ini_set('display_errors', (string) $displayErrorsOriginalState);
924 // Send a fault, if we have one
925 if ($fault instanceof SoapFault
&& !$this->returnResponse
) {
926 $soap->fault($fault->faultcode
, $fault->getMessage());
931 // Echo the response, if we're not returning it
932 if (!$this->returnResponse
) {
933 echo $this->response
;
938 // Return a fault, if we have it
939 if ($fault instanceof SoapFault
) {
943 // Return the response
944 return $this->response
;
948 * Method initializes the error context that the SOAPServer environment will run in.
950 * @return bool display_errors original value
952 protected function _initializeSoapErrorContext()
954 $displayErrorsOriginalState = ini_get('display_errors');
955 ini_set('display_errors', '0');
956 set_error_handler(array($this, 'handlePhpErrors'), E_USER_ERROR
);
957 return $displayErrorsOriginalState;
961 * Set the debug mode.
962 * In debug mode, all exceptions are send to the client.
967 public function setDebugMode($debug)
969 $this->debug
= $debug;
974 * Validate and register fault exception
976 * @param string|array $class Exception class or array of exception classes
978 * @throws Exception\InvalidArgumentException
980 public function registerFaultException($class)
982 if (is_array($class)) {
983 foreach ($class as $row) {
984 $this->registerFaultException($row);
986 } elseif (is_string($class)
987 && class_exists($class)
988 && (is_subclass_of($class, 'Exception') ||
'Exception' === $class)
990 $ref = new ReflectionClass($class);
992 $this->faultExceptions
[] = $ref->getName();
993 $this->faultExceptions
= array_unique($this->faultExceptions
);
995 throw new Exception\
InvalidArgumentException(
996 'Argument for Zend\Soap\Server::registerFaultException should be'
997 . ' string or array of strings with valid exception names'
1005 * Checks if provided fault name is registered as valid in this server.
1007 * @param string $fault Name of a fault class
1010 public function isRegisteredAsFaultException($fault)
1016 $ref = new ReflectionClass($fault);
1017 $classNames = $ref->getName();
1018 return in_array($classNames, $this->faultExceptions
);
1022 * Deregister a fault exception from the fault exception stack
1024 * @param string $class
1027 public function deregisterFaultException($class)
1029 if (in_array($class, $this->faultExceptions
, true)) {
1030 $index = array_search($class, $this->faultExceptions
);
1031 unset($this->faultExceptions
[$index]);
1039 * Return fault exceptions list
1043 public function getFaultExceptions()
1045 return $this->faultExceptions
;
1049 * Return caught exception during business code execution
1050 * @return null|\Exception caught exception
1052 public function getException()
1054 return $this->caughtException
;
1058 * Generate a server fault
1060 * Note that the arguments are reverse to those of SoapFault.
1062 * If an exception is passed as the first argument, its message and code
1063 * will be used to create the fault object if it has been registered via
1064 * {@Link registerFaultException()}.
1066 * @link http://www.w3.org/TR/soap12-part1/#faultcodes
1067 * @param string|\Exception $fault
1068 * @param string $code SOAP Fault Codes
1071 public function fault($fault = null, $code = 'Receiver')
1073 $this->caughtException
= (is_string($fault)) ?
new \
Exception($fault) : $fault;
1075 if ($fault instanceof \Exception
) {
1076 if ($this->isRegisteredAsFaultException($fault)) {
1077 $message = $fault->getMessage();
1078 $eCode = $fault->getCode();
1079 $code = empty($eCode) ?
$code : $eCode;
1081 $message = 'Unknown error';
1083 } elseif (is_string($fault)) {
1086 $message = 'Unknown error';
1089 $allowedFaultModes = array(
1092 'DataEncodingUnknown',
1097 if (!in_array($code, $allowedFaultModes)) {
1101 return new SoapFault($code, $message);
1105 * Throw PHP errors as SoapFaults
1108 * @param string $errstr
1109 * @param string $errfile
1110 * @param int $errline
1111 * @param array $errcontext
1114 public function handlePhpErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null)
1116 throw $this->fault($errstr, 'Receiver');