├── .gitignore ├── phpci.yml ├── composer.json ├── LICENSE ├── README.md └── Chill ├── Exception ├── Conflict.php ├── Response.php └── Connection.php ├── Exception.php ├── Document.php └── Client.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /phpci.yml: -------------------------------------------------------------------------------- 1 | build_settings: 2 | verbose: false 3 | 4 | test: 5 | php_mess_detector: 6 | allowed_warnings: 2 7 | php_code_sniffer: 8 | standard: "PSR2" 9 | allowed_warnings: 0 10 | allowed_errors: 0 11 | php_loc: 12 | php_docblock_checker: 13 | allowed_warnings: 0 14 | 15 | failure: 16 | email: 17 | committer: true 18 | cc: ["dan@block8.co.uk"] 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chill/chill", 3 | "type": "library", 4 | "description": "CouchDb client library for PHP 5.3+", 5 | "keywords": ["couchdb", "couch", "database", "client", "library"], 6 | "homepage": "https://github.com/dancryer/Chill", 7 | "license": "BSD-2-Clause", 8 | "authors": [ 9 | { 10 | "name": "Dan Cryer", 11 | "email": "dan@dancryer.com", 12 | "homepage": "http://www.dancryer.com", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.0" 18 | }, 19 | "autoload": { 20 | "psr-0": { "Chill": "" } 21 | }, 22 | "support": { 23 | "issues": "https://github.com/dancryer/Chill/issues" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Dan Cryer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chill - PHP Library for CouchDb 2 | =============================== 3 | 4 | Chill is a simple and efficient CouchDb client library for PHP. Released under the BSD 2 Clause Licence and made available via [Composer/Packagist](https://packagist.org/packages/chill/chill). 5 | 6 | **Current Build Status:** 7 | 8 | [![Build Status](http://phpci.block8.net/build-status/image/4?branch=master)](http://phpci.block8.net/build-status/view/4?branch=master) 9 | 10 | Example usage 11 | ------------- 12 | 13 | **Retrieve a single document by ID:** 14 | 15 | ```php 16 | $chill = new Chill\Client('localhost', 'my_database'); 17 | $doc = $chill->get('8128173972d50affdb6724ecbd00d9fc'); 18 | print $doc['_id']; 19 | ``` 20 | 21 | **Retrieve the results of a view as Chill Document objects:** 22 | 23 | ```php 24 | $chill = new Chill\Client('localhost', 'my_database'); 25 | $docs = $chill->asDocuments()->getView('mydesign', 'myview', array('key1', 'key2')); 26 | 27 | foreach ($docs as $doc) { 28 | print $doc->_id . PHP_EOL; 29 | } 30 | ``` 31 | 32 | **Retrieve and update a document** 33 | 34 | ```php 35 | $chill = new Chill\Client('localhost', 'my_database'); 36 | $doc = $chill->get('8128173972d50affdb6724ecbd00d9fc'); 37 | $doc->title = 'Changing my doc.'; 38 | $doc->save(); 39 | ``` 40 | 41 | With thanks to 42 | -------------- 43 | * [Sylvain Filteau](https://github.com/sylvainfilteau) for contributing various bug fixes. 44 | * [Luke Plaster](https://github.com/notatestuser) for contributing support for arrays as view parameters. 45 | * [Ryan Hughes](https://github.com/ryanhughes) for fixing a bug related to PUT requests. 46 | -------------------------------------------------------------------------------- /Chill/Exception/Conflict.php: -------------------------------------------------------------------------------- 1 | 29 | * @link https://github.com/dancryer/Chill 30 | * @package Chill 31 | */ 32 | 33 | namespace Chill\Exception; 34 | 35 | /** 36 | * Chill Conflict Exception 37 | * 38 | * @package Chill 39 | */ 40 | class Conflict extends \Chill\Exception 41 | { 42 | } 43 | -------------------------------------------------------------------------------- /Chill/Exception/Response.php: -------------------------------------------------------------------------------- 1 | 29 | * @link https://github.com/dancryer/Chill 30 | * @package Chill 31 | */ 32 | 33 | namespace Chill\Exception; 34 | 35 | /** 36 | * Chill Response Exception 37 | * 38 | * @package Chill 39 | */ 40 | class Response extends \Chill\Exception 41 | { 42 | } 43 | -------------------------------------------------------------------------------- /Chill/Exception.php: -------------------------------------------------------------------------------- 1 | 29 | * @link https://github.com/dancryer/Chill 30 | * @package Chill 31 | */ 32 | 33 | namespace Chill; 34 | 35 | /** 36 | * Chill Basic Exception 37 | * 38 | * @package Chill 39 | */ 40 | class Exception extends \Exception 41 | { 42 | } 43 | -------------------------------------------------------------------------------- /Chill/Exception/Connection.php: -------------------------------------------------------------------------------- 1 | 29 | * @link https://github.com/dancryer/Chill 30 | * @package Chill 31 | */ 32 | 33 | namespace Chill\Exception; 34 | 35 | /** 36 | * Chill Connection Exception 37 | * 38 | * @package Chill 39 | */ 40 | class Connection extends \Chill\Exception 41 | { 42 | } 43 | -------------------------------------------------------------------------------- /Chill/Document.php: -------------------------------------------------------------------------------- 1 | 29 | * @link https://github.com/dancryer/Chill 30 | * @package Chill 31 | */ 32 | 33 | namespace Chill; 34 | 35 | /** 36 | * ChillDoc object - Representation of a CouchDb document. 37 | * 38 | * Usage: 39 | * 40 | * $chill = new Chill\Client('localhost', 'my_database'); 41 | * $doc = $chill->get('8128173972d50affdb6724ecbd00d9fc'); 42 | * $doc->title = 'Changing my doc.'; 43 | * $doc->save(); 44 | * 45 | * 46 | * @package Chill 47 | */ 48 | class Document 49 | { 50 | /** 51 | * @var array Document data from CouchDb. 52 | */ 53 | protected $data = array(); 54 | 55 | /** 56 | * @var Chill Chill class, for interacting with CouchDb. 57 | */ 58 | protected $chill = null; 59 | 60 | /** 61 | * Constructor - Create a new document, or load an existing one from data. 62 | * 63 | * @param Chill $chill Chill class. 64 | * @param array $doc (Optional) Document data. 65 | */ 66 | public function __construct(Client $chill, array $doc = array()) 67 | { 68 | $this->chill = $chill; 69 | $this->data = $doc; 70 | } 71 | 72 | /** 73 | * Checks whether a key is set on this document. 74 | * 75 | * @param string $key The key. 76 | * @return bool 77 | */ 78 | public function __isset($key) 79 | { 80 | return isset($this->data[$key]); 81 | } 82 | 83 | /** 84 | * Get the value of a key in this document. 85 | * 86 | * @param string $key The key. 87 | * @return mixed 88 | */ 89 | public function __get($key) 90 | { 91 | if (isset($this->data[$key])) { 92 | return $this->data[$key]; 93 | } else { 94 | return null; 95 | } 96 | } 97 | 98 | /** 99 | * Set the value of a key in this document. 100 | * 101 | * @param string $key The key. 102 | * @param string $value The value. 103 | */ 104 | public function __set($key, $value) 105 | { 106 | $this->data[$key] = $value; 107 | } 108 | 109 | /** 110 | * Save this document, either by updating the document that already exists, or creating. Based on presence of _id. 111 | * 112 | * @return bool 113 | */ 114 | public function save() 115 | { 116 | try { 117 | if (isset($this->data['_id'])) { 118 | $this->data = array_merge($this->data, $this->chill->put($this->_id, $this->data)); 119 | } else { 120 | $this->data = array_merge($this->data, $this->chill->post($this->data)); 121 | } 122 | 123 | return true; 124 | } catch (Chill\Exception $ex) { 125 | return false; 126 | } 127 | } 128 | 129 | /** 130 | * Get the internal data array for this object. 131 | * 132 | * @return array 133 | */ 134 | public function getArray() 135 | { 136 | return $this->data; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Chill/Client.php: -------------------------------------------------------------------------------- 1 | 29 | * @link https://github.com/dancryer/Chill 30 | * @package Chill 31 | */ 32 | 33 | namespace Chill; 34 | 35 | /** 36 | * Chill - CouchDb Client Library 37 | * 38 | * Usage - Get one document as array: 39 | * 40 | * $chill = new Chill\Client('localhost', 'my_database'); 41 | * $doc = $chill->get('8128173972d50affdb6724ecbd00d9fc'); 42 | * print $doc['_id']; 43 | * 44 | * 45 | * Usage - Get view results as Chill Document objects: 46 | * 47 | * $chill = new Chill\Client('localhost', 'my_database'); 48 | * $docs = $chill->asDocuments()->getView('mydesign', 'myview', array('key1', 'key2')); 49 | * foreach($docs as $doc) 50 | * { 51 | * print $doc->_id . PHP_EOL; 52 | * } 53 | * 54 | * 55 | * @package Chill 56 | */ 57 | class Client 58 | { 59 | /** 60 | * @var string Database URL 61 | * @see Chill\Client::__construct() 62 | */ 63 | protected $url = null; 64 | 65 | /** 66 | * @var array Object cache 67 | * @see Chill\Client::getCache() 68 | * @see Chill\Client::setCache() 69 | */ 70 | protected $cache = array(); 71 | 72 | /** 73 | * @var bool Get documents as arrays (false) or Chill Document objects (true) 74 | * @see Chill\Client::asDocuments() 75 | * @see Chill\Client::toDocument() 76 | * @see Chill\Client::toDocuments() 77 | */ 78 | protected $asDocs = false; 79 | 80 | /** 81 | * Constructor - Create a new Chill object. 82 | * 83 | * @param string $host Hostname of the CouchDb server. 84 | * @param string $database Database name. 85 | * @param integer $port Port number. 86 | * @param string $scheme http or https. 87 | */ 88 | public function __construct($host, $database, $port = 5984, $scheme = 'http') 89 | { 90 | $this->url = $scheme . '://' . $host . ':' . $port . '/' . $database . '/'; 91 | } 92 | 93 | /** 94 | * Get the results of a CouchDb view as an array of arrays, or Chill Document objects. 95 | * 96 | * @param string $design Design name. 97 | * @param string $view View name. 98 | * @param mixed $key (Optional) String key, or array of keys, to pass to the view. 99 | * @param array $params (Optional) Array of query string parameters to pass to the view. 100 | * @return \Chill\Document[]|array 101 | */ 102 | public function getView($design, $view, $key = null, $params = array()) 103 | { 104 | $query = $this->processViewParameters($params); 105 | 106 | $url = '_design/' . $design . '/_view/' . $view . '?' . implode('&', $query); 107 | 108 | if (is_array($key)) { 109 | $rtn = $this->getViewByPost($url, $key); 110 | } else { 111 | if (!is_null($key)) { 112 | if (is_string($key)) { 113 | $key = '"' . $key . '"'; 114 | } 115 | 116 | if (is_bool($key)) { 117 | $key = $key ? 'true' : 'false'; 118 | } 119 | 120 | $url .= '&key=' . $key; 121 | } 122 | 123 | $rtn = $this->getViewByGet($url); 124 | } 125 | 126 | return $rtn; 127 | } 128 | 129 | /** 130 | * Sub-function of getView() - Makes requests by GET. 131 | * 132 | * @param string $url Full URL of the view, including parameters. 133 | * @return \Chill\Document[]|array 134 | */ 135 | protected function getViewByGet($url) 136 | { 137 | $response = $this->getCache($url); 138 | 139 | if (!$response) { 140 | list($status, $response) = $this->sendRequest($url); 141 | 142 | if ($status == 200) { 143 | $response = $this->setCache($url, $response); 144 | } else { 145 | $response = array(); 146 | } 147 | } 148 | 149 | return $this->asDocs ? $this->toDocuments($response) : $response; 150 | } 151 | 152 | /** 153 | * Sub-function of getView() - Makes requests by POST. 154 | * 155 | * @param string $url Full URL of the view, including parameters. 156 | * @param array $keys Array of acceptable keys. 157 | * @see Chill\Client::getView() 158 | * @return \Chill\Document[]|array 159 | * @throws \Chill\Exception\Response 160 | */ 161 | protected function getViewByPost($url, array $keys) 162 | { 163 | $context = array('http' => array()); 164 | 165 | $context['http']['method'] = 'POST'; 166 | $context['http']['header'] = 'Content-Type: application/json'; 167 | $context['http']['content'] = json_encode(array('keys' => $keys)); 168 | 169 | list($status, $response) = $this->sendRequest($url, $context); 170 | 171 | if ($status != 200) { 172 | throw new Exception\Response('POST View - Unknown response status.'); 173 | } 174 | 175 | return $this->asDocs ? $this->toDocuments($response) : $response; 176 | } 177 | 178 | /** 179 | * Get all documents in the database. 180 | * 181 | * @see Chill\Client::getViewByGet() 182 | */ 183 | public function getAllDocuments() 184 | { 185 | $response = $this->getViewByGet('_all_docs'); 186 | return $this->asDocs ? $this->toDocuments($response) : $response; 187 | } 188 | 189 | /** 190 | * Get document by ID, optionally pull from cache if previously queried. 191 | * 192 | * @param string $documentId Document ID. 193 | * @param bool $cache Whether or not to use the cache. 194 | * @link http://wiki.apache.org/couchdb/HTTP_Document_API#GET 195 | * @return \Chill\Document[]|array 196 | */ 197 | public function get($documentId, $cache = true) 198 | { 199 | $rtn = $this->getCache($documentId); 200 | 201 | if (!$cache || !$rtn) { 202 | list($status, $doc) = $this->sendRequest(urlencode($documentId)); 203 | 204 | if ($status == 200) { 205 | $rtn = $this->setCache($documentId, $doc); 206 | } else { 207 | $rtn = null; 208 | } 209 | } 210 | 211 | return $this->asDocs ? $this->toDocument($rtn) : $rtn; 212 | } 213 | 214 | /** 215 | * Update or create a document by ID. 216 | * CouchDb recommends using PUT rather than POST where possible to avoid proxy issues. 217 | * 218 | * @param string $documentId ID to update or create. 219 | * @param array $doc Document to store. 220 | * @link http://wiki.apache.org/couchdb/HTTP_Document_API#PUT 221 | * @throws \Chill\Exception\Conflict 222 | * @throws \Chill\Exception\Response 223 | * @return array 224 | */ 225 | public function put($documentId, array $doc) 226 | { 227 | $context = array('http' => array()); 228 | 229 | $context['http']['method'] = 'PUT'; 230 | $context['http']['header'] = 'Content-Type: application/json'; 231 | $context['http']['content'] = json_encode($doc); 232 | 233 | $rev = isset($doc['_rev']) ? '?rev=' . $doc['_rev'] : ''; 234 | list($status, $response) = $this->sendRequest(urlencode($documentId) . $rev, $context); 235 | 236 | if ($status == 409) { 237 | throw new Exception\Conflict('PUT /' . $documentId . ' failed.'); 238 | } elseif ($status != 201) { 239 | throw new Exception\Response('PUT /' . $documentId . ' - Unknown response status.'); 240 | } 241 | 242 | if (isset($response['id'])) { 243 | return array('_id' => $response['id'], '_rev' => $response['rev']); 244 | } else { 245 | return $response; 246 | } 247 | } 248 | 249 | /** 250 | * Create a new document by POST. 251 | * CouchDb recommends using PUT rather than POST where possible to avoid proxy issues. 252 | * 253 | * @param array $doc Document to store. 254 | * @link http://wiki.apache.org/couchdb/HTTP_Document_API#POST 255 | * @throws \Chill\Exception\Response 256 | * @return array 257 | */ 258 | public function post(array $doc) 259 | { 260 | $context = array('http' => array()); 261 | 262 | $context['http']['method'] = 'POST'; 263 | $context['http']['header'] = 'Content-Type: application/json'; 264 | $context['http']['content'] = json_encode($doc); 265 | 266 | list($status, $response) = $this->sendRequest('', $context); 267 | 268 | if ($status != 201) { 269 | throw new Exception\Response('POST - Unknown response status.'); 270 | } 271 | 272 | if (isset($response['id'])) { 273 | return array('_id' => $response['id'], '_rev' => $response['rev']); 274 | } else { 275 | return $response; 276 | } 277 | } 278 | 279 | /** 280 | * Delete document by ID. 281 | * 282 | * @param string $documentId Document ID. 283 | * @param string $rev Document revision ID. 284 | * @link http://wiki.apache.org/couchdb/HTTP_Document_API#DELETE 285 | * @throws \Chill\Exception\Response 286 | * @return bool|void 287 | */ 288 | public function delete($documentId, $rev) 289 | { 290 | $context = array('http' => array()); 291 | $context['http']['method'] = 'DELETE'; 292 | 293 | list($status, $response) = $this->sendRequest($documentId . '?rev=' . $rev, $context); 294 | unset($response); 295 | 296 | if ($status != 200) { 297 | throw new Exception\Response('DELETE - Unknown response status.'); 298 | } 299 | 300 | if ($this->getCache($documentId)) { 301 | unset($this->cache[$documentId]); 302 | } 303 | 304 | return true; 305 | } 306 | 307 | /** 308 | * Get a document from this class' internal cache. 309 | * 310 | * @param string $documentId ID to get from cache. 311 | * @return \Chill\Document[]|array|null 312 | */ 313 | protected function getCache($documentId) 314 | { 315 | if (isset($this->cache[$documentId])) { 316 | return $this->cache[$documentId]; 317 | } 318 | 319 | return null; 320 | } 321 | 322 | /** 323 | * Put a document into this class' internal cache. 324 | * 325 | * @param string $documentId ID to get from cache. 326 | * @param mixed $value Object to store. 327 | * @return \Chill\Document[]|array 328 | */ 329 | protected function setCache($documentId, $value) 330 | { 331 | $this->cache[$documentId] = $value; 332 | 333 | return $this->cache[$documentId]; 334 | } 335 | 336 | /** 337 | * Define whether or not to convert documents to Chill Documents on return. 338 | * 339 | * @param bool $docs Convert, or not? 340 | * @return Chill\Document This class. (Chainable) 341 | */ 342 | public function asDocuments($docs = true) 343 | { 344 | $this->asDocs = $docs; 345 | return $this; 346 | } 347 | 348 | /** 349 | * Convert one CouchDb document result to a Chill Document object. 350 | * 351 | * @param array $doc Document to convert. 352 | * @return \Chill\Document 353 | */ 354 | protected function toDocument($doc = array()) 355 | { 356 | if ($doc && isset($doc['_id'])) { 357 | // Single document: 358 | return new Document($this, $doc); 359 | } else { 360 | return null; 361 | } 362 | } 363 | 364 | /** 365 | * Convert many CouchDb documents to Chill Document objects. 366 | * 367 | * @param array $docs Documents to convert. 368 | * @return \Chill\Document[] 369 | */ 370 | protected function toDocuments(array $docs = array()) 371 | { 372 | if (isset($docs['rows'])) { 373 | $rtn = array(); 374 | 375 | foreach ($docs['rows'] as $row) { 376 | $rtn[] = $this->toDocument($row['value']); 377 | } 378 | 379 | return $rtn; 380 | } else { 381 | return array(); 382 | } 383 | } 384 | 385 | /** 386 | * Send request to CouchDb server. Currently uses file_get_contents() to make requests. 387 | * 388 | * @param string $uri Request URI, e.g. _design/mydesign/_view/myview?key="test" 389 | * @param array @context Array of stream_context_create options. 390 | * @throws \Chill\Exception\Connection 391 | * @return array 392 | */ 393 | protected function sendRequest($uri, array $context = array()) 394 | { 395 | $context['http']['timeout'] = 5; 396 | $context['http']['ignore_errors'] = true; 397 | $context['http']['user_agent'] = 'Simple Couch/1.0'; 398 | 399 | $context = stream_context_create($context); 400 | $response = @file_get_contents($this->url . $uri, false, $context); 401 | 402 | if ($response === false) { 403 | throw new Exception\Connection('Could not connect to CouchDb server.'); 404 | } 405 | 406 | $statusParts = explode(' ', $http_response_header[0]); 407 | 408 | return array((int)$statusParts[1], json_decode($response, true)); 409 | } 410 | 411 | /** 412 | * @param array $params 413 | * @return string[] 414 | */ 415 | protected function processViewParameters(array $params) 416 | { 417 | $query = array(); 418 | 419 | foreach ($params as $k => $v) { 420 | if (is_string($v)) { 421 | $v = '"' . $v . '"'; 422 | } 423 | 424 | if (is_bool($v)) { 425 | $v = $v ? 'true' : 'false'; 426 | } 427 | 428 | if (is_array($v)) { 429 | $v = json_encode($v); 430 | } 431 | 432 | $query[] = $k . '=' . urlencode($v); 433 | } 434 | 435 | return $query; 436 | } 437 | } 438 | --------------------------------------------------------------------------------