PageRenderTime 51ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/Object.php

https://bitbucket.org/intel352/riiak
PHP | 878 lines | 311 code | 100 blank | 467 comment | 29 complexity | 4fef6759f86f3befb8b1a7aae1387571 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. namespace riiak;
  3. use \CComponent,
  4. \CJSON,
  5. \Exception,
  6. \Yii;
  7. /**
  8. * The Object holds meta information about a Riak object, plus the
  9. * object's data.
  10. * @package riiak
  11. *
  12. * Magic properties
  13. *
  14. * @property string $contentType
  15. * @property mixed $data
  16. * @property bool $exists
  17. * @property array[string]string $meta
  18. * @property array[string][int]string|int $indexes
  19. * @property array[string]string $autoIndexes
  20. * @property array[int]string $siblings
  21. *
  22. * @property-read int $httpCode Status code of response
  23. * @property-read string $httpStatus Status msg response
  24. * @property-read bool $hasSiblings
  25. * @property-read int $siblingCount
  26. * @property-read array[int]Link $links
  27. * @property-read MapReduce $mapReduce
  28. * @property-read string $vclock
  29. */
  30. class Object extends CComponent {
  31. /**
  32. * Client instance
  33. *
  34. * @var Riiak
  35. */
  36. public $client;
  37. /**
  38. * Bucket
  39. *
  40. * @var Bucket
  41. */
  42. public $bucket;
  43. /**
  44. * Key
  45. *
  46. * @var string
  47. */
  48. public $key;
  49. /**
  50. * Whether or not to treat object as json
  51. *
  52. * @var bool
  53. */
  54. public $jsonize = true;
  55. /**
  56. * @var array
  57. */
  58. public $headers = array();
  59. /**
  60. * Array of Links
  61. *
  62. * @var array[int]Link
  63. */
  64. protected $_links = array();
  65. /**
  66. * Array of vtags
  67. *
  68. * @var array[int]string
  69. */
  70. protected $_siblings = array();
  71. /**
  72. * Whether the object exists
  73. *
  74. * @var bool
  75. */
  76. protected $_exists = false;
  77. /**
  78. * If constructed by newBinary|getBinary, returns string.
  79. * If not a string, will be JSON encoded when stored
  80. *
  81. * @var mixed
  82. */
  83. protected $_data;
  84. /**
  85. * @var array[string]string
  86. */
  87. protected $_meta = array();
  88. /**
  89. * @var array[string][int]string|int
  90. */
  91. protected $_indexes = array();
  92. /**
  93. * @var array[string]string
  94. */
  95. protected $_autoIndexes = array();
  96. /**
  97. * Construct a new Object
  98. *
  99. * @param Riiak $client A Riiak object
  100. * @param Bucket $bucket A Bucket object
  101. * @param string $key Optional - If empty, generated upon store()
  102. */
  103. public function __construct(Riiak $client, Bucket $bucket, $key = null) {
  104. $this->client = $client;
  105. $this->bucket = $bucket;
  106. $this->key = $key;
  107. }
  108. /**
  109. * Returns HTTP status code of last operation
  110. *
  111. * @return int
  112. */
  113. public function getHttpCode() {
  114. return $this->headers['http_code'];
  115. }
  116. /**
  117. * Returns HTTP status msg of last operation
  118. *
  119. * @return string
  120. */
  121. public function getHttpStatus() {
  122. return $this->headers['http_status'];
  123. }
  124. /**
  125. * Returns the object's content type
  126. *
  127. * @return string
  128. */
  129. public function getContentType() {
  130. return $this->headers['content-type'];
  131. }
  132. /**
  133. * Set the object's content type
  134. *
  135. * @param string $contentType The new content type
  136. * @return Object
  137. */
  138. public function setContentType($contentType) {
  139. $this->headers['content-type'] = $contentType;
  140. return $this;
  141. }
  142. /**
  143. * Returns the object's data
  144. *
  145. * @return mixed
  146. */
  147. public function getData() {
  148. return $this->_data;
  149. }
  150. /**
  151. * Set the object's data
  152. *
  153. * @param mixed $data The new data value
  154. * @return Object
  155. */
  156. public function setData($data) {
  157. $this->_data = $data;
  158. return $this;
  159. }
  160. /**
  161. * Get whether the object exists
  162. *
  163. * @return bool
  164. */
  165. public function getExists() {
  166. return $this->_exists;
  167. }
  168. /**
  169. * Set whether the object exists
  170. *
  171. * @param bool $bool
  172. * @return Object
  173. */
  174. public function setExists($bool) {
  175. $this->_exists = (bool) $bool;
  176. return $this;
  177. }
  178. /**
  179. * Add a link to a Object
  180. *
  181. * @param Link|Object $obj Either Object or Link
  182. * @param string $tag optional link tag. Default: bucket name. Ignored for Link
  183. * @return Object
  184. */
  185. public function addLink($obj, $tag = null) {
  186. if ($obj instanceof Link)
  187. $newlink = $obj;
  188. else
  189. $newlink = new Link($obj->bucket->name, $obj->key, $tag);
  190. $this->removeLink($newlink);
  191. $this->_links[] = $newlink;
  192. return $this;
  193. }
  194. /**
  195. * Remove a link to a Object
  196. *
  197. * @param Link|Object $obj Either Object or Link
  198. * @param string $tag optional link tag. Default: bucket name. Ignored for Link
  199. * @return Object
  200. */
  201. public function removeLink($obj, $tag = null) {
  202. if ($obj instanceof Link)
  203. $oldlink = $obj;
  204. else
  205. $oldlink = new Link($obj->bucket->name, $obj->key, $tag);
  206. foreach ($this->_links as $k => $link)
  207. if (!$link->isEqual($oldlink))
  208. unset($this->_links[$k]);
  209. return $this;
  210. }
  211. /**
  212. * Return an array of Link objects
  213. *
  214. * @return array
  215. */
  216. public function getLinks() {
  217. /**
  218. * Set the clients before returning
  219. */
  220. foreach ($this->_links as $link)
  221. $link->client = $this->client;
  222. return $this->_links;
  223. }
  224. /** @section Indexes */
  225. /**
  226. * @param array $array
  227. * @param string $name
  228. * @param string|int $value
  229. * @param 'int'|'bin' $type optional
  230. * @return Object
  231. */
  232. protected function _addIndex(array &$array, $name, $value, $type = null) {
  233. $index = strtolower($name . ($type !== null ? '_' . $type : ''));
  234. if (!isset($array[$index]))
  235. $array[$index] = array();
  236. /**
  237. * Riak de-dupes, but _addIndex is also used for autoIndex management
  238. */
  239. if (!in_array($value, $array[$index]))
  240. $array[$index][] = $value;
  241. return $this;
  242. }
  243. /**
  244. * @param array $array
  245. * @param string $name
  246. * @param array|string|int $value
  247. * @param 'int'|'bin' $type optional
  248. * @return Object
  249. */
  250. protected function _setIndex(array &$array, $name, $value, $type = null) {
  251. $index = strtolower($name . ($type !== null ? '_' . $type : ''));
  252. $array[$index] = $value;
  253. return $this;
  254. }
  255. /**
  256. * @param array $array
  257. * @param string $name
  258. * @param 'int'|'bin' $type optional
  259. * @return bool
  260. */
  261. protected function _hasIndex(array &$array, $name, $type = null) {
  262. $index = strtolower($name . ($type !== null ? '_' . $type : ''));
  263. return isset($array[$index]);
  264. }
  265. /**
  266. * @param array $array
  267. * @param string $name
  268. * @param 'int'|'bin' $type optional
  269. * @return array|string|int
  270. */
  271. protected function _getIndex(array &$array, $name, $type = null) {
  272. $index = strtolower($name . ($type !== null ? '_' . $type : ''));
  273. if (!isset($array[$index]))
  274. return null;
  275. return $array[$index];
  276. }
  277. /**
  278. * @param array $array
  279. * @param string $name
  280. * @param 'int'|'bin' $type optional
  281. * @param string|int $value optional Explicit value to remove
  282. * @return Object
  283. */
  284. protected function _removeIndex(array &$array, $name, $type = null, $value = null) {
  285. $index = strtolower($name . ($type !== null ? '_' . $type : ''));
  286. if (isset($array[$index]))
  287. if ($value !== null) {
  288. if (is_array($array[$index]) && false !== ($position = array_search($value, $array[$index])))
  289. unset($array[$index][$position]);
  290. }else{
  291. unset($array[$index]);
  292. }
  293. return $this;
  294. }
  295. /**
  296. * @param array $array
  297. * @param string $name
  298. * @param 'int'|'bin' $type optional
  299. * @return Object
  300. */
  301. protected function _removeAllIndexes(array &$array, $name = null, $type = null) {
  302. if ($name === null)
  303. $array = array();
  304. else if ($type !== null)
  305. unset($array[strtolower($name . '_' . $type)]);
  306. else {
  307. $name = strtolower($name);
  308. unset($array[$name . '_int']);
  309. unset($array[$name . '_bin']);
  310. }
  311. return $this;
  312. }
  313. /**
  314. * Adds a secondary index to the object
  315. * This will create the index if it does not exist, or will
  316. * append an additional value if the index already exists and
  317. * does not contain the provided value.
  318. *
  319. * @param string $name
  320. * @param 'int'|'bin' $type optional
  321. * @param string|int $explicitValue optional If provided, uses this
  322. * value explicitly. If not provided, this will search the object's
  323. * data for a data field named $name, and use it's value.
  324. * @return $this
  325. */
  326. public function addIndex($name, $type = null, $explicitValue = null) {
  327. if ($explicitValue === null)
  328. return $this->addAutoIndex($name, $type);
  329. return $this->_addIndex($this->_indexes, $name, $explicitValue, $type);
  330. }
  331. /**
  332. * Sets a given index to a specific value or set of values
  333. *
  334. * @param string $name
  335. * @param array|string|int $value
  336. * @param 'int'|'bin' $type optional
  337. * @return $this
  338. */
  339. public function setIndex($name, $value, $type = null) {
  340. return $this->_setIndex($this->_indexes, $name, (array) $value, $type);
  341. }
  342. /**
  343. * @param string $name
  344. * @param 'int'|'bin' $type optional
  345. * @return bool
  346. */
  347. public function hasIndex($name, $type = null) {
  348. return $this->_hasIndex($this->_indexes, $name, $type);
  349. }
  350. /**
  351. * Gets the current values for the identified index
  352. * Note, the NULL value has special meaning - when the object is
  353. * ->store()d, this value will be replaced with the current value
  354. * the value of the field matching $indexName from the object's data
  355. *
  356. * @param string $name
  357. * @param 'int'|'bin' $type optional
  358. *
  359. * @return array
  360. */
  361. public function getIndex($name, $type = null) {
  362. return $this->_getIndex($this->_indexes, $name, $type);
  363. }
  364. /**
  365. * @param array[string][int]string|int $value
  366. * @return Object
  367. */
  368. public function setIndexes(array $value) {
  369. $this->_indexes = $value;
  370. return $this;
  371. }
  372. /**
  373. * @return array[string][int]string|int
  374. */
  375. public function getIndexes() {
  376. return $this->_indexes;
  377. }
  378. /**
  379. * Removes a specific value from a given index
  380. *
  381. * @param string $name
  382. * @param 'int'|'bin' $type optional
  383. * @param string|int $explicitValue optional
  384. * @return $this
  385. */
  386. public function removeIndex($name, $type = null, $explicitValue = null) {
  387. return $this->_removeIndex($this->_indexes, $name, $type, $explicitValue);
  388. }
  389. /**
  390. * Bulk index removal
  391. * If $indexName and $indexType are provided, all values for the
  392. * identified index are removed.
  393. * If just $indexName is provided, all values for all types of
  394. * the identified index are removed
  395. * If neither is provided, all indexes are removed from the object
  396. *
  397. * Note that this function will NOT affect auto indexes
  398. *
  399. * @param string $name optional
  400. * @param 'int'|'bin' $type optional
  401. *
  402. * @return $this
  403. */
  404. public function removeAllIndexes($name = null, $type = null) {
  405. return $this->_removeAllIndexes($this->_indexes, $name, $type);
  406. }
  407. /** @section Auto Indexes */
  408. /**
  409. * Adds an automatic secondary index to the object
  410. * The value of an automatic secondary index is determined at
  411. * time of ->store() by looking for an $fieldName key
  412. * in the object's data.
  413. *
  414. * @param string $name
  415. * @param 'int'|'bin' $type optional
  416. *
  417. * @return $this
  418. */
  419. public function addAutoIndex($name, $type = null) {
  420. return $this->_setIndex($this->_autoIndexes, $name, $name, $type);
  421. }
  422. /**
  423. * Returns whether the object has a given auto index
  424. *
  425. * @param string $name
  426. * @param 'int'|'bin' $type optional
  427. *
  428. * @return boolean
  429. */
  430. public function hasAutoIndex($name, $type = null) {
  431. return $this->_hasIndex($this->_autoIndexes, $name, $type);
  432. }
  433. /**
  434. * @param array[string]string $value
  435. * @return Object
  436. */
  437. public function setAutoIndexes(array $value) {
  438. $this->_autoIndexes = $value;
  439. return $this;
  440. }
  441. /**
  442. * @return array[string]string
  443. */
  444. public function getAutoIndexes() {
  445. return $this->_autoIndexes;
  446. }
  447. /**
  448. * Removes a given auto index from the object
  449. *
  450. * @param string $name
  451. * @param 'int'|'bin' $type optional
  452. *
  453. * @return $this
  454. */
  455. public function removeAutoIndex($name, $type = null) {
  456. return $this->_removeIndex($this->_autoIndexes, $name, $type);
  457. }
  458. /**
  459. * Removes all auto indexes
  460. * If $fieldName is not provided, all auto indexes on the
  461. * object are stripped, otherwise just indexes on the given field
  462. * are stripped.
  463. * If $indexType is not provided, all types of index for the
  464. * given field are stripped, otherwise just a given type is stripped.
  465. *
  466. * @param string $name
  467. * @param 'int'|'bin' $type optional
  468. *
  469. * @return $this
  470. */
  471. public function removeAllAutoIndexes($name = null, $type = null) {
  472. return $this->_removeAllIndexes($this->_autoIndexes, $name, $type);
  473. }
  474. /** @section Meta Data */
  475. /**
  476. * Gets a given metadata value
  477. * Returns null if no metadata value with the given name exists
  478. *
  479. * @param string $metaName
  480. *
  481. * @return string|null
  482. */
  483. public function getMetaValue($metaName) {
  484. $metaName = strtolower($metaName);
  485. if (isset($this->_meta[$metaName]))
  486. return $this->_meta[$metaName];
  487. return null;
  488. }
  489. /**
  490. * Sets a given metadata value, overwriting an existing
  491. * value with the same name if it exists.
  492. * @param string $metaName
  493. * @param string $value
  494. * @return $this
  495. */
  496. public function setMetaValue($metaName, $value) {
  497. $this->_meta[strtolower($metaName)] = $value;
  498. return $this;
  499. }
  500. /**
  501. * Removes a given metadata value
  502. * @param string $metaName
  503. * @return $this
  504. */
  505. public function removeMetaValue($metaName) {
  506. unset($this->_meta[strtolower($metaName)]);
  507. return $this;
  508. }
  509. /**
  510. * Gets all metadata values
  511. * @return array[string]string
  512. */
  513. public function getMeta() {
  514. return $this->_meta;
  515. }
  516. /**
  517. * @param array $value
  518. * @return Object
  519. */
  520. public function setMeta(array $value) {
  521. $this->_meta = $value;
  522. return $this;
  523. }
  524. /**
  525. * Strips all metadata values
  526. * @return $this
  527. */
  528. public function removeMeta() {
  529. $this->_meta = array();
  530. return $this;
  531. }
  532. /**
  533. * Store the object in Riak. Upon completion, object could contain new
  534. * metadata, and possibly new data if Riak contains a newer version of
  535. * the object according to the object's vector clock.
  536. *
  537. * @param int $w optional W-Value: X partitions must respond before returning
  538. * @param int $dw optional DW-Value: X partitions must confirm write before returning
  539. * @return Object
  540. */
  541. public function store($w = null, $dw = null) {
  542. $params = array('returnbody' => 'true', 'w' => $this->bucket->getW($w), 'dw' => $this->bucket->getDW($dw));
  543. $response = $this->client->transport->storeObject($this, $params);
  544. return self::populateResponse($this, $response);
  545. }
  546. /**
  547. * Reload the object from Riak. When this operation completes, the object
  548. * could contain new metadata and a new value, if the object was updated
  549. * in Riak since it was last retrieved.
  550. *
  551. * @param int $r optional R-Value: X partitions must respond before returning
  552. * @return Object
  553. */
  554. public function reload($r = null) {
  555. /**
  556. * Do the request
  557. */
  558. $params = array('r' => $this->bucket->getR($r));
  559. Yii::trace('Reloading object "' . $this->key . '" from bucket "' . $this->bucket->name . '"', 'ext.riiak.Object');
  560. $response = $this->client->transport->fetchObject($this->bucket, $this->key, $params);
  561. return self::populateResponse($this, $response);
  562. }
  563. /**
  564. * @param Riiak $client
  565. * @param array $objects
  566. * @param int $r optional
  567. * @return array[string]Object
  568. */
  569. public static function reloadMulti(Riiak $client, array $objects, $r = null) {
  570. Yii::trace('Reloading multiple objects', 'ext.riiak.Object');
  571. $objects = array_combine(array_map(array('self', 'buildReloadUrl'), $objects, array_fill(0, count($objects), $r)), $objects);
  572. /**
  573. * Get (fetch) multiple objects
  574. */
  575. $responses = $client->transport->multiGet(array_keys($objects));
  576. array_walk($objects, function($object, $url)use(&$responses) {
  577. Object::populateResponse($object, $responses[$url]);
  578. });
  579. return $objects;
  580. }
  581. /**
  582. * @param Object $object
  583. * @param int $r optional
  584. * @return string
  585. */
  586. protected static function buildReloadUrl(Object $object, $r = null) {
  587. $params = array('r' => $object->bucket->getR($r));
  588. return $object->client->transport->buildBucketKeyPath($object->bucket, $object->key, null, $params);
  589. }
  590. /**
  591. * @static
  592. * @param Object $object
  593. * @param array $response
  594. * @return Object
  595. */
  596. public static function populateResponse(Object $object, $response) {
  597. $object->client->transport->populate($object, $response);
  598. /**
  599. * Parse the index and metadata headers
  600. */
  601. foreach ($object->headers as $key => $val) {
  602. if (preg_match('~^x-riak-([^-]+)-(.+)$~', $key, $matches)) {
  603. switch ($matches[1]) {
  604. case 'index':
  605. $index = substr($matches[2], 0, strrpos($matches[2], '_'));
  606. $type = substr($matches[2], strlen($index) + 1);
  607. $object->setIndex($index, array_map('urldecode', explode(', ', $val)), $type);
  608. break;
  609. case 'meta':
  610. $object->setMetaValue($matches[2], $val);
  611. break;
  612. }
  613. }
  614. }
  615. /**
  616. * If there are siblings, load the data for the first one by default
  617. */
  618. if ($object->getHasSiblings()) {
  619. $sibling = $object->getSibling(0);
  620. $object->data = $sibling->data;
  621. }
  622. /**
  623. * Look for auto indexes and deindex explicit values if appropriate
  624. */
  625. if (isset($object->meta['client-autoindex'])) {
  626. /**
  627. * dereference the autoindexes
  628. */
  629. $object->autoIndexes = CJSON::decode($object->meta['client-autoindex']);
  630. $collisions = isset($object->meta['client-autoindexcollision']) ? CJSON::decode($object->meta['client-autoindexcollision']) : array();
  631. if (is_array($object->autoIndexes) && is_array($object->data))
  632. foreach ($object->autoIndexes as $index => $fieldName) {
  633. $value = null;
  634. if (isset($object->data[$fieldName])) {
  635. $value = $object->data[$fieldName];
  636. /**
  637. * Only strip this value if not explicit index
  638. * @todo review logic
  639. */
  640. if (!(isset($collisions[$index]) && $collisions[$index] === $value))
  641. if ($value !== null)
  642. $object->removeIndex($index, null, $value);
  643. }
  644. }
  645. }
  646. return $object;
  647. }
  648. /**
  649. * Delete this object from Riak
  650. *
  651. * @param int $dw optional DW-Value: X partitions must delete object before returning
  652. * @return Object
  653. */
  654. public function delete($dw = null) {
  655. /**
  656. * Use defaults if not specified
  657. */
  658. $params = array('dw'=>$this->bucket->getDW($dw));
  659. $response = $this->client->transport->deleteObject($this, $params);
  660. $this->client->transport->populate($this, $response);
  661. return $this;
  662. }
  663. /**
  664. * Reset this object
  665. *
  666. * @return Object
  667. */
  668. public function clear() {
  669. $this->headers = array();
  670. $this->_links = array();
  671. $this->_data = null;
  672. $this->_exists = false;
  673. $this->_siblings = array();
  674. $this->_indexes = array();
  675. $this->_autoIndexes = array();
  676. $this->_meta = array();
  677. return $this;
  678. }
  679. /**
  680. * Get the vclock of this object
  681. *
  682. * @return string|null
  683. */
  684. public function getVclock() {
  685. if (array_key_exists('x-riak-vclock', $this->headers))
  686. return $this->headers['x-riak-vclock'];
  687. return null;
  688. }
  689. /**
  690. * Populate object links
  691. *
  692. * @param string $linkHeaders
  693. * @return Object
  694. */
  695. public function populateLinks($linkHeaders) {
  696. $linkHeaders = explode(',', trim($linkHeaders));
  697. foreach ($linkHeaders as $linkHeader)
  698. if (preg_match('/\<\/([^\/]+)\/([^\/]+)\/([^\/]+)\>; ?riaktag="([^"]+)"/', trim($linkHeader), $matches))
  699. $this->_links[] = new Link(urldecode($matches[2]), urldecode($matches[3]), urldecode($matches[4]));
  700. return $this;
  701. }
  702. /**
  703. * Return true if this object has siblings
  704. *
  705. * @return bool
  706. */
  707. public function getHasSiblings() {
  708. return ($this->getSiblingCount() > 0);
  709. }
  710. /**
  711. * Get the number of siblings that this object contains
  712. *
  713. * @return int
  714. */
  715. public function getSiblingCount() {
  716. return count($this->_siblings);
  717. }
  718. /**
  719. * Retrieve a sibling by sibling number
  720. *
  721. * @param int $i Sibling number
  722. * @param int $r R-Value: X partitions must respond before returning
  723. * @return Object
  724. */
  725. public function getSibling($i, $r = null) {
  726. /**
  727. * Use defaults if not specified
  728. */
  729. $r = $this->bucket->getR($r);
  730. /**
  731. * Run the request
  732. */
  733. $vtag = $this->_siblings[$i];
  734. $params = array('r' => $r, 'vtag' => $vtag);
  735. Yii::trace('Fetching sibling "' . $i . '" of object "' . $this->key . '" from bucket "' . $this->bucket->name . '"', 'ext.riiak.Object');
  736. $response = $this->client->transport->fetchObject($this->bucket, $this->key, $params);
  737. /**
  738. * Respond with a new object
  739. */
  740. $obj = new Object($this->client, $this->bucket, $this->key);
  741. $obj->jsonize = $this->jsonize;
  742. return self::populateResponse($obj, $response);
  743. }
  744. /**
  745. * Retrieve an array of siblings
  746. *
  747. * @todo It's possible to fetch multiple siblings in 1 request. That should be implemented here.
  748. * @link http://wiki.basho.com/HTTP-Fetch-Object.html#Get-all-siblings-in-one-request
  749. *
  750. * @param int $r R-Value: X partitions must respond before returning
  751. * @return array[int]Object
  752. */
  753. public function getSiblings($r = null) {
  754. $a = array();
  755. for ($i = 0; $i < $this->getSiblingCount(); $i++)
  756. $a[] = $this->getSibling($i, $r);
  757. return $a;
  758. }
  759. /**
  760. * Specify sibling vtags
  761. *
  762. * @param array[int]string $siblings
  763. * @return Object
  764. */
  765. public function setSiblings(array $siblings) {
  766. $this->_siblings = $siblings;
  767. return $this;
  768. }
  769. /**
  770. * Returns a MapReduce instance
  771. *
  772. * @param bool $reset Whether to create a new MapReduce instance
  773. * @return MapReduce
  774. */
  775. public function getMapReduce($reset = false) {
  776. return $this->client->getMapReduce($reset);
  777. }
  778. }