/library/Phly/Couch.php

https://github.com/abtris/phplogger-couchdb · PHP · 611 lines · 349 code · 54 blank · 208 comment · 48 complexity · 1a5cadad8fe2fcfd0e30824926690565 MD5 · raw file

  1. <?php
  2. require_once 'Zend/Json.php';
  3. class Phly_Couch
  4. {
  5. /**
  6. * @var Zend_Http_Client HTTP client used for accessing server
  7. */
  8. protected $_client;
  9. /**
  10. * @var string Database on which operations are performed
  11. */
  12. protected $_db;
  13. /**
  14. * @var Zend_Http_Client Default HTTP client to use for CouchDB access
  15. */
  16. protected static $_defaultClient;
  17. /**
  18. * @var string Database host; defaults to 127.0.0.1
  19. */
  20. protected $_host = '127.0.0.1';
  21. /**
  22. * @var int Database host port; defaults to 5984
  23. */
  24. protected $_port = 5984;
  25. /**
  26. * Constructor
  27. *
  28. * @param null|string|array|Zend_Config $info Database name, or array/config of options
  29. * @return void
  30. */
  31. public function __construct($info = null)
  32. {
  33. if (null !== $info) {
  34. if (is_array($info)) {
  35. $this->setOptions($info);
  36. } elseif ($info instanceof Zend_Config) {
  37. $this->setConfig($info);
  38. } elseif (is_string($info)) {
  39. $this->setDb($info);
  40. }
  41. }
  42. }
  43. /**
  44. * Set connection options
  45. *
  46. * @param array $options
  47. * @return Phly_Couch
  48. */
  49. public function setOptions(array $options)
  50. {
  51. foreach ($options as $key => $value) {
  52. $method = 'set' . ucfirst($key);
  53. if (method_exists($this, $method)) {
  54. $this->$method($value);
  55. }
  56. }
  57. return $this;
  58. }
  59. /**
  60. * Set connection options from Zend_Config object
  61. *
  62. * @param Zend_Config $config
  63. * @return Phly_Couch
  64. */
  65. public function setConfig(Zend_Config $config)
  66. {
  67. return $this->setOptions($config->toArray());
  68. }
  69. /**
  70. * Set Database on which to perform operations
  71. *
  72. * @param string $db
  73. * @return Phly_Couch
  74. * @throws Phly_Couch_Exception for invalid DB name
  75. */
  76. public function setDb($db)
  77. {
  78. if (!preg_match('/^[a-z][a-z0-9_$()+-\/]+$/', $db)) {
  79. require_once 'Phly/Couch/Exception.php';
  80. throw new Phly_Couch_Exception(sprintf('Invalid database specified: "%s"', htmlentities($db)));
  81. }
  82. $this->_db = $db;
  83. return $this;
  84. }
  85. /**
  86. * Retrieve current database name
  87. *
  88. * @return string|null
  89. */
  90. public function getDb()
  91. {
  92. return $this->_db;
  93. }
  94. /**
  95. * Set database host
  96. *
  97. * @param string $host
  98. * @return Phly_Couch
  99. */
  100. public function setHost($host)
  101. {
  102. $this->_host = $host;
  103. return $this;
  104. }
  105. /**
  106. * Retrieve database host
  107. *
  108. * @return string
  109. */
  110. public function getHost()
  111. {
  112. return $this->_host;
  113. }
  114. /**
  115. * Set database host port
  116. *
  117. * @param int $port
  118. * @return Phly_Couch
  119. */
  120. public function setPort($port)
  121. {
  122. $this->_port = (int) $port;
  123. return $this;
  124. }
  125. /**
  126. * Retrieve database host port
  127. *
  128. * @return int
  129. */
  130. public function getPort()
  131. {
  132. return $this->_port;
  133. }
  134. // HTTP client
  135. /**
  136. * Set HTTP client
  137. *
  138. * @param Zend_Http_Client $client
  139. * @return Phly_Couch
  140. */
  141. public function setHttpClient(Zend_Http_Client $client)
  142. {
  143. $this->_client = $client;
  144. return $this;
  145. }
  146. /**
  147. * Set default HTTP client
  148. *
  149. * @param Zend_Http_Client $client
  150. * @return void
  151. */
  152. public static function setDefaultHttpClient(Zend_Http_Client $client)
  153. {
  154. self::$_defaultClient = $client;
  155. }
  156. /**
  157. * Get current HTTP client
  158. *
  159. * @return Zend_Http_Client
  160. */
  161. public function getHttpClient()
  162. {
  163. if (null === $this->_client) {
  164. $client = self::getDefaultHttpClient();
  165. if (null === $client) {
  166. require_once 'Zend/Http/Client.php';
  167. $client = new Zend_Http_Client;
  168. }
  169. $this->setHttpClient($client);
  170. }
  171. return $this->_client;
  172. }
  173. /**
  174. * Retrieve default HTTP client
  175. *
  176. * @return null|Zend_Http_Client
  177. */
  178. public static function getDefaultHttpClient()
  179. {
  180. return self::$_defaultClient;
  181. }
  182. // API METHODS
  183. // Server API methods
  184. /**
  185. * Get server information
  186. *
  187. * @return Phly_Couch_Result
  188. */
  189. public function serverInfo()
  190. {
  191. $this->_prepareUri('');
  192. $response = $this->_prepareAndSend('', 'GET');
  193. if (!$response->isSuccessful()) {
  194. require_once 'Phly/Couch/Exception.php';
  195. throw new Phly_Couch_Exception(sprintf('Failed retrieving server information; received response code "%s"', (string) $response->getStatus()));
  196. }
  197. require_once 'Phly/Couch/Result.php';
  198. return new Phly_Couch_Result($response);
  199. }
  200. /**
  201. * Get list of all databases
  202. *
  203. * @return Phly_Couch_Result
  204. */
  205. public function allDbs()
  206. {
  207. $response = $this->_prepareAndSend('_all_dbs', 'GET');
  208. if (!$response->isSuccessful()) {
  209. require_once 'Phly/Couch/Exception.php';
  210. throw new Phly_Couch_Exception(sprintf('Failed retrieving database list; received response code "%s"', (string) $response->getStatus()));
  211. }
  212. require_once 'Phly/Couch/Result.php';
  213. return new Phly_Couch_Result($response);
  214. }
  215. // Database API methods
  216. /**
  217. * Compact a database
  218. *
  219. * @param null|string $db
  220. * @return Phly_Couch_Result
  221. * @throws Phly_Couch_Exception when fails or no database specified
  222. */
  223. public function dbCompact($db = null)
  224. {
  225. $db = $this->_verifyDb($db);
  226. $response = $this->_prepareAndSend($db . '/_compact', 'POST');
  227. $status = $response->getStatus();
  228. if (202 !== $status) {
  229. require_once 'Phly/Couch/Exception.php';
  230. throw new Phly_Couch_Exception(sprintf('Failed compacting database "%s": received response code "%s"', $db, (string) $response->getStatus()));
  231. }
  232. require_once 'Phly/Couch/Result.php';
  233. return new Phly_Couch_Result($response);
  234. }
  235. /**
  236. * Create database
  237. *
  238. * @param string $db
  239. * @return Phly_Couch_Result
  240. * @throws Phly_Couch_Exception when fails or invalid database name
  241. */
  242. public function dbCreate($db = null)
  243. {
  244. $db = $this->_verifyDb($db);
  245. $response = $this->_prepareAndSend($db, 'PUT');
  246. if (!$response->isSuccessful()) {
  247. require_once 'Phly/Couch/Exception.php';
  248. throw new Phly_Couch_Exception(sprintf('Failed creating database "%s"; received response code "%s"', $db, (string) $response->getStatus()));
  249. }
  250. require_once 'Phly/Couch/Result.php';
  251. return new Phly_Couch_Result($response);
  252. }
  253. /**
  254. * Drop database
  255. *
  256. * @param string $db
  257. * @return Phly_Couch_Result
  258. * @throws Phly_Couch_Exception when fails
  259. */
  260. public function dbDrop($db = null)
  261. {
  262. $db = $this->_verifyDb($db);
  263. $response = $this->_prepareAndSend($db, 'DELETE');
  264. if (!$response->isSuccessful()) {
  265. require_once 'Phly/Couch/Exception.php';
  266. throw new Phly_Couch_Exception(sprintf('Failed dropping database "%s"; received response code "%s"', $db, (string) $response->getStatus()));
  267. }
  268. require_once 'Phly/Couch/Result.php';
  269. return new Phly_Couch_Result($response);
  270. }
  271. /**
  272. * Get database info
  273. *
  274. * @param string $db
  275. * @return Phly_Couch_Result
  276. * @throws Phly_Couch_Exception when fails
  277. */
  278. public function dbInfo($db = null)
  279. {
  280. $db = $this->_verifyDb($db);
  281. $response = $this->_prepareAndSend($db, 'GET');
  282. if (!$response->isSuccessful()) {
  283. require_once 'Phly/Couch/Exception.php';
  284. throw new Phly_Couch_Exception(sprintf('Failed querying database "%s"; received response code "%s"', $db, (string) $response->getStatus()));
  285. }
  286. require_once 'Phly/Couch/Result.php';
  287. return new Phly_Couch_Result($response);
  288. }
  289. // Document API methods
  290. /**
  291. * Retrieve all documents for a give database
  292. *
  293. * @param null|array $options Query options
  294. * @return Phly_Couch_DocumentSet
  295. * @throws Phly_Couch_Exception on failure or bad db
  296. */
  297. public function allDocs(array $options = null)
  298. {
  299. $db = null;
  300. if (is_array($options) && array_key_exists('db', $options)) {
  301. $db = $options['db'];
  302. unset($options['db']);
  303. }
  304. $db = $this->_verifyDb($db);
  305. $response = $this->_prepareAndSend($db . '/_all_docs', 'GET', $options);
  306. if (!$response->isSuccessful()) {
  307. require_once 'Phly/Couch/Exception.php';
  308. throw new Phly_Couch_Exception(sprintf('Failed querying database "%s"; received response code "%s"', $db, (string) $response->getStatus()));
  309. }
  310. require_once 'Phly/Couch/DocumentSet.php';
  311. return new Phly_Couch_DocumentSet($response->getBody());
  312. }
  313. /**
  314. * Open a document
  315. *
  316. * @param string $id
  317. * @param null|array $options
  318. * @return Phly_Couch_Document
  319. * @throws Phly_Couch_Exception on failure
  320. * @todo handle unsuccessful call
  321. */
  322. public function docOpen($id, array $options = null)
  323. {
  324. $db = null;
  325. if (is_array($options) && array_key_exists('db', $options)) {
  326. $db = $options['db'];
  327. unset($options['db']);
  328. }
  329. $db = $this->_verifyDb($db);
  330. $response = $this->_prepareAndSend($db . '/' . $id, 'GET', $options);
  331. require_once 'Phly/Couch/Document.php';
  332. return new Phly_Couch_Document($response->getBody());
  333. }
  334. /**
  335. * Save a document
  336. *
  337. * @param string|array|Phly_Couch_Document $document
  338. * @param null|string $id
  339. * @param null|string $db
  340. * @return Phly_Couch_Result
  341. * @throws Phly_Couch_Exception on failure
  342. */
  343. public function docSave($document, $id = null, $db = null)
  344. {
  345. $db = $this->_verifyDb($db);
  346. $path = $db . '/';
  347. $method = 'POST';
  348. if (null !== $id) {
  349. $method = 'PUT';
  350. $path .= $id;
  351. }
  352. if (is_string($document)) {
  353. if ('{' != substr($document, 0, 1)) {
  354. require_once 'Phly/Couch/Exception.php';
  355. throw new Phly_Couch_Exception('Invalid document provided');
  356. }
  357. require_once 'Phly/Couch/Document.php';
  358. $document = new Phly_Couch_Document($document);
  359. } elseif (is_array($document)) {
  360. require_once 'Phly/Couch/Document.php';
  361. $document = new Phly_Couch_Document($document);
  362. } elseif (!$document instanceof Phly_Couch_Document) {
  363. require_once 'Phly/Couch/Exception.php';
  364. throw new Phly_Couch_Exception('Invalid document provided');
  365. }
  366. if (null !== $document->getRevision()) {
  367. if ((null === $id) && (null === ($id = $document->getId()))) {
  368. require_once 'Phly/Couch/Exception.php';
  369. throw new Phly_Couch_Exception('Document updates require a document id; none provided');
  370. }
  371. $method = 'PUT';
  372. }
  373. $this->getHttpClient()->setRawData($document->toJson());
  374. $response = $this->_prepareAndSend($path, $method);
  375. $status = $response->getStatus();
  376. switch ($status) {
  377. case 412:
  378. require_once 'Phly/Couch/Exception.php';
  379. throw new Phly_Couch_Exception(sprintf('Document with the specified document id "%s" already exists', $id));
  380. break;
  381. case 409:
  382. require_once 'Phly/Couch/Exception.php';
  383. throw new Phly_Couch_Exception(sprintf('Document with document id "%s" does not contain the revision "%s"', $id, $data['_rev']));
  384. break;
  385. case 201:
  386. default:
  387. require_once 'Phly/Couch/Result.php';
  388. return new Phly_Couch_Result($response);
  389. break;
  390. }
  391. }
  392. /**
  393. * Remove a document
  394. *
  395. * @param string $id
  396. * @param array $options
  397. * @return Phly_Couch_Result
  398. * @throws Phly_Couch_Exception on failed call
  399. */
  400. public function docRemove($id, array $options = null)
  401. {
  402. $db = null;
  403. if (is_array($options) && array_key_exists('db', $options)) {
  404. $db = $options['db'];
  405. unset($options['db']);
  406. }
  407. $db = $this->_verifyDb($db);
  408. $response = $this->_prepareAndSend($db . '/' . $id, 'DELETE', $options);
  409. if (!$response->isSuccessful()) {
  410. require_once 'Phly/Couch/Exception.php';
  411. throw new Phly_Couch_Exception(sprintf('Failed deleting document with id "%s" from database "%s"; received response code "%s"', $id, $db, (string) $response->getStatus()));
  412. }
  413. require_once 'Phly/Couch/Result.php';
  414. return new Phly_Couch_Result($response);
  415. }
  416. /**
  417. * Bulk save many documents at once
  418. *
  419. * @param array|Phly_Couch_DocumentSet $documents
  420. * @param array $options
  421. * @return Phly_Couch_Result
  422. * @throws Phly_Couch_Exception on failed save
  423. */
  424. public function docBulkSave($documents, array $options = null)
  425. {
  426. $db = null;
  427. if (is_array($options) && array_key_exists('db', $options)) {
  428. $db = $options['db'];
  429. unset($options['db']);
  430. }
  431. $db = $this->_verifyDb($db);
  432. if (is_array($documents)) {
  433. require_once 'Phly/Couch/DocumentSet.php';
  434. $documents = new Phly_Couch_DocumentSet($documents);
  435. } elseif (!$documents instanceof Phly_Couch_DocumentSet) {
  436. require_once 'Phly/Couch/Exception.php';
  437. throw new Phly_Couch_Exception('Invalid document set provided to bulk save operation');
  438. }
  439. $this->getHttpClient()->setRawData($documents->toJson());
  440. $response = $this->_prepareAndSend($db . '/_bulk_docs', 'POST', $options);
  441. if (!$response->isSuccessful()) {
  442. require_once 'Phly/Couch/Exception.php';
  443. throw new Phly_Couch_Exception(sprintf('Failed deleting document with id "%s" from database "%s"; received response code "%s"', $id, $db, (string) $response->getStatus()));
  444. }
  445. require_once 'Phly/Couch/Result.php';
  446. return new Phly_Couch_Result($response);
  447. }
  448. /**
  449. * Retrieve a view
  450. *
  451. * @param string $design
  452. * @param string $view
  453. * @param string $key ?key=key
  454. * @param null|array $options
  455. * @return Phly_Couch_DocumentSet
  456. * @throws Phly_Couch_Exception on failure or bad db
  457. */
  458. public function view($design, $view, $key = null, array $options = null)
  459. {
  460. $db = null;
  461. if (is_array($options) && array_key_exists('db', $options)) {
  462. $db = $options['db'];
  463. unset($options['db']);
  464. }
  465. $db = $this->_verifyDb($db);
  466. $param = '';
  467. if (!is_null($key)) {
  468. // simple key
  469. if (is_numeric($key)) {
  470. $param = '?key='.$key;
  471. } elseif (is_string($key)) {
  472. $param = '?key=%22'.$key.'%22';
  473. // for intervals keys
  474. } elseif (is_array($key) && count($key)==2) {
  475. if (is_numeric($key[0])) {
  476. $param = '?startkey='.$key[0];
  477. } else {
  478. $param = '?startkey=%22'.$key[0].'%22';
  479. }
  480. if (is_numeric($key[1])) {
  481. $param .= '&endkey='.$key[1];
  482. } else {
  483. $param .= '&endkey=%22'.$key[1].'%22';
  484. }
  485. } else {
  486. throw new Phly_Couch_Exception('Wrong key type!');
  487. }
  488. }
  489. $q = $db . '/_design/'.$design.'/_view/'.$view.'/'.$param;
  490. $response = $this->_prepareAndSend($q, 'GET', $options);
  491. if (!$response->isSuccessful()) {
  492. require_once 'Phly/Couch/Exception.php';
  493. throw new Phly_Couch_Exception(sprintf('Failed querying database "%s"; received response code "%s"', $db, (string) $response->getStatus()));
  494. }
  495. require_once 'Phly/Couch/DocumentSet.php';
  496. return new Phly_Couch_DocumentSet($response->getBody());
  497. }
  498. // Helper methods
  499. /**
  500. * Prepare the URI
  501. *
  502. * @param string $path
  503. * @param null|array $queryParams
  504. * @return void
  505. */
  506. protected function _prepareUri($path, array $queryParams = null)
  507. {
  508. $client = $this->getHttpClient();
  509. $uri = 'http://' . $this->getHost() . ':' . $this->getPort() . '/' . $path;
  510. $client->setUri($uri);
  511. if (null !== $queryParams) {
  512. foreach ($queryParams as $key => $value) {
  513. if (is_bool($value)) {
  514. $queryParams[$key] = ($value) ? 'true' : 'false';
  515. }
  516. }
  517. $client->setParameterGet($queryParams);
  518. }
  519. }
  520. /**
  521. * Prepare the URI and send the request
  522. *
  523. * @param string $path
  524. * @param string $method
  525. * @param null|array $queryParams
  526. * @return Zend_Http_Response
  527. */
  528. protected function _prepareAndSend($path, $method, array $queryParams = null)
  529. {
  530. $client = $this->getHttpClient();
  531. $this->_prepareUri($path, $queryParams);
  532. $client->setEncType('application/json');
  533. $response = $client->request($method);
  534. $client->resetParameters();
  535. return $response;
  536. }
  537. /**
  538. * Verify database parameter
  539. *
  540. * @param mixed $db
  541. * @return string
  542. * @throws Phly_Couch_Exception for invalid database
  543. */
  544. protected function _verifyDb($db)
  545. {
  546. if (null === $db) {
  547. $db = $this->getDb();
  548. if (null === $db) {
  549. require_once 'Phly/Couch/Exception.php';
  550. throw new Phly_Couch_Exception('Must specify a database to query');
  551. }
  552. } else {
  553. $this->setDb($db);
  554. }
  555. return $db;
  556. }
  557. }