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 = [];
48 * Array of SOAP type => PHP class pairings for handling return/incoming values
60 * Registered fault exceptions
63 protected $faultExceptions = [];
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 = [];
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;
149 * The send_errors Options of SOAP Server
152 protected $sendErrors;
155 * Allows LIBXML_PARSEHUGE Options of DOMDocument->loadXML( string $source [, int $options = 0 ] ) to be set
158 protected $parseHuge;
163 * Sets display_errors INI setting to off (prevent client errors due to bad
164 * XML in response). Registers {@link handlePhpErrors()} as error handler
167 * If $wsdl is provided, it is passed on to {@link setWSDL()}; if any
168 * options are specified, they are passed on to {@link setOptions()}.
170 * @param string $wsdl
171 * @param array $options
172 * @throws Exception\ExtensionNotLoadedException
174 public function __construct($wsdl = null, array $options = null)
176 if (! extension_loaded('soap')) {
177 throw new Exception\
ExtensionNotLoadedException('SOAP extension is not loaded.');
180 if (null !== $wsdl) {
181 $this->setWSDL($wsdl);
184 if (null !== $options) {
185 $this->setOptions($options);
192 * Allows setting options as an associative array of option => value pairs.
194 * @param array|\Traversable $options
197 public function setOptions($options)
199 if ($options instanceof Traversable
) {
200 $options = ArrayUtils
::iteratorToArray($options);
203 foreach ($options as $key => $value) {
204 switch (strtolower($key)) {
206 $this->setActor($value);
211 $this->setClassmap($value);
216 $this->setTypemap($value);
220 $this->setEncoding($value);
225 $this->setSoapVersion($value);
229 $this->setUri($value);
233 $this->setWSDL($value);
237 $this->setWSDLCache($value);
241 $this->setSoapFeatures($value);
245 $this->setSendErrors($value);
249 $this->setParseHuge($value);
261 * Return array of options suitable for using with SoapServer constructor
265 public function getOptions()
268 if (null !== $this->actor
) {
269 $options['actor'] = $this->getActor();
272 if (null !== $this->classmap
) {
273 $options['classmap'] = $this->getClassmap();
276 if (null !== $this->typemap
) {
277 $options['typemap'] = $this->getTypemap();
280 if (null !== $this->encoding
) {
281 $options['encoding'] = $this->getEncoding();
284 if (null !== $this->soapVersion
) {
285 $options['soap_version'] = $this->getSoapVersion();
288 if (null !== $this->uri
) {
289 $options['uri'] = $this->getUri();
292 if (null !== $this->features
) {
293 $options['features'] = $this->getSoapFeatures();
296 if (null !== $this->wsdlCache
) {
297 $options['cache_wsdl'] = $this->getWSDLCache();
300 if (null !== $this->sendErrors
) {
301 $options['send_errors'] = $this->getSendErrors();
304 if (null !== $this->parseHuge
) {
305 $options['parse_huge'] = $this->getParseHuge();
314 * @param string $encoding
316 * @throws Exception\InvalidArgumentException with invalid encoding argument
318 public function setEncoding($encoding)
320 if (! is_string($encoding)) {
321 throw new Exception\
InvalidArgumentException('Invalid encoding specified');
324 $this->encoding
= $encoding;
333 public function getEncoding()
335 return $this->encoding
;
341 * @param int $version One of the SOAP_1_1 or SOAP_1_2 constants
343 * @throws Exception\InvalidArgumentException with invalid soap version argument
345 public function setSoapVersion($version)
347 if (! in_array($version, [SOAP_1_1
, SOAP_1_2
])) {
348 throw new Exception\
InvalidArgumentException('Invalid soap version specified');
351 $this->soapVersion
= $version;
360 public function getSoapVersion()
362 return $this->soapVersion
;
366 * Check for valid URN
370 * @throws Exception\InvalidArgumentException on invalid URN
372 public function validateUrn($urn)
374 $scheme = parse_url($urn, PHP_URL_SCHEME
);
375 if ($scheme === false ||
$scheme === null) {
376 throw new Exception\
InvalidArgumentException('Invalid URN');
385 * Actor is the actor URI for the server.
387 * @param string $actor
390 public function setActor($actor)
392 $this->validateUrn($actor);
393 $this->actor
= $actor;
402 public function getActor()
410 * URI in SoapServer is actually the target namespace, not a URI; $uri must begin with 'urn:'.
415 public function setUri($uri)
417 $this->validateUrn($uri);
427 public function getUri()
435 * @param array $classmap
437 * @throws Exception\InvalidArgumentException for any invalid class in the class map
439 public function setClassmap($classmap)
441 if (! is_array($classmap)) {
442 throw new Exception\
InvalidArgumentException('Classmap must be an array');
444 foreach ($classmap as $class) {
445 if (! class_exists($class)) {
446 throw new Exception\
InvalidArgumentException('Invalid class in class map');
450 $this->classmap
= $classmap;
459 public function getClassmap()
461 return $this->classmap
;
465 * Set typemap with xml to php type mappings with appropriate validation.
467 * @param array $typeMap
469 * @throws Exception\InvalidArgumentException
471 public function setTypemap($typeMap)
473 if (! is_array($typeMap)) {
474 throw new Exception\
InvalidArgumentException('Typemap must be an array');
477 foreach ($typeMap as $type) {
478 if (! is_callable($type['from_xml'])) {
479 throw new Exception\
InvalidArgumentException(sprintf(
480 'Invalid from_xml callback for type: %s',
484 if (! is_callable($type['to_xml'])) {
485 throw new Exception\
InvalidArgumentException('Invalid to_xml callback for type: ' . $type['type_name']);
489 $this->typemap
= $typeMap;
498 public function getTypemap()
500 return $this->typemap
;
506 * @param string $wsdl URI or path to a WSDL
509 public function setWSDL($wsdl)
520 public function getWSDL()
526 * Set the SOAP Feature options.
528 * @param string|int $feature
531 public function setSoapFeatures($feature)
533 $this->features
= $feature;
538 * Return current SOAP Features options
542 public function getSoapFeatures()
544 return $this->features
;
548 * Set the SOAP WSDL Caching Options
550 * @param string|int|bool $options
553 public function setWSDLCache($options)
555 $this->wsdlCache
= $options;
560 * Get current SOAP WSDL Caching option
562 public function getWSDLCache()
564 return $this->wsdlCache
;
568 * Set the SOAP send_errors Option
570 * @param bool $sendErrors
573 public function setSendErrors($sendErrors)
575 $this->sendErrors
= (bool) $sendErrors;
580 * Get current SOAP send_errors option
584 public function getSendErrors()
586 return $this->sendErrors
;
590 * Set flag to allow DOMDocument->loadXML() to parse huge nodes
592 * @param bool $parseHuge
595 public function setParseHuge($parseHuge)
597 $this->parseHuge
= (bool) $parseHuge;
602 * Get flag to allow DOMDocument->loadXML() to parse huge nodes
606 public function getParseHuge()
608 return $this->parseHuge
;
612 * Attach a function as a server method
614 * @param array|string $function Function name, array of function names to attach,
615 * or SOAP_FUNCTIONS_ALL to attach all functions
616 * @param string $namespace Ignored
618 * @throws Exception\InvalidArgumentException on invalid functions
620 public function addFunction($function, $namespace = '')
622 // Bail early if set to SOAP_FUNCTIONS_ALL
623 if ($this->functions
== SOAP_FUNCTIONS_ALL
) {
627 if (is_array($function)) {
628 foreach ($function as $func) {
629 if (is_string($func) && function_exists($func)) {
630 $this->functions
[] = $func;
632 throw new Exception\
InvalidArgumentException('One or more invalid functions specified in array');
635 } elseif (is_string($function) && function_exists($function)) {
636 $this->functions
[] = $function;
637 } elseif ($function == SOAP_FUNCTIONS_ALL
) {
638 $this->functions
= SOAP_FUNCTIONS_ALL
;
640 throw new Exception\
InvalidArgumentException('Invalid function specified');
643 if (is_array($this->functions
)) {
644 $this->functions
= array_unique($this->functions
);
651 * Attach a class to a server
653 * Accepts a class name to use when handling requests. Any additional
654 * arguments will be passed to that class' constructor when instantiated.
656 * See {@link setObject()} to set pre-configured object instances as request handlers.
658 * @param string|object $class Class name or object instance which executes
659 * SOAP Requests at endpoint.
660 * @param string $namespace
661 * @param null|array $argv
663 * @throws Exception\InvalidArgumentException if called more than once, or if class does not exist
665 public function setClass($class, $namespace = '', $argv = null)
667 if (isset($this->class)) {
668 throw new Exception\
InvalidArgumentException(
669 'A class has already been registered with this soap server instance'
673 if (is_object($class)) {
674 return $this->setObject($class);
677 if (! is_string($class)) {
678 throw new Exception\
InvalidArgumentException(sprintf(
679 'Invalid class argument (%s)',
684 if (! class_exists($class)) {
685 throw new Exception\
InvalidArgumentException(sprintf(
686 'Class "%s" does not exist',
691 $this->class = $class;
692 if (2 < func_num_args()) {
693 $argv = func_get_args();
694 $this->classArgs
= array_slice($argv, 2);
701 * Attach an object to a server
703 * Accepts an instantiated object to use when handling requests.
705 * @param object $object
707 * @throws Exception\InvalidArgumentException
709 public function setObject($object)
711 if (! is_object($object)) {
712 throw new Exception\
InvalidArgumentException(sprintf(
713 'Invalid object argument (%s)',
718 if (isset($this->object)) {
719 throw new Exception\
InvalidArgumentException(
720 'An object has already been registered with this soap server instance'
724 $this->object = $object;
729 * Return a server definition array
731 * Returns a list of all functions registered with {@link addFunction()},
732 * merged with all public methods of the class set with {@link setClass()}
737 public function getFunctions()
740 if (null !== $this->class) {
741 $functions = get_class_methods($this->class);
742 } elseif (null !== $this->object) {
743 $functions = get_class_methods($this->object);
746 return array_merge((array) $this->functions
, $functions);
750 * Unimplemented: Load server definition
752 * @param array $definition
753 * @throws Exception\RuntimeException Unimplemented
755 public function loadFunctions($definition)
757 throw new Exception\
RuntimeException('Unimplemented method.');
761 * Set server persistence
763 * @param int $mode SOAP_PERSISTENCE_SESSION or SOAP_PERSISTENCE_REQUEST constants
765 * @throws Exception\InvalidArgumentException
767 public function setPersistence($mode)
769 if (! in_array($mode, [SOAP_PERSISTENCE_SESSION
, SOAP_PERSISTENCE_REQUEST
])) {
770 throw new Exception\
InvalidArgumentException('Invalid persistence mode specified');
773 $this->persistence
= $mode;
778 * Get server persistence
782 public function getPersistence()
784 return $this->persistence
;
790 * $request may be any of:
791 * - DOMDocument; if so, then cast to XML
792 * - DOMNode; if so, then grab owner document and cast to XML
793 * - SimpleXMLElement; if so, then cast to XML
794 * - stdClass; if so, calls __toString() and verifies XML
795 * - string; if so, verifies XML
797 * @param DOMDocument|DOMNode|SimpleXMLElement|\stdClass|string $request
799 * @throws Exception\InvalidArgumentException
801 protected function setRequest($request)
805 if ($request instanceof DOMDocument
) {
806 $xml = $request->saveXML();
807 } elseif ($request instanceof DOMNode
) {
808 $xml = $request->ownerDocument
->saveXML();
809 } elseif ($request instanceof SimpleXMLElement
) {
810 $xml = $request->asXML();
811 } elseif (is_object($request) ||
is_string($request)) {
812 if (is_object($request)) {
813 $xml = $request->__toString();
819 if (strlen($xml) === 0) {
820 throw new Exception\
InvalidArgumentException('Empty request');
823 $loadEntities = libxml_disable_entity_loader(true);
825 $dom = new DOMDocument();
827 if (true === $this->getParseHuge()) {
828 $loadStatus = $dom->loadXML($xml, LIBXML_PARSEHUGE
);
830 $loadStatus = $dom->loadXML($xml);
833 libxml_disable_entity_loader($loadEntities);
835 // @todo check libxml errors ? validate document ?
837 throw new Exception\
InvalidArgumentException('Invalid XML');
840 foreach ($dom->childNodes
as $child) {
841 if ($child->nodeType
=== XML_DOCUMENT_TYPE_NODE
) {
842 throw new Exception\
InvalidArgumentException('Invalid XML: Detected use of illegal DOCTYPE');
847 $this->request
= $xml;
852 * Retrieve request XML
856 public function getLastRequest()
858 return $this->request
;
862 * Set return response flag
864 * If true, {@link handle()} will return the response instead of
865 * automatically sending it back to the requesting client.
867 * The response is always available via {@link getResponse()}.
872 public function setReturnResponse($flag = true)
874 $this->returnResponse
= (bool) $flag;
879 * Retrieve return response flag
883 public function getReturnResponse()
885 return $this->returnResponse
;
893 public function getResponse()
895 return $this->response
;
899 * Get SoapServer object
901 * Uses {@link $wsdl} and return value of {@link getOptions()} to instantiate
902 * SoapServer object, and then registers any functions or class with it, as
903 * well as persistence.
907 public function getSoap()
909 if ($this->server
instanceof SoapServer
) {
910 return $this->server
;
913 $options = $this->getOptions();
914 $server = new SoapServer($this->wsdl
, $options);
916 if (! empty($this->functions
)) {
917 $server->addFunction($this->functions
);
920 if (! empty($this->class)) {
921 $args = $this->classArgs
;
922 array_unshift($args, $this->class);
923 call_user_func_array([$server, 'setClass'], $args);
926 if (! empty($this->object)) {
927 $server->setObject($this->object);
930 if (null !== $this->persistence
) {
931 $server->setPersistence($this->persistence
);
934 $this->server
= $server;
935 return $this->server
;
939 * Proxy for _getSoap method
941 * @return SoapServer the soapServer instance
942 public function getSoap()
944 return $this->_getSoap();
951 * Instantiates SoapServer object with options set in object, and
952 * dispatches its handle() method.
954 * $request may be any of:
955 * - DOMDocument; if so, then cast to XML
956 * - DOMNode; if so, then grab owner document and cast to XML
957 * - SimpleXMLElement; if so, then cast to XML
958 * - stdClass; if so, calls __toString() and verifies XML
959 * - string; if so, verifies XML
961 * If no request is passed, pulls request using php:://input (for
962 * cross-platform compatibility purposes).
964 * @param DOMDocument|DOMNode|SimpleXMLElement|\stdClass|string $request Optional request
965 * @return void|string
967 public function handle($request = null)
969 if (null === $request) {
970 $request = file_get_contents('php://input');
973 // Set Server error handler
974 $displayErrorsOriginalState = $this->initializeSoapErrorContext();
976 $setRequestException = null;
978 $this->setRequest($request);
979 } catch (\Exception
$e) {
980 $setRequestException = $e;
983 $soap = $this->getSoap();
986 $this->response
= '';
988 if ($setRequestException instanceof \Exception
) {
989 // Create SOAP fault message if we've caught a request exception
990 $fault = $this->fault($setRequestException->getMessage(), 'Sender');
994 $soap->handle($this->request
);
995 } catch (\Exception
$e) {
996 $fault = $this->fault($e);
998 $this->response
= ob_get_clean();
1001 // Restore original error handler
1002 restore_error_handler();
1003 ini_set('display_errors', (string) $displayErrorsOriginalState);
1005 // Send a fault, if we have one
1006 if ($fault instanceof SoapFault
&& ! $this->returnResponse
) {
1007 $soap->fault($fault->faultcode
, $fault->getMessage());
1012 // Echo the response, if we're not returning it
1013 if (! $this->returnResponse
) {
1014 echo $this->response
;
1019 // Return a fault, if we have it
1020 if ($fault instanceof SoapFault
) {
1024 // Return the response
1025 return $this->response
;
1029 * Method initializes the error context that the SOAPServer environment will run in.
1031 * @return bool display_errors original value
1033 protected function initializeSoapErrorContext()
1035 $displayErrorsOriginalState = ini_get('display_errors');
1036 ini_set('display_errors', '0');
1037 set_error_handler([$this, 'handlePhpErrors'], E_USER_ERROR
);
1038 return $displayErrorsOriginalState;
1042 * Set the debug mode.
1043 * In debug mode, all exceptions are send to the client.
1045 * @param bool $debug
1048 public function setDebugMode($debug)
1050 $this->debug
= $debug;
1055 * Validate and register fault exception
1057 * @param string|array $class Exception class or array of exception classes
1059 * @throws Exception\InvalidArgumentException
1061 public function registerFaultException($class)
1063 if (is_array($class)) {
1064 foreach ($class as $row) {
1065 $this->registerFaultException($row);
1067 } elseif (is_string($class)
1068 && class_exists($class)
1069 && (is_subclass_of($class, 'Exception') ||
'Exception' === $class)
1071 $ref = new ReflectionClass($class);
1073 $this->faultExceptions
[] = $ref->getName();
1074 $this->faultExceptions
= array_unique($this->faultExceptions
);
1076 throw new Exception\
InvalidArgumentException(
1077 'Argument for Zend\Soap\Server::registerFaultException should be'
1078 . ' string or array of strings with valid exception names'
1086 * Checks if provided fault name is registered as valid in this server.
1088 * @param string $fault Name of a fault class
1091 public function isRegisteredAsFaultException($fault)
1097 $ref = new ReflectionClass($fault);
1098 $classNames = $ref->getName();
1099 return in_array($classNames, $this->faultExceptions
);
1103 * Deregister a fault exception from the fault exception stack
1105 * @param string $class
1108 public function deregisterFaultException($class)
1110 if (in_array($class, $this->faultExceptions
, true)) {
1111 $index = array_search($class, $this->faultExceptions
);
1112 unset($this->faultExceptions
[$index]);
1120 * Return fault exceptions list
1124 public function getFaultExceptions()
1126 return $this->faultExceptions
;
1130 * Return caught exception during business code execution
1131 * @return null|\Exception caught exception
1133 public function getException()
1135 return $this->caughtException
;
1139 * Generate a server fault
1141 * Note that the arguments are reverse to those of SoapFault.
1143 * If an exception is passed as the first argument, its message and code
1144 * will be used to create the fault object if it has been registered via
1145 * {@Link registerFaultException()}.
1147 * @link http://www.w3.org/TR/soap12-part1/#faultcodes
1148 * @param string|\Exception $fault
1149 * @param string $code SOAP Fault Codes
1152 public function fault($fault = null, $code = 'Receiver')
1154 $this->caughtException
= (is_string($fault)) ?
new \
Exception($fault) : $fault;
1156 if ($fault instanceof \Exception
) {
1157 if ($this->isRegisteredAsFaultException($fault)) {
1158 $message = $fault->getMessage();
1159 $eCode = $fault->getCode();
1160 $code = empty($eCode) ?
$code : $eCode;
1162 $message = 'Unknown error';
1164 } elseif (is_string($fault)) {
1167 $message = 'Unknown error';
1170 $allowedFaultModes = [
1173 'DataEncodingUnknown',
1178 if (! in_array($code, $allowedFaultModes)) {
1182 return new SoapFault($code, $message);
1186 * Throw PHP errors as SoapFaults
1189 * @param string $errstr
1192 public function handlePhpErrors($errno, $errstr)
1194 throw $this->fault($errstr, 'Receiver');