PageRenderTime 24ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/storage/arangodb-php/lib/ArangoDBClient/Batch.php

https://bitbucket.org/hfab/webservice
PHP | 556 lines | 224 code | 98 blank | 234 comment | 21 complexity | de50ff63f46e0cb44f78ff3a0b617517 MD5 | raw file
Possible License(s): BSD-2-Clause, Apache-2.0, MIT, GPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * ArangoDB PHP client: batch
  4. *
  5. * @package ArangoDBClient
  6. * @author Frank Mayer
  7. * @since 1.1
  8. *
  9. */
  10. namespace ArangoDBClient;
  11. /**
  12. * Provides batching functionality
  13. *
  14. * <br>
  15. *
  16. * @package ArangoDBClient
  17. * @since 1.1
  18. */
  19. class Batch
  20. {
  21. /**
  22. * Batch Response Object
  23. *
  24. * @var HttpResponse $_batchResponse
  25. */
  26. public $_batchResponse;
  27. /**
  28. * Flag that signals if this batch was processed or not. Processed => true ,or not processed => false
  29. *
  30. * @var boolean $_processed
  31. */
  32. private $_processed = false;
  33. /**
  34. * The array of BatchPart objects
  35. *
  36. * @var array $_batchParts
  37. */
  38. private $_batchParts = [];
  39. /**
  40. * The next batch part id
  41. *
  42. * @var integer|string $_nextBatchPartId
  43. */
  44. private $_nextBatchPartId;
  45. /**
  46. * An array of BatchPartCursor options
  47. *
  48. * @var array $_batchParts
  49. */
  50. private $_batchPartCursorOptions = [];
  51. /**
  52. * The connection object
  53. *
  54. * @var Connection $_connection
  55. */
  56. private $_connection;
  57. /**
  58. * The sanitize default value
  59. *
  60. * @var bool $_sanitize
  61. */
  62. private $_sanitize = false;
  63. /**
  64. * The Batch NextId
  65. *
  66. * @var integer|string $_nextId
  67. */
  68. private $_nextId = 0;
  69. /**
  70. * Constructor for Batch instance. Batch instance by default starts capturing request after initiated.
  71. * To disable this, pass startCapture=>false inside the options array parameter
  72. *
  73. * @param Connection $connection that this batch class will monitor for requests in order to batch them. Connection parameter is mandatory.
  74. * @param array $options An array of options for Batch construction. See below for options:
  75. *
  76. * <p>Options are :
  77. * <li>'_sanitize' - True to remove _id and _rev attributes from result documents returned from this batch. Defaults to false.</li>
  78. * <li>'startCapture' - Start batch capturing immediately after batch instantiation. Defaults to true.</li>
  79. * <li>'batchSize' - Defines a fixed array size for holding the batch parts. The id's of the batch parts can only be integers.
  80. * When this option is defined, the batch mechanism will use an SplFixedArray instead of the normal PHP arrays.
  81. * In most cases, this will result in increased performance of about 5% to 15%, depending on batch size and data.</li>
  82. * </p>
  83. */
  84. public function __construct(Connection $connection, array $options = [])
  85. {
  86. $startCapture = true;
  87. $sanitize = false;
  88. $batchSize = 0;
  89. $options = array_merge($options, $this->getCursorOptions());
  90. extract($options, EXTR_IF_EXISTS);
  91. $this->_sanitize = $sanitize;
  92. $this->batchSize = $batchSize;
  93. if ($this->batchSize > 0) {
  94. $this->_batchParts = new \SplFixedArray($this->batchSize);
  95. }
  96. $this->setConnection($connection);
  97. // set default cursor options. Sanitize is currently the only local one.
  98. $this->_batchPartCursorOptions = [Cursor::ENTRY_SANITIZE => (bool) $this->_sanitize];
  99. if ($startCapture === true) {
  100. $this->startCapture();
  101. }
  102. }
  103. /**
  104. * Sets the connection for he current batch. (mostly internal function)
  105. *
  106. * @param Connection $connection
  107. *
  108. * @return Batch
  109. */
  110. public function setConnection($connection)
  111. {
  112. $this->_connection = $connection;
  113. return $this;
  114. }
  115. /**
  116. * Start capturing requests. To stop capturing, use stopCapture()
  117. *
  118. * see ArangoDBClient\Batch::stopCapture()
  119. *
  120. * @return Batch
  121. *
  122. */
  123. public function startCapture()
  124. {
  125. $this->activate();
  126. return $this;
  127. }
  128. /**
  129. * Stop capturing requests. If the batch has not been processed yet, more requests can be appended by calling startCapture() again.
  130. *
  131. * see Batch::startCapture()
  132. *
  133. * @throws ClientException
  134. * @return Batch
  135. */
  136. public function stopCapture()
  137. {
  138. // check if this batch is the active one... and capturing. Ignore, if we're not capturing...
  139. if ($this->isActive() && $this->isCapturing()) {
  140. $this->setCapture(false);
  141. return $this;
  142. } else {
  143. throw new ClientException('Cannot stop capturing with this batch. Batch is not active...');
  144. }
  145. }
  146. /**
  147. * Returns true, if this batch is active in its associated connection.
  148. *
  149. * @return bool
  150. */
  151. public function isActive()
  152. {
  153. $activeBatch = $this->getActive($this->_connection);
  154. return $activeBatch === $this;
  155. }
  156. /**
  157. * Returns true, if this batch is capturing requests.
  158. *
  159. * @return bool
  160. */
  161. public function isCapturing()
  162. {
  163. return $this->getConnectionCaptureMode($this->_connection);
  164. }
  165. /**
  166. * Activates the batch. This sets the batch active in its associated connection and also starts capturing.
  167. *
  168. * @return Batch $this
  169. */
  170. public function activate()
  171. {
  172. $this->setActive();
  173. $this->setCapture(true);
  174. return $this;
  175. }
  176. /**
  177. * Sets the batch active in its associated connection.
  178. *
  179. * @return Batch $this
  180. */
  181. public function setActive()
  182. {
  183. $this->_connection->setActiveBatch($this);
  184. return $this;
  185. }
  186. /**
  187. * Sets the batch's associated connection into capture mode.
  188. *
  189. * @param boolean $state
  190. *
  191. * @return Batch $this
  192. */
  193. public function setCapture($state)
  194. {
  195. $this->_connection->setCaptureBatch($state);
  196. return $this;
  197. }
  198. /**
  199. * Gets active batch in given connection.
  200. *
  201. * @param Connection $connection
  202. *
  203. * @return $this
  204. */
  205. public function getActive($connection)
  206. {
  207. $connection->getActiveBatch();
  208. return $this;
  209. }
  210. /**
  211. * Returns true, if given connection is in batch-capture mode.
  212. *
  213. * @param Connection $connection
  214. *
  215. * @return bool
  216. */
  217. public function getConnectionCaptureMode($connection)
  218. {
  219. return $connection->isInBatchCaptureMode();
  220. }
  221. /**
  222. * Sets connection into Batch-Request mode. This is necessary to distinguish between normal and the batch request.
  223. *
  224. * @param boolean $state
  225. *
  226. * @return $this
  227. */
  228. private function setBatchRequest($state)
  229. {
  230. $this->_connection->setBatchRequest($state);
  231. $this->_processed = true;
  232. return $this;
  233. }
  234. /**
  235. * Sets the id of the next batch-part. The id can later be used to retrieve the batch-part.
  236. *
  237. * @param mixed $batchPartId
  238. *
  239. * @return Batch
  240. */
  241. public function nextBatchPartId($batchPartId)
  242. {
  243. $this->_nextBatchPartId = $batchPartId;
  244. return $this;
  245. }
  246. /**
  247. * Set client side cursor options (for example: sanitize) for the next batch part.
  248. *
  249. * @param mixed $batchPartCursorOptions
  250. *
  251. * @return Batch
  252. */
  253. public function nextBatchPartCursorOptions($batchPartCursorOptions)
  254. {
  255. $this->_batchPartCursorOptions = $batchPartCursorOptions;
  256. return $this;
  257. }
  258. /**
  259. * Append the request to the batch-part
  260. *
  261. * @param mixed $method - The method of the request (GET, POST...)
  262. * @param mixed $request - The request that will get appended to the batch
  263. *
  264. * @return HttpResponse
  265. *
  266. * @throws \ArangoDBClient\ClientException
  267. */
  268. public function append($method, $request)
  269. {
  270. preg_match('%/_api/simple/(?P<simple>\w*)|/_api/(?P<direct>\w*)%ix', $request, $regs);
  271. if (!isset($regs['direct'])) {
  272. $regs['direct'] = '';
  273. }
  274. $type = $regs['direct'] !== '' ? $regs['direct'] : $regs['simple'];
  275. if ($method === 'GET' && $type === $regs['direct']) {
  276. $type = 'get' . $type;
  277. }
  278. if (null === $this->_nextBatchPartId) {
  279. if (is_a($this->_batchParts, 'SplFixedArray')) {
  280. $nextNumeric = $this->_nextId;
  281. $this->_nextId++;
  282. } else {
  283. $nextNumeric = count($this->_batchParts);
  284. }
  285. $batchPartId = $nextNumeric;
  286. } else {
  287. $batchPartId = $this->_nextBatchPartId;
  288. $this->_nextBatchPartId = null;
  289. }
  290. $eol = HttpHelper::EOL;
  291. $result = 'HTTP/1.1 202 Accepted' . $eol;
  292. $result .= 'location: /_db/_system/_api/document/0/0' . $eol;
  293. $result .= 'content-type: application/json; charset=utf-8' . $eol;
  294. $result .= 'etag: "0"' . $eol;
  295. $result .= 'connection: Close' . $eol . $eol;
  296. $result .= '{"error":false,"_id":"0/0","id":"0","_rev":0,"hasMore":1, "result":[{}], "documents":[{}]}' . $eol . $eol;
  297. $response = new HttpResponse($result);
  298. $batchPart = new BatchPart($this, $batchPartId, $type, $request, $response, ['cursorOptions' => $this->_batchPartCursorOptions]);
  299. $this->_batchParts[$batchPartId] = $batchPart;
  300. $response->setBatchPart($batchPart);
  301. return $response;
  302. }
  303. /**
  304. * Split batch request and use ContentId as array key
  305. *
  306. * @param mixed $pattern
  307. * @param mixed $string
  308. *
  309. * @return array $array - Array of batch-parts
  310. *
  311. * @throws \ArangoDBClient\ClientException
  312. */
  313. public function splitWithContentIdKey($pattern, $string)
  314. {
  315. $array = [];
  316. $exploded = explode($pattern, $string);
  317. foreach ($exploded as $key => $value) {
  318. $response = new HttpResponse($value);
  319. $contentId = $response->getHeader('Content-Id');
  320. if (null !== $contentId) {
  321. $array[$contentId] = $value;
  322. } else {
  323. $array[$key] = $value;
  324. }
  325. }
  326. return $array;
  327. }
  328. /**
  329. * Processes this batch. This sends the captured requests to the server as one batch.
  330. *
  331. * @return HttpResponse|Batch - Batch if processing of the batch was successful or the HttpResponse object in case of a failure. A successful process just means that tha parts were processed. Each part has it's own response though and should be checked on its own.
  332. *
  333. * @throws ClientException
  334. * @throws \ArangoDBClient\Exception
  335. */
  336. public function process()
  337. {
  338. if ($this->isCapturing()) {
  339. $this->stopCapture();
  340. }
  341. $this->setBatchRequest(true);
  342. $data = '';
  343. $batchParts = $this->getBatchParts();
  344. if (count($batchParts) === 0) {
  345. throw new ClientException('Can\'t process empty batch.');
  346. }
  347. /** @var $partValue BatchPart */
  348. foreach ($batchParts as $partValue) {
  349. if (null !== $partValue) {
  350. $data .= '--' . HttpHelper::MIME_BOUNDARY . HttpHelper::EOL;
  351. $data .= 'Content-Type: application/x-arango-batchpart' . HttpHelper::EOL;
  352. if (null !== $partValue->getId()) {
  353. $data .= 'Content-Id: ' . (string) $partValue->getId() . HttpHelper::EOL . HttpHelper::EOL;
  354. } else {
  355. $data .= HttpHelper::EOL;
  356. }
  357. $data .= (string) $partValue->getRequest() . HttpHelper::EOL;
  358. }
  359. }
  360. $data .= '--' . HttpHelper::MIME_BOUNDARY . '--' . HttpHelper::EOL . HttpHelper::EOL;
  361. $params = [];
  362. $url = UrlHelper::appendParamsUrl(Urls::URL_BATCH, $params);
  363. $this->_batchResponse = $this->_connection->post($url, $data);
  364. if ($this->_batchResponse->getHttpCode() !== 200) {
  365. return $this->_batchResponse;
  366. }
  367. $body = $this->_batchResponse->getBody();
  368. $body = trim($body, '--' . HttpHelper::MIME_BOUNDARY . '--');
  369. $batchParts = $this->splitWithContentIdKey('--' . HttpHelper::MIME_BOUNDARY . HttpHelper::EOL, $body);
  370. foreach ($batchParts as $partKey => $partValue) {
  371. $response = new HttpResponse($partValue);
  372. $body = $response->getBody();
  373. $response = new HttpResponse($body);
  374. $batchPartResponses[$partKey] = $response;
  375. $this->getPart($partKey)->setResponse($batchPartResponses[$partKey]);
  376. }
  377. return $this;
  378. }
  379. /**
  380. * Get the total count of the batch parts
  381. *
  382. * @return integer $count
  383. */
  384. public function countParts()
  385. {
  386. return count($this->_batchParts);
  387. }
  388. /**
  389. * Get the batch part identified by the array key (0...n) or its id (if it was set with nextBatchPartId($id) )
  390. *
  391. * @param mixed $partId the batch part id. Either it's numeric key or a given name.
  392. *
  393. * @return mixed $batchPart
  394. *
  395. * @throws ClientException
  396. */
  397. public function getPart($partId)
  398. {
  399. if (!isset($this->_batchParts[$partId])) {
  400. throw new ClientException('Request batch part does not exist.');
  401. }
  402. return $this->_batchParts[$partId];
  403. }
  404. /**
  405. * Get the batch part identified by the array key (0...n) or its id (if it was set with nextBatchPartId($id) )
  406. *
  407. * @param mixed $partId the batch part id. Either it's numeric key or a given name.
  408. *
  409. * @return mixed $partId
  410. *
  411. * @throws \ArangoDBClient\ClientException
  412. */
  413. public function getPartResponse($partId)
  414. {
  415. return $this->getPart($partId)->getResponse();
  416. }
  417. /**
  418. * Get the batch part identified by the array key (0...n) or its id (if it was set with nextBatchPartId($id) )
  419. *
  420. * @param mixed $partId the batch part id. Either it's numeric key or a given name.
  421. *
  422. * @return mixed $partId
  423. *
  424. * @throws \ArangoDBClient\ClientException
  425. */
  426. public function getProcessedPartResponse($partId)
  427. {
  428. return $this->getPart($partId)->getProcessedResponse();
  429. }
  430. /**
  431. * Returns the array of batch-parts
  432. *
  433. * @return array $_batchParts
  434. */
  435. public function getBatchParts()
  436. {
  437. return $this->_batchParts;
  438. }
  439. /**
  440. * Return an array of cursor options
  441. *
  442. * @return array - array of options
  443. */
  444. private function getCursorOptions()
  445. {
  446. return $this->_batchPartCursorOptions;
  447. }
  448. /**
  449. * Return this batch's connection
  450. *
  451. * @return Connection
  452. */
  453. public function getConnection()
  454. {
  455. return $this->_connection;
  456. }
  457. }
  458. class_alias(Batch::class, '\triagens\ArangoDb\Batch');