├── .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 | [](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 |
--------------------------------------------------------------------------------