Client.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more |
  17. // | Authors: Dietrich Ayala <dietrich@ganx4.com> Original Author |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: Client.php,v 1.62 2003/08/06 20:08:45 arnaud Exp $
  21. //
  22. require_once 'SOAP/Value.php';
  23. require_once 'SOAP/Base.php';
  24. require_once 'SOAP/Transport.php';
  25. require_once 'SOAP/WSDL.php';
  26. require_once 'SOAP/Fault.php';
  27. require_once 'SOAP/Parser.php';
  28. /**
  29. * SOAP Client Class
  30. * this class is the main interface for making soap requests
  31. *
  32. * basic usage:
  33. * $soapclient = new SOAP_Client( string path [ , boolean wsdl] );
  34. * echo $soapclient->call( string methodname [ , array parameters] );
  35. *
  36. * originaly based on SOAPx4 by Dietrich Ayala http://dietrich.ganx4.com/soapx4
  37. *
  38. * @access public
  39. * @version $Id: Client.php,v 1.62 2003/08/06 20:08:45 arnaud Exp $
  40. * @package SOAP::Client
  41. * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
  42. * @author Stig Bakken <ssb@fast.no> Conversion to PEAR
  43. * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
  44. */
  45. class SOAP_Client extends SOAP_Base
  46. {
  47. /**
  48. * Communication endpoint.
  49. *
  50. * Currently the following transport formats are supported:
  51. * - HTTP
  52. * - SMTP
  53. *
  54. * Example endpoints:
  55. * http://www.example.com/soap/server.php
  56. * https://www.example.com/soap/server.php
  57. * mailto:soap@example.com
  58. *
  59. * @var string
  60. * @see SOAP_Client()
  61. */
  62. var $_endpoint = '';
  63. /**
  64. * portname
  65. *
  66. * @var string contains the SOAP PORT name that is used by the client
  67. */
  68. var $_portName = '';
  69. /**
  70. * Endpoint type
  71. *
  72. * @var string e.g. wdsl
  73. */
  74. var $__endpointType = '';
  75. /**
  76. * wire
  77. *
  78. * @var string contains outoing and incoming data stream for debugging.
  79. */
  80. var $xml; // contains the received xml
  81. var $wire;
  82. var $__last_request = null;
  83. var $__last_response = null;
  84. /**
  85. * Options
  86. *
  87. * @var array
  88. */
  89. var $__options = array('trace'=>0);
  90. /**
  91. * encoding
  92. *
  93. * @var string Contains the character encoding used for XML parser, etc.
  94. */
  95. var $_encoding = SOAP_DEFAULT_ENCODING;
  96. /**
  97. * headersOut
  98. *
  99. * @var array contains an array of SOAP_Headers that we are sending
  100. */
  101. var $headersOut = null;
  102. /**
  103. * headersOut
  104. *
  105. * @var array contains an array headers we recieved back in the response
  106. */
  107. var $headersIn = null;
  108. /**
  109. * __proxy_params
  110. *
  111. * @var array contains options for HTTP_Request class (see HTTP/Request.php)
  112. */
  113. var $__proxy_params = array();
  114. var $_soap_transport = NULL;
  115. /**
  116. * SOAP_Client constructor
  117. *
  118. * @param string endpoint (URL)
  119. * @param boolean wsdl (true if endpoint is a wsdl file)
  120. * @param string portName
  121. * @param array contains options for HTTP_Request class (see HTTP/Request.php)
  122. * @access public
  123. */
  124. function SOAP_Client($endpoint, $wsdl = false, $portName = false, $proxy_params=array())
  125. {
  126. parent::SOAP_Base('Client');
  127. $this->_endpoint = $endpoint;
  128. $this->_portName = $portName;
  129. $this->__proxy_params = $proxy_params;
  130. $wsdl = $wsdl?$wsdl:strcasecmp('wsdl',substr($endpoint,strlen($endpoint)-4))==0;
  131. // make values
  132. if ($wsdl) {
  133. $this->__endpointType = 'wsdl';
  134. // instantiate wsdl class
  135. $this->_wsdl =& new SOAP_WSDL($this->_endpoint, $this->__proxy_params);
  136. if ($this->_wsdl->fault) {
  137. $this->_raiseSoapFault($this->_wsdl->fault);
  138. }
  139. }
  140. }
  141. function _reset()
  142. {
  143. $this->xml = NULL;
  144. $this->wire = NULL;
  145. $this->__last_request = NULL;
  146. $this->__last_response = NULL;
  147. $this->headersIn = NULL;
  148. $this->headersOut = NULL;
  149. }
  150. /**
  151. * setEncoding
  152. *
  153. * set the character encoding, limited to 'UTF-8', 'US_ASCII' and 'ISO-8859-1'
  154. *
  155. * @param string encoding
  156. * @return mixed returns NULL or SOAP_Fault
  157. * @access public
  158. */
  159. function setEncoding($encoding)
  160. {
  161. if (in_array($encoding, $this->_encodings)) {
  162. $this->_encoding = $encoding;
  163. return NULL;
  164. }
  165. return $this->_raiseSoapFault('Invalid Encoding');
  166. }
  167. /**
  168. * addHeader
  169. *
  170. * To add headers to the envelop, you use this function, sending it a
  171. * SOAP_Header class instance.
  172. *
  173. * @param SOAP_Header a soap value to send as a header
  174. * @access public
  175. */
  176. function addHeader(&$soap_value)
  177. {
  178. # add a new header to the message
  179. if (is_a($soap_value,'soap_header')) {
  180. $this->headersOut[] =& $soap_value;
  181. } else if (gettype($soap_value) == 'array') {
  182. // name, value, namespace, mustunderstand, actor
  183. $this->headersOut[] =& new SOAP_Header($soap_value[0], NULL, $soap_value[1], $soap_value[2], $soap_value[3]);;
  184. } else {
  185. $this->_raiseSoapFault("Don't understand the header info you provided. Must be array or SOAP_Header.");
  186. }
  187. }
  188. /**
  189. * SOAP_Client::call
  190. *
  191. * the namespace parameter is overloaded to accept an array of
  192. * options that can contain data necessary for various transports
  193. * if it is used as an array, it MAY contain a namespace value and a
  194. * soapaction value. If it is overloaded, the soapaction parameter is
  195. * ignored and MUST be placed in the options array. This is done
  196. * to provide backwards compatibility with current clients, but
  197. * may be removed in the future.
  198. *
  199. * @param string method
  200. * @param array params
  201. * @param array options (hash with namespace, soapaction, timeout, from, subject, etc.)
  202. *
  203. * The options parameter can have a variety of values added. The currently supported
  204. * values are:
  205. * namespace
  206. * soapaction
  207. * timeout (http socket timeout)
  208. * from (smtp)
  209. * transfer-encoding (smtp, sets the Content-Transfer-Encoding header)
  210. * subject (smtp, subject header)
  211. * headers (smtp, array-hash of extra smtp headers)
  212. *
  213. * @return array of results
  214. * @access public
  215. */
  216. function &call($method, &$params, $namespace = false, $soapAction = false)
  217. {
  218. $this->headersIn = null;
  219. $this->__last_request = null;
  220. $this->__last_response = null;
  221. $this->wire = null;
  222. $this->xml = NULL;
  223. $soap_data =& $this->__generate($method, $params, $namespace, $soapAction);
  224. if (PEAR::isError($soap_data)) {
  225. return $this->_raiseSoapFault($soap_data);
  226. }
  227. // __generate may have changed the endpoint if the wsdl has more
  228. // than one service, so we need to see if we need to generate
  229. // a new transport to hook to a different URI. Since the transport
  230. // protocol can also change, we need to get an entirely new object,
  231. // though this could probably be optimized.
  232. if (!$this->_soap_transport || $this->_endpoint != $this->_soap_transport->url) {
  233. $this->_soap_transport =& SOAP_Transport::getTransport($this->_endpoint);
  234. if (PEAR::isError($this->_soap_transport)) {
  235. $fault =& $this->_soap_transport;
  236. $this->_soap_transport = NULL;
  237. return $this->_raiseSoapFault($fault);
  238. }
  239. }
  240. $this->_soap_transport->encoding = $this->_encoding;
  241. // send the message
  242. $transport_options = array_merge_recursive($this->__proxy_params, $this->__options);
  243. $this->xml =& $this->_soap_transport->send($soap_data, $transport_options);
  244. // save the wire information for debugging
  245. if ($this->__options['trace'] > 0) {
  246. $this->__last_request =& $this->_soap_transport->outgoing_payload;
  247. $this->__last_response =& $this->_soap_transport->incoming_payload;
  248. $this->wire =& $this->__get_wire();
  249. }
  250. if ($this->_soap_transport->fault) {
  251. return $this->_raiseSoapFault($this->xml);
  252. }
  253. $this->__attachments =& $this->_soap_transport->attachments;
  254. $this->__result_encoding = $this->_soap_transport->result_encoding;
  255. if (isset($this->__options['result']) && $this->__options['result'] != 'parse') return $this->xml;
  256. return $this->__parse($this->xml, $this->__result_encoding,$this->__attachments);
  257. }
  258. /**
  259. * Sets option to use with the transports layers.
  260. *
  261. * An example of such use is
  262. * $soapclient->setOpt('curl', CURLOPT_VERBOSE, 1)
  263. * to pass a specific option to when using an SSL connection.
  264. *
  265. * @access public
  266. * @param string $category category to which the option applies
  267. * @param string $option option name
  268. * @param string $value option value
  269. * @return void
  270. */
  271. function setOpt($category, $option, $value = null)
  272. {
  273. if (!is_null($value)) {
  274. if (!isset($this->__options[$category])) {
  275. $this->__options[$category] = array();
  276. }
  277. $this->__options[$category][$option] = $value;
  278. } else {
  279. $this->__options[$category] = $option;
  280. }
  281. }
  282. /**
  283. * SOAP_Client::__call
  284. *
  285. * Overload extension support
  286. * if the overload extension is loaded, you can call the client class
  287. * with a soap method name
  288. * $soap = new SOAP_Client(....);
  289. * $value = $soap->getStockQuote('MSFT');
  290. *
  291. * @param string method
  292. * @param array args
  293. * @param string retur_value
  294. *
  295. * @return boolean
  296. * @access public
  297. */
  298. function &__call($method, &$args, &$return_value)
  299. {
  300. // XXX overloading lowercases the method name, we
  301. // need to look into the wsdl and try to find
  302. // the correct method name to get the correct
  303. // case for the call.
  304. if ($this->_wsdl)
  305. $this->_wsdl->matchMethod($method);
  306. $return_value =& $this->call($method, $args);
  307. return TRUE;
  308. }
  309. function &__getlastrequest()
  310. {
  311. return $this->__last_request;
  312. }
  313. function &__getlastresponse()
  314. {
  315. return $this->__last_response;
  316. }
  317. function __use($use)
  318. {
  319. $this->__options['use'] = $use;
  320. }
  321. function __style($style)
  322. {
  323. $this->__options['style'] = $style;
  324. }
  325. function __trace($level)
  326. {
  327. $this->__options['trace'] = $level;
  328. }
  329. function &__generate($method, &$params, $namespace = false, $soapAction = false)
  330. {
  331. $this->fault = null;
  332. $this->__options['input']='parse';
  333. $this->__options['result']='parse';
  334. $this->__options['parameters'] = false;
  335. if ($params && gettype($params) != 'array') {
  336. $params = array($params);
  337. }
  338. if (gettype($namespace) == 'array') {
  339. foreach ($namespace as $optname=>$opt) {
  340. $this->__options[strtolower($optname)]=$opt;
  341. }
  342. if (isset($this->__options['namespace'])) $namespace = $this->__options['namespace'];
  343. else $namespace = false;
  344. } else {
  345. // we'll place soapaction into our array for usage in the transport
  346. $this->__options['soapaction'] = $soapAction;
  347. $this->__options['namespace'] = $namespace;
  348. }
  349. if ($this->__endpointType == 'wsdl') {
  350. $this->_setSchemaVersion($this->_wsdl->xsd);
  351. // get portName
  352. if (!$this->_portName) {
  353. $this->_portName = $this->_wsdl->getPortName($method);
  354. }
  355. if (PEAR::isError($this->_portName)) {
  356. return $this->_raiseSoapFault($this->_portName);
  357. }
  358. // get endpoint
  359. $this->_endpoint = $this->_wsdl->getEndpoint($this->_portName);
  360. if (PEAR::isError($this->_endpoint)) {
  361. return $this->_raiseSoapFault($this->_endpoint);
  362. }
  363. // get operation data
  364. $opData = $this->_wsdl->getOperationData($this->_portName, $method);
  365. if (PEAR::isError($opData)) {
  366. return $this->_raiseSoapFault($opData);
  367. }
  368. $namespace = $opData['namespace'];
  369. $this->__options['style'] = $opData['style'];
  370. $this->__options['use'] = $opData['input']['use'];
  371. $this->__options['soapaction'] = $opData['soapAction'];
  372. // set input params
  373. if ($this->__options['input'] == 'parse') {
  374. $this->__options['parameters'] = $opData['parameters'];
  375. $nparams = array();
  376. if (isset($opData['input']['parts']) && count($opData['input']['parts']) > 0) {
  377. $i = 0;
  378. reset($params);
  379. foreach ($opData['input']['parts'] as $name => $part) {
  380. $xmlns = '';
  381. $attrs = array();
  382. // is the name actually a complex type?
  383. if (isset($part['element'])) {
  384. $xmlns = $this->_wsdl->namespaces[$part['namespace']];
  385. $part = $this->_wsdl->elements[$part['namespace']][$part['type']];
  386. $name = $part['name'];
  387. }
  388. if (array_key_exists($name,$params) ||
  389. $this->_wsdl->getDataHandler($name,$part['namespace'])) {
  390. $nparams[$name] =& $params[$name];
  391. } else {
  392. # we now force an associative array for parameters if using wsdl
  393. return $this->_raiseSoapFault("The named parameter $name is not in the call parameters.");
  394. }
  395. if (gettype($nparams[$name]) != 'object' ||
  396. !is_a($nparams[$name],'soap_value')) {
  397. // type is a qname likely, split it apart, and get the type namespace from wsdl
  398. $qname =& new QName($part['type']);
  399. if ($qname->ns)
  400. $type_namespace = $this->_wsdl->namespaces[$qname->ns];
  401. else if (isset($part['namespace']))
  402. $type_namespace = $this->_wsdl->namespaces[$part['namespace']];
  403. else
  404. $type_namespace = NULL;
  405. $qname->namespace = $type_namespace;
  406. $type = $qname->name;
  407. $pqname = $name;
  408. if ($xmlns) $pqname = '{'.$xmlns.'}'.$name;
  409. $nparams[$name] =& new SOAP_Value($pqname, $qname->fqn(), $nparams[$name],$attrs);
  410. } else {
  411. // wsdl fixups to the soap value
  412. }
  413. }
  414. }
  415. $params =& $nparams;
  416. unset($nparams);
  417. }
  418. } else {
  419. $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION);
  420. }
  421. // serialize the message
  422. $this->_section5 = TRUE; // assume we encode with section 5
  423. if (isset($this->__options['use']) && $this->__options['use']=='literal') $this->_section5 = FALSE;
  424. if (!isset($this->__options['style']) || $this->__options['style'] == 'rpc') {
  425. $this->__options['style'] = 'rpc';
  426. $this->docparams = true;
  427. $mqname =& new QName($method, $namespace);
  428. $methodValue =& new SOAP_Value($mqname->fqn(), 'Struct', $params);
  429. $soap_msg =& $this->_makeEnvelope($methodValue, $this->headersOut, $this->_encoding,$this->__options);
  430. } else {
  431. if (!$params) {
  432. $mqname =& new QName($method, $namespace);
  433. $mynull = NULL;
  434. $params =& new SOAP_Value($mqname->fqn(), 'Struct', $mynull);
  435. } elseif ($this->__options['input'] == 'parse') {
  436. if (is_array($params)) {
  437. $nparams = array();
  438. $keys = array_keys($params);
  439. foreach ($keys as $k) {
  440. if (gettype($params[$k]) != 'object') {
  441. $nparams[] =& new SOAP_Value($k, false, $params[$k]);
  442. } else {
  443. $nparams[] =& $params[$k];
  444. }
  445. }
  446. $params =& $nparams;
  447. }
  448. if ($this->__options['parameters']) {
  449. $mqname =& new QName($method, $namespace);
  450. $params =& new SOAP_Value($mqname->fqn(), 'Struct', $params);
  451. }
  452. }
  453. $soap_msg =& $this->_makeEnvelope($params, $this->headersOut, $this->_encoding,$this->__options);
  454. }
  455. unset($this->headersOut);
  456. if (PEAR::isError($soap_msg)) {
  457. return $this->_raiseSoapFault($soap_msg);
  458. }
  459. // handle Mime or DIME encoding
  460. // XXX DIME Encoding should move to the transport, do it here for now
  461. // and for ease of getting it done
  462. if (count($this->__attachments)) {
  463. if ((isset($this->__options['attachments']) && $this->__options['attachments'] == 'Mime') || isset($this->__options['Mime'])) {
  464. $soap_msg =& $this->_makeMimeMessage($soap_msg, $this->_encoding);
  465. } else {
  466. // default is dime
  467. $soap_msg =& $this->_makeDIMEMessage($soap_msg, $this->_encoding);
  468. $this->__options['headers']['Content-Type'] = 'application/dime';
  469. }
  470. if (PEAR::isError($soap_msg)) {
  471. return $this->_raiseSoapFault($soap_msg);
  472. }
  473. }
  474. // instantiate client
  475. if (is_array($soap_msg)) {
  476. $soap_data =& $soap_msg['body'];
  477. if (count($soap_msg['headers'])) {
  478. if (isset($this->__options['headers'])) {
  479. $this->__options['headers'] = array_merge($this->__options['headers'],$soap_msg['headers']);
  480. } else {
  481. $this->__options['headers'] = $soap_msg['headers'];
  482. }
  483. }
  484. } else {
  485. $soap_data =& $soap_msg;
  486. }
  487. return $soap_data;
  488. }
  489. function &__parse(&$response, $encoding, &$attachments)
  490. {
  491. // parse the response
  492. $response =& new SOAP_Parser($response, $encoding, $attachments);
  493. if ($response->fault) {
  494. return $this->_raiseSoapFault($response->fault);
  495. }
  496. // return array of parameters
  497. $return =& $response->getResponse();
  498. $headers =& $response->getHeaders();
  499. if ($headers) {
  500. $this->headersIn =& $this->__decodeResponse($headers,false);
  501. }
  502. return $this->__decodeResponse($return);
  503. }
  504. function &__decodeResponse(&$response,$shift=true)
  505. {
  506. if (!$response) return NULL;
  507. // check for valid response
  508. if (PEAR::isError($response)) {
  509. return $this->_raiseSoapFault($response);
  510. } else if (!is_a($response,'soap_value')) {
  511. return $this->_raiseSoapFault("didn't get SOAP_Value object back from client");
  512. }
  513. // decode to native php datatype
  514. $returnArray =& $this->_decode($response);
  515. // fault?
  516. if (PEAR::isError($returnArray)) {
  517. return $this->_raiseSoapFault($returnArray);
  518. }
  519. if (is_object($returnArray) && strcasecmp(get_class($returnArray),'stdClass')==0) {
  520. $returnArray = get_object_vars($returnArray);
  521. }
  522. if (is_array($returnArray)) {
  523. if (isset($returnArray['faultcode']) || isset($returnArray['SOAP-ENV:faultcode'])) {
  524. $faultcode = $faultstring = $faultdetail = $faultactor = '';
  525. foreach ($returnArray as $k => $v) {
  526. if (stristr($k,'faultcode')) $faultcode = $v;
  527. if (stristr($k,'faultstring')) $faultstring = $v;
  528. if (stristr($k,'detail')) $faultdetail = $v;
  529. if (stristr($k,'faultactor')) $faultactor = $v;
  530. }
  531. return $this->_raiseSoapFault($faultstring, $faultdetail, $faultactor, $faultcode);
  532. }
  533. // return array of return values
  534. if ($shift && count($returnArray) == 1) {
  535. return array_shift($returnArray);
  536. }
  537. return $returnArray;
  538. }
  539. return $returnArray;
  540. }
  541. function __get_wire()
  542. {
  543. if ($this->__options['trace'] > 0 && ($this->__last_request || $this->__last_response)) {
  544. return "OUTGOING:\n\n".
  545. $this->__last_request.
  546. "\n\nINCOMING\n\n".
  547. preg_replace("/></",">\r\n<",$this->__last_response);
  548. }
  549. return NULL;
  550. }
  551. }
  552. #if (extension_loaded('overload')) {
  553. # overload('SOAP_Client');
  554. #}
  555. ?>