/src/sixdg/Sixdg/DynamicsCRMConnector/Components/Soap/SoapRequester.php

https://bitbucket.org/6dg/dynamics-crm-php-connector · PHP · 271 lines · 143 code · 35 blank · 93 comment · 6 complexity · 415b21b702331fc071ad88c3709d4318 MD5 · raw file

  1. <?php
  2. namespace Sixdg\DynamicsCRMConnector\Components\Soap;
  3. use Sixdg\DynamicsCRMConnector\Responses\DynamicsCRMResponse;
  4. /**
  5. * Class SoapRequester
  6. *
  7. * @package Sixdg\DynamicsCRMConnector\Components\Soap
  8. */
  9. class SoapRequester
  10. {
  11. public static $soapEnvelope = 'http://www.w3.org/2003/05/soap-envelope';
  12. public static $soapFaults = [
  13. 'http://www.w3.org/2005/08/addressing/soap/fault',
  14. 'http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault',
  15. 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/ExecuteOrganizationServiceFaultFault',
  16. 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/CreateOrganizationServiceFaultFault',
  17. 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/RetrieveOrganizationServiceFaultFault',
  18. 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/UpdateOrganizationServiceFaultFault',
  19. 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/DeleteOrganizationServiceFaultFault',
  20. 'http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/RetrieveMultipleOrganizationServiceFaultFault',
  21. ];
  22. protected $timeout = 10;
  23. protected $responder = null;
  24. /**
  25. * @param DynamicsCRMResponse $responder
  26. */
  27. public function setResponder(\DOMDocument $responder)
  28. {
  29. $this->responder = $responder;
  30. }
  31. /**
  32. * @param string $uri
  33. * @param string $request
  34. *
  35. * @return mixed
  36. * @throws \Exception
  37. */
  38. public function sendRequest($uri, $request)
  39. {
  40. $headers = $this->getHeaders($uri, $request);
  41. $ch = $this->getCurlHandle($uri, $headers, $request);
  42. $responseXml = curl_exec($ch);
  43. try {
  44. $this->hasError($ch, $responseXml);
  45. } catch (\Exception $ex) {
  46. throw $ex;
  47. }
  48. curl_close($ch);
  49. if ($this->responder) {
  50. $this->responder->loadXML($responseXml);
  51. return $this->responder;
  52. }
  53. return $responseXml;
  54. }
  55. /**
  56. * @param string $uri
  57. * @param string $request
  58. *
  59. * @return array
  60. */
  61. private function getHeaders($uri, $request)
  62. {
  63. $urlDetails = parse_url($uri);
  64. return [
  65. "POST " . $urlDetails['path'] . " HTTP/1.1",
  66. "Host: " . $urlDetails['host'],
  67. 'Connection: Keep-Alive',
  68. 'Content-type: application/soap+xml; charset=UTF-8',
  69. 'Content-length: ' . strlen($request)
  70. ];
  71. }
  72. /**
  73. * @param string $uri
  74. * @param array $headers
  75. * @param string $request
  76. *
  77. * @return resource
  78. */
  79. private function getCurlHandle($uri, $headers, $request)
  80. {
  81. $ch = curl_init();
  82. curl_setopt($ch, CURLOPT_URL, $uri);
  83. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  84. curl_setopt($ch, CURLOPT_TIMEOUT, 60);
  85. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  86. curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  87. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  88. curl_setopt($ch, CURLOPT_POST, true);
  89. curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
  90. curl_setopt($ch, CURLOPT_HEADER, false);
  91. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
  92. curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
  93. return $ch;
  94. }
  95. /**
  96. * @param resource $ch
  97. * @param string $responseXML
  98. *
  99. * @throws \Exception
  100. */
  101. private function hasError($ch, $responseXML)
  102. {
  103. $this->testCurlResponse($ch, $responseXML);
  104. if ($responseXML) {
  105. $responseDOM = new \DOMDocument();
  106. $responseDOM->loadXML($responseXML);
  107. $this->testIsValidSoapResponse($responseDOM, $responseXML);
  108. $this->testIsValidSoapHeader($responseDOM, $responseXML);
  109. $this->testActionIsNotError($responseDOM, $responseXML);
  110. return;
  111. }
  112. throw new \Exception('No response found');
  113. }
  114. /**
  115. * @param resource $ch
  116. * @param string $responseXML
  117. *
  118. * @throws \Exception
  119. */
  120. private function testCurlResponse($ch, $responseXML)
  121. {
  122. if ($responseXML === false) {
  123. throw new \Exception('cURL Error: ' . curl_error($ch));
  124. }
  125. }
  126. /**
  127. * @param \DOMDocument $responseDOM
  128. * @param string $responseXML
  129. *
  130. * @throws \Exception
  131. */
  132. private function testIsValidSoapResponse(\DOMDocument $responseDOM, $responseXML)
  133. {
  134. if ($responseDOM->getElementsByTagNameNS(SoapRequester::$soapEnvelope, 'Envelope')->length < 1) {
  135. throw new \Exception('Invalid SOAP Response: HTTP Response ' . $responseXML . PHP_EOL . $responseXML . PHP_EOL);
  136. }
  137. }
  138. /**
  139. * @param string $responseDOM
  140. *
  141. * @return mixed
  142. */
  143. private function getEnvelope($responseDOM)
  144. {
  145. return $responseDOM->getElementsByTagNameNS(SoapRequester::$soapEnvelope, 'Envelope')->item(0);
  146. }
  147. /**
  148. * @param \DOMElement $envelope
  149. *
  150. * @return mixed
  151. */
  152. private function getHeader($envelope)
  153. {
  154. return $envelope->getElementsByTagNameNS(SoapRequester::$soapEnvelope, 'Header')->item(0);
  155. }
  156. /**
  157. * @param \DOMElement $header
  158. *
  159. * @return mixed
  160. */
  161. private function getAction($header)
  162. {
  163. return $header->getElementsByTagNameNS('http://www.w3.org/2005/08/addressing', 'Action')->item(0);
  164. }
  165. /**
  166. * @param \DOMDocument $responseDOM
  167. * @param string $responseXML
  168. *
  169. * @throws \Exception
  170. */
  171. private function testIsValidSoapHeader(\DOMDocument $responseDOM, $responseXML)
  172. {
  173. $envelope = $this->getEnvelope($responseDOM);
  174. $header = $this->getHeader($envelope);
  175. if (!$header) {
  176. throw new \Exception('Invalid SOAP Response: No SOAP Header!' . PHP_EOL . $responseXML . PHP_EOL);
  177. }
  178. }
  179. /**
  180. * @param \DOMDocument $responseDOM
  181. *
  182. * @throws \Exception
  183. */
  184. private function testActionIsNotError(\DOMDocument $responseDOM)
  185. {
  186. $envelope = $this->getEnvelope($responseDOM);
  187. $header = $this->getHeader($envelope);
  188. $actionString = $this->getAction($header)->textContent;
  189. if (in_array($actionString, self::$soapFaults)) {
  190. throw $this->getSoapFault($responseDOM);
  191. }
  192. }
  193. /**
  194. * @param \DOMDocument $responseDOM
  195. *
  196. * @return \Exception
  197. */
  198. private function getSoapFault(\DOMDocument $responseDOM)
  199. {
  200. return new \SoapFault($this->getSoapFaultCode($responseDOM), $this->getSoapFaultMessage($responseDOM));
  201. }
  202. /**
  203. * @param \DomDocument $responseDOM
  204. *
  205. * @return string
  206. */
  207. private function getSoapFaultCode(\DomDocument $responseDOM)
  208. {
  209. /**
  210. * TODO Change to use xpath
  211. */
  212. $hierarchy = ['Envelope', 'Body', 'Fault', 'Code', 'Value'];
  213. $item = $responseDOM;
  214. foreach ($hierarchy as $currentLevel) {
  215. $item = $item->getElementsByTagNameNS('http://www.w3.org/2003/05/soap-envelope', $currentLevel)->item(0);
  216. }
  217. return $item->nodeValue;
  218. }
  219. /**
  220. * @param \DOMDocument $responseDOM
  221. *
  222. * @return string
  223. */
  224. private function getSoapFaultMessage(\DOMDocument $responseDOM)
  225. {
  226. /**
  227. * TODO Change to use xpath
  228. */
  229. $hierarchy = ['Envelope', 'Body', 'Fault', 'Reason', 'Text'];
  230. $item = $responseDOM;
  231. foreach ($hierarchy as $currentLevel) {
  232. $item = $item->getElementsByTagNameNS('http://www.w3.org/2003/05/soap-envelope', $currentLevel)->item(0);
  233. }
  234. return $item->nodeValue;
  235. }
  236. }