Completed
Push — master ( cd07bc...3f0c07 )
by Filippo
03:48
created

Couch::setRevsLimit()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 2
nop 2
1
<?php
2
3
/**
4
 * @file Couch.php
5
 * @brief This file contains the Couch class.
6
 * @details
7
 * @author Filippo F. Fadda
8
 */
9
10
//! This is the main Elephant on Couch library namespace.
11
namespace EoC;
12
13
14
use Surfer\Surfer;
15
use Surfer\Adapter\IClientAdapter;
16
use Surfer\Message\Request;
17
use Surfer\Message\Response;
18
use Surfer\Hook\IChunkHook;
19
20
21
/**
22
 * @brief The CouchDB's client. You need an instance of this class to interact with CouchDB.
23
 * @nosubgrouping
24
 * @todo Add Memcached support. Remember to use Memcached extension, not memcache.
25
 * @todo Check ISO-8859-1 because CouchDB uses it, in particular utf8_encode().
26
 */
27
final class Couch extends Surfer {
28
29
  //! The user agent name.
30
  const USER_AGENT_NAME = "EoC Client";
31
32
  //! Default CouchDB revisions limit number.
33
  const REVS_LIMIT = 1000;
34
35
  /** @name Document Paths */
36
  //!@{
37
38
  const STD_DOC_PATH = ""; //!< Path for standard documents.
39
  const LOCAL_DOC_PATH = "_local/";  //!< Path for local documents.
40
  const DESIGN_DOC_PATH = "_design/";  //!< Path for design documents.
41
42
  //!@}
43
44
  // Socket or cURL HTTP client adapter.
45
  private $client;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $client is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
46
47
  // A database's name prefix.
48
  private $prefix = '';
49
50
51
  /**
52
   * @brief Creates a Couch class instance.
53
   * @param[in] IClientAdapter $adapter An instance of a class that implements the IClientAdapter interface.
54
   */
55
  public function __construct(IClientAdapter $adapter) {
56
    parent::__construct($adapter);
57
  }
58
59
60
  /**
61
   * @brief Returns a CouchDB wild card.
62
   * @details A standard object is translated to JSON as `{}` same of a JavaScript empty object.
63
   * @return [stdClass](http://php.net/manual/en/language.types.object.php)
0 ignored issues
show
Documentation introduced by
The doc-type stdClass">...guage.types.object.php">stdClass could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
64
   */
65
  public static function WildCard() {
66
    return new \stdClass();
67
  }
68
69
70
  /**
71
   * @brief Sets the document class and type in case the document hasn't one.
72
   * @param[in] Doc\IDoc $doc A document.
73
   */
74
  private function setDocInfo(Doc\IDoc $doc) {
75
    // Sets the class name.
76
    $class = get_class($doc);
77
    $doc->setClass($class);
78
79
    // Sets the document type.
80
    if (!$doc->hasType()) {
81
      preg_match('/([\w]+$)/', $class, $matches);
82
      $type = strtolower($matches[1]);
83
      $doc->setType($type);
84
    }
85
  }
86
87
88
  /**
89
   * @brief Overrides the rows, adding a new row for every key hasn't been matched.
90
   * @details CouchDB doesn't return rows for the keys a match is not found. To make joins having a row for each key is
91
   * essential. The algorithm below overrides the rows, adding a new row for every key hasn't been matched. The JSON
92
   * encoding is necessary because we might have complex keys. A complex key is no more than an array.
93
   * @param array $keys An array containing the keys.
94
   * @param[out] array $rows An associative array containing the rows.
95
   */
96
  private function addMissingRows($keys, &$rows) {
97
98
    if (!empty($keys) && isset($rows)) {
99
100
      // These are the rows for the matched keys.
101
      $matches = [];
102
      foreach ($rows as $row) {
103
        $hash = md5(json_encode($row['key']));
104
        $matches[$hash] = $row;
105
      }
106
107
      $allRows = [];
108
      foreach ($keys as $key) {
109
        $hash = md5(json_encode($key));
110
111
        if (isset($matches[$hash])) // Match found.
112
          $allRows[] = $matches[$hash];
113
        else // No match found.
114
          $allRows[] = ['id' => NULL, 'key' => $key, 'value' => NULL];
115
      }
116
117
      // Overrides the response, replacing rows.
118
      $rows = $allRows;
119
    }
120
121
  }
122
123
124
  /**
125
   * @brief Sets a prefix which is used to compose the database's name.
126
   * @param string $prefix A string prefix.
127
   */
128
  public function setDbPrefix($prefix) {
129
    $this->prefix = $prefix;
130
  }
131
132
133
  /**
134
   * @brief Gets the prefix used to compose the database's name if any.
135
   * @return string
136
   */
137
  public function getDbPrefix() {
138
    return $this->prefix;
139
  }
140
141
142
  /**
143
   * @brief This method is used to send a Request to CouchDB.
144
   * @details If you want call a not supported CouchDB API, you can use this function to send your request.\n
145
   * You can also provide an instance of a class that implements the IChunkHook interface, to deal with a chunked
146
   * response.
147
   * @param Request $request The Request object.
148
   * @param IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
149
   * @return Response
150
   */
151
  public function send(Request $request, IChunkHook $chunkHook = NULL) {
152
    // Sets user agent information.
153
    $request->setHeaderField(Request::USER_AGENT_HF, self::USER_AGENT_NAME." ".Version::getNumber());
154
155
    // We accept JSON.
156
    $request->setHeaderField(Request::ACCEPT_HF, "application/json");
157
158
    // We close the connection after read the response.
159
    // NOTE: we don't use anymore the connection header field, because we use the same socket until the end of script.
160
    //$request->setHeaderField(Message::CONNECTION_HF, "close");
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
161
162
    return parent::send($request, $chunkHook);
163
  }
164
165
166
  /** @name Validation and Encoding Methods */
167
  //!@{
168
169
  /**
170
   * @brief This method raise an exception when a user provide an invalid document path.
171
   * @details This method is called by any other methods that interacts with CouchDB. You don't need to call, unless
172
   * you are making a not supported call to CouchDB.
173
   * @param string $path Document path.
174
   * @param bool $excludeLocal Document path.
175
   */
176
  public function validateDocPath($path, $excludeLocal = FALSE) {
177
    if (empty($path)) // STD_DOC_PATH
178
      return;
179
180
    if ($path == self::DESIGN_DOC_PATH)
181
      return;
182
    elseif ($path == self::LOCAL_DOC_PATH && $excludeLocal)
183
      throw new \InvalidArgumentException("Local document doesn't have attachments.");
184
    else
185
      throw new \InvalidArgumentException("Invalid document path.");
186
  }
187
188
189
  /**
190
   * @brief Encodes the document id.
191
   * @details This method is called by any other methods that interacts with CouchDB. You don't need to call, unless
192
   * you are making a not supported call to CouchDB.
193
   * @param string $docId Document id.
194
   */
195
  public function validateAndEncodeDocId(&$docId) {
196
    if (!empty($docId))
197
      $docId = rawurlencode($docId);
198
    else
199
      throw new \InvalidArgumentException("\$docId must be a non-empty string.");
200
  }
201
202
  //!@}
203
204
205
  /** @name Miscellaneous Methods */
206
  //!@{
207
208
  /**
209
   * @brief Creates the admin user.
210
   * @todo Implement the method.
211
   */
212
  public function createAdminUser() {
213
    throw new \BadMethodCallException("The method `createAdminUser()` is not available.");
214
  }
215
216
217
  /**
218
   * @brief Restarts the server.
219
   * @attention Requires admin privileges.
220
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#restart
221
   * @bug [COUCHDB-947](https://issues.apache.org/jira/browse/COUCHDB-947)
222
   */
223
  public function restartServer() {
224
    $request = new Request(Request::POST_METHOD, "/_restart");
225
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
226
227
    // There is a bug in CouchDB, sometimes it doesn't return the 200 Status Code because it closes the connection
228
    // before the client has received the entire response. To avoid problems, we trap the exception and we go on.
229
    try {
230
      $this->send($request);
231
    }
232
    catch (\Exception $e) {
233
      if ($e->getCode() > 0)
234
        throw $e;
235
    }
236
  }
237
238
239
  /**
240
   * @brief Returns an object that contains MOTD, server and client and PHP versions.
241
   * @details The MOTD can be specified in CouchDB configuration files. This function returns more information
242
   * compared to the CouchDB standard REST call.
243
   * @return Info::ServerInfo
0 ignored issues
show
Documentation introduced by
The doc-type Info::ServerInfo could not be parsed: Unknown type name "Info::ServerInfo" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
244
   */
245
  public function getServerInfo() {
246
    $response = $this->send(new Request(Request::GET_METHOD, "/"));
247
    $info = $response->getBodyAsArray();
248
    return new Info\ServerInfo($info["couchdb"], $info["version"]);
249
  }
250
251
252
  /**
253
   * @brief Returns information about the Elephant on Couch client.
254
   * @return Info::ClientInfo
0 ignored issues
show
Documentation introduced by
The doc-type Info::ClientInfo could not be parsed: Unknown type name "Info::ClientInfo" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
255
   */
256
  public function getClientInfo() {
257
    return new Info\ClientInfo();
258
  }
259
260
261
  /**
262
   * @brief Returns the favicon.ico file.
263
   * @details The favicon is a part of the admin interface, but the handler for it is special as CouchDB tries to make
264
   * sure that the favicon is cached for one year. Returns a string that represents the icon.
265
   * @return string
266
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#favicon-ico
267
   */
268
  public function getFavicon() {
269
    $response = $this->send(new Request(Request::GET_METHOD, "/favicon.ico"));
270
271
    if ($response->getHeaderFieldValue(Request::CONTENT_TYPE_HF) == "image/x-icon")
272
      return $response->getBody();
273
    else
274
      throw new \InvalidArgumentException("Content-Type must be image/x-icon.");
275
  }
276
277
278
  /**
279
   * @brief Returns server statistics.
280
   * @return array An associative array
281
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#stats
282
   */
283
  public function getStats() {
284
    return $this->send(new Request(Request::GET_METHOD, "/_stats"))->getBodyAsArray();
285
  }
286
287
288
  /**
289
   * @brief Returns a list of all databases on this server.
290
   * @return array of string
291
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#all-dbs
292
   */
293
  public function getAllDbs() {
294
    return $this->send(new Request(Request::GET_METHOD, "/_all_dbs"))->getBodyAsArray();
295
  }
296
297
298
  /**
299
   * @brief Returns a list of all database events in the CouchDB instance.
300
   * @param DbUpdatesFeedOpts $opts Additional options.
301
   * @return Response
302
   * @attention Requires admin privileges.
303
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#db-updates
304
   */
305
  public function getDbUpdates(Opt\DbUpdatesFeedOpts $opts = NULL) {
306
    $request = new Request(Request::GET_METHOD, "/_db_updates");
307
308
    if (isset($opts))
309
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
310
311
    return $this->send($request)->getBodyAsArray();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->send($request)->getBodyAsArray(); (object|integer|double|string|array|boolean|null) is incompatible with the return type documented by EoC\Couch::getDbUpdates of type Surfer\Message\Response|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
312
  }
313
314
315
  /**
316
   * @brief Returns a list of running tasks.
317
   * @attention Requires admin privileges.
318
   * @return array An associative array
319
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#active-tasks
320
   */
321
  public function getActiveTasks() {
322
    return $this->send(new Request(Request::GET_METHOD, "/_active_tasks"))->getBodyAsArray();
323
  }
324
325
326
  /**
327
   * @brief Returns the tail of the server's log file.
328
   * @attention Requires admin privileges.
329
   * @param integer $bytes How many bytes to return from the end of the log file.
330
   * @return string
331
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#log
332
   */
333
  public function getLogTail($bytes = 1000) {
334
    if (is_int($bytes) and ($bytes > 0)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
335
      $request = new Request(Request::GET_METHOD, "/_log");
336
      $request->setQueryParam("bytes", $bytes);
337
      return $this->send($request)->getBody();
338
    }
339
    else
340
      throw new \InvalidArgumentException("\$bytes must be a positive integer.");
341
  }
342
343
344
  /**
345
   * @brief Returns a list of generated UUIDs.
346
   * @param integer $count Requested UUIDs number.
347
   * @return string|array If `$count == 1` (default) returns a string else returns an array of strings.
348
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#uuids
349
   */
350
  public function getUuids($count = 1) {
351
    if (is_int($count) and ($count > 0)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
352
      $request = new Request(Request::GET_METHOD, "/_uuids");
353
      $request->setQueryParam("count", $count);
354
355
      $response = $this->send($request);
356
357
      if ($count == 1) // We don't need to use === operator because, just above, we made a type checking.
358
        return $response->getBodyAsArray()['uuids'][0];
359
      else
360
        return $response->getBodyAsArray()['uuids'];
361
    }
362
    else
363
      throw new \InvalidArgumentException("\$count must be a positive integer.");
364
  }
365
366
  //!@}
367
368
369
  /** @name Server Configuration Methods */
370
  //!@{
371
372
  /**
373
   * @brief Returns the entire server configuration or a single section or a single configuration value of a section.
374
   * @param string $section Requested section.
375
   * @param string $key Requested key.
376
   * @return string|array An array with the configuration keys or a simple string in case of a single key.
377
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#get--_config
378
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#get--_config-section
379
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#config-section-key
380
   */
381
  public function getConfig($section = "", $key = "") {
382
    $path = "/_config";
383
384
    if (!empty($section)) {
385
      $path .= "/".$section;
386
387
      if (!empty($key))
388
        $path .= "/".$key;
389
    }
390
391
    return $this->send(new Request(Request::GET_METHOD, $path))->getBodyAsArray();
392
  }
393
394
395
  /**
396
   * @brief Sets a single configuration value in a given section to server configuration.
397
   * @param string $section The configuration section.
398
   * @param string $key The key.
399
   * @param string $value The value for the key.
400
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#put--_config-section-key
401
   */
402
  public function setConfigKey($section, $key, $value) {
403
    if (!is_string($section) or empty($section))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
404
      throw new \InvalidArgumentException("\$section must be a not empty string.");
405
406
    if (!is_string($key) or empty($key))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
407
      throw new \InvalidArgumentException("\$key must be a not empty string.");
408
409
    if (is_null($value))
410
      throw new \InvalidArgumentException("\$value cannot be null.");
411
412
    $request = new Request(Request::PUT_METHOD, "/_config/".$section."/".$key);
413
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
414
    $request->setBody(json_encode(utf8_encode($value)));
415
    $this->send($request);
416
  }
417
418
419
  /**
420
   * @brief Deletes a single configuration value from a given section in server configuration.
421
   * @param string $section The configuration section.
422
   * @param string $key The key.
423
   * @see http://docs.couchdb.org/en/latest/api/configuration.html#delete-config-section-key
424
   */
425
  public function deleteConfigKey($section, $key) {
426
    if (!is_string($section) or empty($section))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
427
      throw new \InvalidArgumentException("\$section must be a not empty string.");
428
429
    if (!is_string($key) or empty($key))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
430
      throw new \InvalidArgumentException("\$key must be a not empty string.");
431
432
    $this->send(new Request(Request::DELETE_METHOD, "/_config/".$section."/".$key));
433
  }
434
435
  //!@}
436
437
438
  /** @name Cookie Authentication */
439
  //!@{
440
441
  /**
442
   * @brief Returns cookie based login user information.
443
   * @return Response
444
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#get--_session
445
   */
446
  public function getSession() {
447
    return $this->send(new Request(Request::GET_METHOD, "/_session"));
448
  }
449
450
451
  /**
452
   * @brief Makes cookie based user login.
453
   * @return Response
454
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#post--_session
455
   */
456
  public function setSession($userName, $password) {
457
    if (!is_string($userName) or empty($userName))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
458
      throw new \InvalidArgumentException("\$userName must be a not empty string.");
459
460
    if (!is_string($password) or empty($password))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
461
      throw new \InvalidArgumentException("\$password must be a not empty string.");
462
463
    $request = new Request(Request::POST_METHOD, "/_session");
464
465
    $request->setHeaderField(Request::X_COUCHDB_WWW_AUTHENTICATE_HF, "Cookie");
466
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/x-www-form-urlencoded");
467
468
    $request->setQueryParam("name", $userName);
469
    $request->setQueryParam("password", $password);
470
471
    return $this->send($request);
472
  }
473
474
475
  /**
476
   * @brief Makes user logout.
477
   * @return Response
478
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#delete--_session
479
   */
480
  public function deleteSession() {
481
    return $this->send(new Request(Request::DELETE_METHOD, "/_session"));
482
  }
483
484
  //!@}
485
486
487
  /** @name OAuth Authentication */
488
  //! @see http://docs.couchdb.org/en/latest/api/server/authn.html#oauth-authentication
489
  //!@{
490
491
  /**
492
   * @brief
493
   * @todo To be implemented and documented.
494
   */
495
  public function getAccessToken() {
496
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/access_token"));
497
  }
498
499
500
  /**
501
   * @brief
502
   * @todo To be implemented and documented.
503
   */
504
  public function getAuthorize() {
505
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/authorize"));
506
  }
507
508
509
  /**
510
   * @brief
511
   * @todo To be implemented and documented.
512
   */
513
  public function setAuthorize() {
514
    return $this->send(new Request(Request::POST_METHOD, "/_oauth/authorize"));
515
  }
516
517
518
  /**
519
   * @brief
520
   * @todo To be implemented and documented.
521
   */
522
  public function requestToken() {
523
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/request_token"));
524
  }
525
526
  //!@}
527
528
529
  /**
530
   * @brief Creates a new database and selects it.
531
   * @param string $name The database name. A database must be named with all lowercase letters (a-z),
532
   * digits (0-9), or any of the _$()+-/ characters and must end with a slash in the URL. The name has to start with a
533
   * lowercase letter (a-z).
534
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#put--db
535
   */
536
  public function createDb($name) {
537
    # \A[a-z][a-z\d_$()+-/]++\z
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
538
    #
539
    # Assert position at the beginning of the string «\A»
540
    # Match a single character in the range between “a” and “z” «[a-z]»
541
    # Match a single character present in the list below «[a-z\d_$()+-/]++»
542
    #    Between one and unlimited times, as many times as possible, without giving back (possessive) «++»
543
    #    A character in the range between “a” and “z” «a-z»
544
    #    A single digit 0..9 «\d»
545
    #    One of the characters “_$()” «_$()»
546
    #    A character in the range between “+” and “/” «+-/»
547
    # Assert position at the very end of the string «\z»
548
    if (preg_match('%\A[a-z][a-z\d_$()+-/]++\z%', $name) === FALSE)
549
      throw new \InvalidArgumentException("Invalid database name.");
550
551
    $this->send(new Request(Request::PUT_METHOD, "/".rawurlencode($this->prefix.$name)."/"));
552
  }
553
554
555
  /**
556
   * @brief Deletes an existing database.
557
   * @param string $name The database name.
558
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#delete--db
559
   */
560
  public function deleteDb($name) {
561
    $this->send(new Request(Request::DELETE_METHOD, "/".rawurlencode($this->prefix.$name)));
562
  }
563
564
565
  /**
566
   * @brief Returns information about the selected database.
567
   * @param string $name The database name.
568
   * @return Info::DbInfo
0 ignored issues
show
Documentation introduced by
The doc-type Info::DbInfo could not be parsed: Unknown type name "Info::DbInfo" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
569
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#get--db
570
   */
571
  public function getDbInfo($name) {
572
    return new Info\Dbinfo($this->send(new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$name)."/"))->getBodyAsArray());
573
  }
574
575
576
  /**
577
   * @brief Obtains a list of the changes made to the database. This can be used to monitor for update and modifications
578
   * to the database for post processing or synchronization.
579
   * @param string $name The database name.
580
   * @param ChangesFeedOpts $opts Additional options.
581
   * @return Response
582
   * @see http://docs.couchdb.org/en/latest/api/database/changes.html
583
   */
584
  public function getDbChanges($name, Opt\ChangesFeedOpts $opts = NULL) {
585
    $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$name)."/_changes");
586
587
    if (isset($opts))
588
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
589
590
    return $this->send($request);
591
  }
592
593
594
  /**
595
   * @brief Starts a compaction for the current selected database.
596
   * @details Writes a new version of the database file, removing any unused sections from the new version during write.
597
   * Because a new file is temporary created for this purpose, you will need twice the current storage space of the
598
   * specified database in order for the compaction routine to complete.\n
599
   * Removes old revisions of documents from the database, up to the per-database limit specified by the `_revs_limit`
600
   * database setting.\n
601
   * Compaction can only be requested on an individual database; you cannot compact all the databases for a CouchDB
602
   * instance.\n
603
   * The compaction process runs as a background process. You can determine if the compaction process is operating on a
604
   * database by obtaining the database meta information, the `compact_running` value of the returned database
605
   * structure will be set to true. You can also obtain a list of running processes to determine whether compaction is
606
   * currently running, using getActiveTasks().
607
   * @param string $name The database name.
608
   * @attention Requires admin privileges.
609
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html
610
   */
611 View Code Duplication
  public function compactDb($name) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
612
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$name)."/_compact");
613
614
    // A POST method requires Content-Type header.
615
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
616
617
    $this->send($request);
618
  }
619
620
621
  /**
622
   * @brief Compacts the specified view.
623
   * @details If you have very large views or are tight on space, you might consider compaction as well. To run compact
624
   * for a particular view on a particular database, use this method.
625
   * @param string $dbName The database name.
626
   * @param string $designDocName Name of the design document where is stored the view.
627
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-compact-design-doc
628
   */
629
  public function compactView($dbName, $designDocName) {
630
    $path = "/".rawurlencode($this->prefix.$dbName)."/_compact/".$designDocName;
631
632
    $request = new Request(Request::POST_METHOD, $path);
633
634
    // A POST method requires Content-Type header.
635
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
636
637
    $this->send($request);
638
  }
639
640
641
  /**
642
   * @brief Removes all outdated view indexes.
643
   * @details Old views files remain on disk until you explicitly run cleanup.
644
   * @param string $dbName The database name.
645
   * @attention Requires admin privileges.
646
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-view-cleanup
647
   */
648 View Code Duplication
  public function cleanupViews($dbName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
649
    $request =  new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_view_cleanup");
650
651
    // A POST method requires Content-Type header.
652
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
653
654
    $this->send($request);
655
  }
656
657
658
  /**
659
   * @brief Makes sure all uncommited database changes are written and synchronized to the disk.
660
   * @details Default CouchDB configuration use delayed commit to improve performances. So CouchDB allows operations to
661
   * be run against the disk without an explicit fsync after each operation. Synchronization takes time (the disk may
662
   * have to seek, on some platforms the hard disk cache buffer is flushed, etc.), so requiring an fsync for each update
663
   * deeply limits CouchDB's performance for non-bulk writers.\n
664
   * Delayed commit should be left set to true in the configuration settings. Anyway, you can still tell CouchDB to make
665
   * an fsync, calling the this method.
666
   * @param string $dbName The database name.
667
   * @return string The timestamp of the last time the database file was opened.
668
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-ensure-full-commit
669
   */
670 View Code Duplication
  public function ensureFullCommit($dbName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
671
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_ensure_full_commit");
672
673
    // A POST method requires Content-Type header.
674
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
675
676
    return $this->send($request)->getBodyAsArray()["instance_start_time"];
677
  }
678
679
  //!@}
680
681
682
  /**
683
   * @name Security Methods
684
   * @details The security object consists of two compulsory elements, admins and members, which are used to specify
685
   * the list of users and/or roles that have admin and members rights to the database respectively:
686
   * - members: they can read all types of documents from the DB, and they can write (and edit) documents to the
687
   * database except for design documents.
688
   * - admins: they have all the privileges of members plus the privileges: write (and edit) design documents, add/remove
689
   * database admins and members, set the database revisions limit and execute temporary views against the database.
690
   * They can not create a database nor delete a database.
691
   *
692
   * Both members and admins objects are contains two array-typed fields:
693
   * - users: List of CouchDB user names
694
   * - roles: List of users roles
695
   *
696
   * Any other additional fields in the security object are optional. The entire security object is made available to
697
   * validation and other internal functions so that the database can control and limit functionality.
698
   * If both the names and roles fields of either the admins or members properties are empty arrays, it means the
699
   * database has no admins or members.\n
700
   * Having no admins, only server admins (with the reserved _admin role) are able to update design document and make
701
   * other admin level changes.\n
702
   * Having no members, any user can write regular documents (any non-design document) and read documents from the database.
703
   * If there are any member names or roles defined for a database, then only authenticated users having a matching name
704
   * or role are allowed to read documents from the database.
705
   */
706
  //!@{
707
708
  /**
709
   * @brief Returns the special security object for the database.
710
   * @param string $dbName The database name.
711
   * @return string A JSON object.
712
   * @see http://docs.couchdb.org/en/latest/api/database/security.html#get--db-_security
713
   */
714
  public function getSecurityObj($dbName) {
715
    return $this->send(new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_security"));
716
  }
717
718
719
  /**
720
   * @brief Sets the special security object for the database.
721
   * @param string $dbName The database name.
722
   * @see http://docs.couchdb.org/en/latest/api/database/security.html#put--db-_security
723
   */
724
  public function setSecurityObj($dbName) {
725
    return $this->send(new Request(Request::PUT_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_security"));
726
  }
727
728
  //!@}
729
730
731
  /**
732
   * @name Database Replication Methods
733
   * @details The replication is an incremental one way process involving two databases (a source and a destination).
734
   * The aim of the replication is that at the end of the process, all active documents on the source database are also
735
   * in the destination database and all documents that were deleted in the source databases are also deleted (if
736
   * exists) on the destination database.\n
737
   * The replication process only copies the last revision of a document, so all previous revisions that were only on
738
   * the source database are not copied to the destination database.\n
739
   * Changes on the master will not automatically replicate to the slaves. To make replication continuous, you must set
740
   * `$continuous = true`. At this time, CouchDB does not remember continuous replications over a server restart.
741
   * Specifying a local source database and a remote target database is called push replication and a remote source and
742
   * local target is called pull replication. As of CouchDB 0.9, pull replication is a lot more efficient and resistant
743
   * to errors, and it is suggested that you use pull replication in most cases, especially if your documents are large
744
   * or you have large attachments.
745
   */
746
  //!@{
747
748
  /**
749
   * @brief Starts replication.
750
   @code
751
     startReplication("sourcedbname", "http://example.org/targetdbname", TRUE, TRUE);
752
   @endcode
753
   * @param string $sourceDbUrl Source database URL.
754
   * @param string $targetDbUrl Destination database URL.
755
   * @param string $proxy (optional) Specify the optional proxy used to perform the replication.
756
   * @param bool $createTargetDb (optional) The target database has to exist and is not implicitly created. You
757
   * can force the creation setting `$createTargetDb = true`.
758
   * @param bool $continuous (optional) When `true` CouchDB will not stop after replicating all missing documents
759
   * from the source to the target.\n
760
   * At the time of writing, CouchDB doesn't remember continuous replications over a server restart. For the time being,
761
   * you are required to trigger them again when you restart CouchDB. In the future, CouchDB will allow you to define
762
   * permanent continuous replications that survive a server restart without you having to do anything.
763
   * @param string|array $filter (optional) Name of a filter function that can choose which revisions get replicated.
764
   * You can also provide an array of document identifiers; if given, only these documents will be replicated.
765
   * @param ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
766
   * docs, etc.
767
   * @return Response
768
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#post--_replicate
769
   */
770
  public function startReplication($sourceDbUrl, $targetDbUrl, $proxy = NULL, $createTargetDb = TRUE,
771
                                     $continuous = FALSE, $filter = NULL, Opt\ViewQueryOpts $opts = NULL) {
772
    // Sets source and target databases.
773
    if (is_string($sourceDbUrl) && !empty($sourceDbUrl) &&
774
      is_string($targetDbUrl) && !empty($targetDbUrl)) {
775
      $body["source"] = $sourceDbUrl;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
776
      $body["target"] = $targetDbUrl;
777
    }
778
    else
779
      throw new \InvalidArgumentException("\$source_db_url and \$target_db_url must be non-empty strings.");
780
781
    if (!is_bool($continuous))
782
      throw new \InvalidArgumentException("\$continuous must be a bool.");
783
    elseif ($continuous)
784
      $body["continuous"] = $continuous;
785
786
    // Uses the specified proxy if any set.
787
    if (isset($proxy))
788
      $body["proxy"] = $proxy;
789
790
    // create_target option
791
    if (!is_bool($createTargetDb))
792
      throw new \InvalidArgumentException("\$createTargetDb must be a bool.");
793
    elseif ($createTargetDb)
794
      $body["create_target"] = $createTargetDb;
795
796
    if (!empty($filter)) {
797
      if (is_string($filter)) // filter option
798
        $body["filter"] = $filter;
799
      elseif (is_array($filter)) // doc_ids option
800
        $body["doc_ids"] = array_values($filter);
801
      else
802
        throw new \InvalidArgumentException("\$filter must be a string or an array.");
803
    }
804
805
    // queryParams option
806
    if (!is_null($opts)) {
807
      if ($opts instanceof Opt\ViewQueryOpts)
808
        $body["query_params"] = get_object_vars($opts);
809
      else
810
        throw new \InvalidArgumentException("\$queryParams must be an instance of ViewQueryOpts class.");
811
    }
812
813
    $request = new Request(Request::POST_METHOD, "/_replicate");
814
    $request->setBody($body);
0 ignored issues
show
Documentation introduced by
$body is of type array<string,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
815
816
    return $this->send($request);
817
  }
818
819
820
  /**
821
   * @brief Cancels replication.
822
   * @param string $replicationId The replication ID.
823
   * @return Response
824
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#canceling-continuous-replication
825
   */
826
  public function stopReplication($replicationId) {
827
    if (is_null($replicationId))
828
      throw new \InvalidArgumentException("You must provide a replication id.");
829
830
    $body["replication_id"] = $replicationId;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
831
    $body["cancel"] = TRUE;
832
833
    $request = new Request(Request::POST_METHOD, "/_replicate");
834
    $request->setBody($body);
0 ignored issues
show
Documentation introduced by
$body is of type array<string,string|boolean,{"cancel":"boolean"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
835
836
    return $this->send($request);
837
  }
838
839
840
  /**
841
   * @brief
842
   * @details
843
   * @see http://wiki.apache.org/couchdb/Replication#Replicator_database
844
   * @see http://docs.couchbase.org/couchdb-release-1.1/index.html#couchb-release-1.1-replicatordb
845
   * @see https://gist.github.com/832610
846
   * @todo To be implemented and documented.
847
   */
848
  public function getReplicator() {
849
    throw new \BadMethodCallException("The method `getReplicator()` is not available.");
850
  }
851
852
  //!@}
853
854
855
  /** @name Query Documents Methods
856
   * @details Queries are the primary mechanism for retrieving a result set from a view. The result of a query is an
857
   * instance of `QueryResult`, a class that implements the [IteratorAggregate](http://php.net/manual/en/class.iteratoraggregate.php),
858
   * [Countable](http://php.net/manual/en/class.countable.php) and [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php)
859
   * interfaces, so you can use the result set as an array.
860
   */
861
  //!@{
862
863
  /**
864
   * @brief Returns a built-in view of all documents in this database. If keys are specified returns only certain rows.
865
   * @param string $dbName The database name.
866
   * @param array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
867
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
868
   * multi-document-fetch feature.
869
   * @param ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
870
   * docs, etc.
871
   * @param ChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
872
   * @return Result::QueryResult The result of the query.
0 ignored issues
show
Documentation introduced by
The doc-type Result::QueryResult could not be parsed: Unknown type name "Result::QueryResult" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
873
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#get--db-_all_docs
874
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_all_docs
875
   */
876
  public function queryAllDocs($dbName, array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, Hook\IChunkHook $chunkHook = NULL) {
877
    if (is_null($keys))
878
      $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_all_docs");
879
    else {
880
      $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_all_docs");
881
      $request->setBody(json_encode(['keys' => $keys]));
882
    }
883
884
    if (isset($opts))
885
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
886
887
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
888
889
    return new Result\QueryResult($result);
890
  }
891
892
893
  /**
894
   * @brief Executes the given view and returns the result.
895
   * @param string $dbName The database name.
896
   * @param string $designDocName The design document's name.
897
   * @param string $viewName The view's name.
898
   * @param array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
899
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
900
   * multi-document-fetch feature.
901
   * @param ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
902
   * docs, etc.
903
   * @param IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
904
   * @return Result::QueryResult The result of the query.
0 ignored issues
show
Documentation introduced by
The doc-type Result::QueryResult could not be parsed: Unknown type name "Result::QueryResult" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
905
   * @attention Multiple keys request to a reduce function only supports `group=true` (identical to `group-level=exact`,
906
   * but it doesn't support `group_level` > 0.
907
   * CouchDB raises "Multi-key fetchs for reduce view must include `group=true`" error, in case you use `group_level`.
908
   * @see http://docs.couchdb.org/en/latest/api/ddoc/views.html#get--db-_design-ddoc-_view-view
909
   * @see http://docs.couchdb.org/en/latest/api/ddoc/views.html#post--db-_design-ddoc-_view-view
910
   */
911
  public function queryView($dbName, $designDocName, $viewName, array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, Hook\IChunkHook $chunkHook = NULL) {
912
    $this->validateAndEncodeDocId($designDocName);
913
914
    if (empty($viewName))
915
      throw new \InvalidArgumentException("You must provide a valid \$viewName.");
916
917
    if (empty($keys))
918
      $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_design/".$designDocName."/_view/".$viewName);
919
    else {
920
      $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_design/".$designDocName."/_view/".$viewName);
921
      $request->setBody(json_encode(['keys' => $keys]));
922
    }
923
924
    // Required since CouchDB 2.0.
925
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
926
927 View Code Duplication
    if (isset($opts)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
928
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
929
      $includeMissingKeys = $opts->issetIncludeMissingKeys();
930
    }
931
    else
932
      $includeMissingKeys = FALSE;
933
934
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
935
936
    if ($includeMissingKeys)
937
      $this->addMissingRows($keys, $result['rows']);
0 ignored issues
show
Bug introduced by
It seems like $keys defined by parameter $keys on line 911 can also be of type null; however, EoC\Couch::addMissingRows() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
938
939
    return new Result\QueryResult($result);
940
  }
941
942
943
  /**
944
   * @brief Executes the given view, both map and reduce functions, for all documents and returns the result.
945
   * @details Map and Reduce functions are provided by the programmer.
946
   * @attention Requires admin privileges.
947
   * @param string $dbName The database name.
948
   * @param string $mapFn The map function.
949
   * @param string $reduceFn The reduce function.
950
   * @param array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
951
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
952
   * multi-document-fetch feature.
953
   * @param ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
954
   * docs, etc.
955
   * @param string $language The language used to implement the map and reduce functions.
956
   * @param IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
957
   * @return Result::QueryResult The result of the query.
0 ignored issues
show
Documentation introduced by
The doc-type Result::QueryResult could not be parsed: Unknown type name "Result::QueryResult" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
958
   * @see http://docs.couchdb.org/en/latest/api/database/temp-views.html#post--db-_temp_view
959
   */
960
  public function queryTempView($dbName, $mapFn, $reduceFn = "", array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, $language = 'php', Hook\IChunkHook $chunkHook = NULL) {
961
    $handler = new Handler\ViewHandler('temp');
962
    $handler->language = $language;
963
    $handler->mapFn = $mapFn;
964
    if (!empty($reduce))
0 ignored issues
show
Bug introduced by
The variable $reduce does not exist. Did you mean $reduceFn?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
965
      $handler->reduceFn = $reduceFn;
966
967
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_temp_view");
968
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
969
970
    $array = $handler->asArray();
971
972
    if (!empty($keys))
973
      $array['keys'] = $keys;
974
975
    $request->setBody(json_encode($array));
976
977 View Code Duplication
    if (isset($opts)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
978
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
979
      $includeMissingKeys = $opts->issetIncludeMissingKeys();
980
    }
981
    else
982
      $includeMissingKeys = FALSE;
983
984
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
985
986
    if ($includeMissingKeys)
987
      $this->addMissingRows($keys, $result['rows']);
0 ignored issues
show
Bug introduced by
It seems like $keys defined by parameter $keys on line 960 can also be of type null; however, EoC\Couch::addMissingRows() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
988
989
    return new Result\QueryResult($result);
990
  }
991
992
  //!@}
993
994
995
  /** @name Revisions Management Methods */
996
  //!@{
997
998
  /**
999
   * @brief Given a list of document revisions, returns the document revisions that do not exist in the database.
1000
   * @param string $dbName The database name.
1001
   * @return Response
1002
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#db-missing-revs
1003
   */
1004
  public function getMissingRevs($dbName) {
1005
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_missing_revs");
1006
1007
    return $this->send($request);
1008
  }
1009
1010
1011
  /**
1012
   * @brief Given a list of document revisions, returns differences between the given revisions and ones that are in
1013
   * the database.
1014
   * @param string $dbName The database name.
1015
   * @return Response
1016
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#db-revs-diff
1017
   */
1018
  public function getRevsDiff($dbName) {
1019
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_missing_revs");
1020
1021
    return $this->send($request);
1022
  }
1023
1024
1025
  /**
1026
   * @brief Gets the limit of historical revisions to store for a single document in the database.
1027
   * @param string $dbName The database name.
1028
   * @return integer The maximum number of document revisions that will be tracked by CouchDB.
1029
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#get--db-_revs_limit
1030
   */
1031
  public function getRevsLimit($dbName) {
1032
    $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_revs_limit");
1033
1034
    return (integer)$this->send($request)->getBody();
1035
  }
1036
1037
1038
  /**
1039
   * @brief Sets the limit of historical revisions for a single document in the database.
1040
   * @param string $dbName The database name.
1041
   * @param integer $revsLimit (optional) Maximum number historical revisions for a single document in the database.
1042
   * Must be a positive integer.
1043
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#put--db-_revs_limit
1044
   */
1045
  public function setRevsLimit($dbName, $revsLimit = self::REVS_LIMIT) {
1046
    if (!is_int($revsLimit) or ($revsLimit <= 0))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1047
      throw new \InvalidArgumentException("\$revsLimit must be a positive integer.");
1048
1049
    $request = new Request(Request::PUT_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_revs_limit");
1050
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1051
    $request->setBody(json_encode($revsLimit));
1052
1053
    $this->send($request);
1054
  }
1055
1056
  //!@}
1057
1058
1059
  /** @name Documents Management Methods */
1060
  //!@{
1061
1062
  /**
1063
   * @brief Returns the document's entity tag, that can be used for caching or optimistic concurrency control purposes.
1064
   * The ETag Header is simply the document's revision in quotes.
1065
   * @details This function is not available for special documents. To get information about a design document, use
1066
   * the special function getDesignDocInfo().
1067
   * @param string $dbName The database name.
1068
   * @param string $docId The document's identifier.
1069
   * @return string The document's revision.
1070
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#db-doc
1071
   * @bug CouchDB ETag is
1072
   */
1073 View Code Duplication
  public function getDocEtag($dbName, $docId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1074
    $this->validateAndEncodeDocId($docId);
1075
1076
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$docId;
1077
1078
    $request = new Request(Request::HEAD_METHOD, $path);
1079
1080
    // CouchDB ETag is included between quotation marks.
1081
    return trim($this->send($request)->getHeaderFieldValue(Response::ETAG_HF), '"');
1082
  }
1083
1084
1085
  /**
1086
   * @brief Returns the latest revision of the document.
1087
   * @details Since CouchDB uses different paths to store special documents, you must provide the document type for
1088
   * design and local documents.
1089
   * @param string $dbName The database name.
1090
   * @param string $docId The document's identifier.
1091
   * @param string $path The document's path.
1092
   * @param string $rev (optional) The document's revision.
1093
   * @param DocOpts $opts Query options to get additional document information, like conflicts, attachments, etc.
1094
   * @return object|Response An instance of Doc, LocalDoc, DesignDoc or any subclass of Doc.
1095
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
1096
   */
1097
  public function getDoc($dbName, $path, $docId, $rev = NULL, Opt\DocOpts $opts = NULL) {
1098
    $this->validateDocPath($path);
1099
    $this->validateAndEncodeDocId($docId);
1100
1101
    $requestPath = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId;
1102
1103
    $request = new Request(Request::GET_METHOD, $requestPath);
1104
1105
    // Retrieves the specific revision of the document.
1106
    if (!empty($rev))
1107
      $request->setQueryParam("rev", (string)$rev);
1108
1109
    // If there are any options, add them to the request.
1110
    if (isset($opts)) {
1111
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
1112
      $ignoreClass = $opts->issetIgnoreClass();
1113
    }
1114
    else
1115
      $ignoreClass = FALSE;
1116
1117
    $response = $this->send($request);
1118
    $body = $response->getBodyAsArray();
1119
1120
    // We use `class` metadata to store an instance of a specialized document class. We can have Article and Book classes,
1121
    // both derived from Doc, with special properties and methods. Instead to convert them, we store the class name in a
1122
    // special attribute called 'class' within the others metadata. So, once we retrieve the document, the client creates
1123
    // an instance of the class we provided when we saved the document; we never need to convert it.
1124
    if (!$ignoreClass && isset($body['class'])) { // Special document class inherited from Doc or LocalDoc.
1125
      $class = "\\".$body['class'];
1126
      $doc = new $class;
1127
    }
1128
    elseif ($path == self::DESIGN_DOC_PATH)
1129
      $doc = new Doc\DesignDoc;
1130
    else
1131
      $doc = NULL;
1132
1133
    if (is_object($doc)) {
1134
      $doc->assignArray($body);
1135
      return $doc;
1136
    }
1137
    else
1138
      return $response;
1139
  }
1140
1141
1142
  /**
1143
   * @brief Inserts or updates a document into the selected database.
1144
   * @details Whether the `$doc` has an id we use a different HTTP method. Using POST CouchDB generates an id for the doc,
1145
   * using PUT instead we need to specify one. We can still use the function getUuids() to ask CouchDB for some ids.
1146
   * This is an internal detail. You have only to know that CouchDB can generate the document id for you.
1147
   * @param string $dbName The database name.
1148
   * @param Doc $doc The document you want insert or update.
1149
   * @param bool $batchMode (optional) You can write documents to the database at a higher rate by using the batch
1150
   * option. This collects document writes together in memory (on a user-by-user basis) before they are committed to
1151
   * disk. This increases the risk of the documents not being stored in the event of a failure, since the documents are
1152
   * not written to disk immediately.
1153
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid
1154
   */
1155
  public function saveDoc($dbName, Doc\IDoc $doc, $batchMode = FALSE) {
1156
    // Whether the document has an id we use a different HTTP method. Using POST CouchDB generates an id for the doc
1157
    // using PUT we need to specify one. We can still use the function getUuids() to ask CouchDB for some ids.
1158
    if (!$doc->issetId())
1159
      $doc->setId(Generator\UUID::generate(Generator\UUID::UUID_RANDOM, Generator\UUID::FMT_STRING));
1160
1161
    $this->setDocInfo($doc);
1162
1163
    // We never use the POST method.
1164
    $method = Request::PUT_METHOD;
1165
1166
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$doc->getPath().$doc->getId();
1167
1168
    $request = new Request($method, $path);
1169
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1170
    $request->setBody($doc->asJson());
1171
1172
    // Enables batch mode.
1173
    if ($batchMode)
1174
      $request->setQueryParam("batch", "ok");
1175
1176
    $this->send($request);
1177
  }
1178
1179
1180
  /**
1181
   * @brief Deletes the specified document.
1182
   * @details To delete a document you must provide the document identifier and the revision number.
1183
   * @param string $dbName The database name.
1184
   * @param string $docId The document's identifier you want delete.
1185
   * @param string $rev The document's revision number you want delete.
1186
   * @param string $path The document path.
1187
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#delete--db-docid
1188
   */
1189
  public function deleteDoc($dbName, $path, $docId, $rev) {
1190
    $this->validateDocPath($path);
1191
    $this->validateAndEncodeDocId($docId);
1192
1193
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId;
1194
1195
    $request = new Request(Request::DELETE_METHOD, $path);
1196
    $request->setQueryParam("rev", (string)$rev);
1197
1198
    // We could use another technique to send the revision number. Here just for documentation.
1199
    // $request->setHeader(Request::IF_MATCH_HEADER, (string)$rev);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1200
1201
    $this->send($request);
1202
  }
1203
1204
1205
  /**
1206
   * @brief Makes a duplicate of the specified document. If you want to overwrite an existing document, you need to
1207
   * specify the target document's revision with a `$rev` parameter.
1208
   * @details If you want copy a special document you must specify his type.
1209
   * @param string $dbName The database name.
1210
   * @param string $sourceDocId The source document id.
1211
   * @param string $targetDocId The destination document id.
1212
   * @param string $rev Needed when you want override an existent document.
1213
   * @param string $path The document path.
1214
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#copy--db-docid
1215
   */
1216
  public function copyDoc($dbName, $path, $sourceDocId, $targetDocId, $rev = NULL) {
1217
    $this->validateDocPath($path);
1218
1219
    $this->validateAndEncodeDocId($sourceDocId);
1220
    $this->validateAndEncodeDocId($targetDocId);
1221
1222
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$sourceDocId;
1223
1224
    // This request uses the special method COPY.
1225
    $request = new Request(Request::COPY_METHOD, $path);
1226
1227
    if (empty($rev))
1228
      $request->setHeaderField(Request::DESTINATION_HF, $targetDocId);
1229
    else
1230
      $request->setHeaderField(Request::DESTINATION_HF, $targetDocId."?rev=".(string)$rev);
1231
1232
    $this->send($request);
1233
  }
1234
1235
1236
  /**
1237
   * @brief The purge operation removes the references to the deleted documents from the database.
1238
   * @details A database purge permanently removes the references to deleted documents from the database. Deleting a
1239
   * document within CouchDB does not actually remove the document from the database, instead, the document is marked
1240
   * as deleted (and a new revision is created). This is to ensure that deleted documents are replicated to other
1241
   * databases as having been deleted. This also means that you can check the status of a document and identify that
1242
   * the document has been deleted.\n
1243
   * The purging of old documents is not replicated to other databases. If you are replicating between databases and
1244
   * have deleted a large number of documents you should run purge on each database.\n
1245
   * Purging documents does not remove the space used by them on disk. To reclaim disk space, you should run compactDb().\n
1246
   * @param string $dbName The database name.
1247
   * @param array $refs An array of references used to identify documents and revisions to delete. The array must
1248
   * contains instances of the DocRef class.
1249
   * @return Response
1250
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#post--db-_purge
1251
   * @see http://wiki.apache.org/couchdb/Purge_Documents
1252
   */
1253
  public function purgeDocs($dbName, array $refs) {
1254
    $path = "/".rawurlencode($this->prefix.$dbName)."/_purge";
1255
1256
    $request = new Request(Request::POST_METHOD, $path);
1257
1258
    $purge = [];
1259
    foreach ($refs as $ref)
1260
      $purge[] = $ref->asArray();
1261
1262
    $request->setBody(json_encode($purge));
1263
1264
    return $this->send($request);
1265
  }
1266
1267
1268
  /**
1269
   * @brief Inserts, updates and deletes documents in a bulk.
1270
   * @details Documents that are updated or deleted must contain the `rev` number. To delete a document, you should
1271
   * call delete() method on your document. When creating new documents the document ID is optional. For updating existing
1272
   * documents, you must provide the document ID and revision.
1273
   * @param string $dbName The database name.
1274
   * @param array $docs An array of documents you want insert, delete or update.
1275
   * @param bool $fullCommit (optional) Makes sure all uncommited database changes are written and synchronized
1276
   * to the disk immediately.
1277
   * @param bool $allOrNothing (optional) In all-or-nothing mode, either all documents are written to the database,
1278
   * or no documents are written to the database, in the event of a system failure during commit.\n
1279
   * You can ask CouchDB to check that all the documents pass your validation functions. If even one fails, none of the
1280
   * documents are written. If all documents pass validation, then all documents will be updated, even if that introduces
1281
   * a conflict for some or all of the documents.
1282
   * @param bool $newEdits (optional) When `false` CouchDB pushes existing revisions instead of creating
1283
   * new ones. The response will not include entries for any of the successful revisions (since their rev IDs are
1284
   * already known to the sender), only for the ones that had errors. Also, the conflict error will never appear,
1285
   * since in this mode conflicts are allowed.
1286
   * @return Response
1287
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#db-bulk-docs
1288
   * @see http://docs.couchdb.org/en/latest/json-structure.html#bulk-documents
1289
   * @see http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
1290
   */
1291
  public function performBulkOperations($dbName, array $docs, $fullCommit = FALSE, $allOrNothing = FALSE, $newEdits = TRUE) {
1292
    if (count($docs) == 0)
1293
      throw new \InvalidArgumentException("The \$docs array cannot be empty.");
1294
    else
1295
      $operations = [];
1296
1297
    $path = "/".rawurlencode($this->prefix.$dbName)."/_bulk_docs";
1298
1299
    $request = new Request(Request::POST_METHOD, $path);
1300
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1301
1302
    if ($fullCommit)
1303
      $request->setHeaderField(Request::X_COUCHDB_FULL_COMMIT_HF, "full_commit");
1304
    else
1305
      $request->setHeaderField(Request::X_COUCHDB_FULL_COMMIT_HF, "delay_commit");
1306
1307
    if ($allOrNothing)
1308
      $operations['all_or_nothing'] = 'true';
1309
1310
    if (!$newEdits)
1311
      $operations['new_edits'] = 'false';
1312
1313
    foreach ($docs as $doc) {
1314
      $this->setDocInfo($doc);
1315
      $operations['docs'][] = $doc->asArray();
1316
    }
1317
1318
    $request->setBody(json_encode($operations));
1319
1320
    return $this->send($request);
1321
  }
1322
1323
  //!@}
1324
1325
1326
  /** @name Attachments Management Methods */
1327
  //!@{
1328
1329
1330
  /**
1331
   * @brief Returns the minimal amount of information about the specified attachment.
1332
   * @param string $dbName The database name.
1333
   * @param string $fileName The attachment's name.
1334
   * @param string $docId The document's identifier.
1335
   * @param string $path The document's path.
1336
   * @param string $rev (optional) The document's revision.
1337
   * @return string The document's revision.
1338
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#db-doc-attachment
1339
   */
1340 View Code Duplication
  public function getAttachmentInfo($dbName, $fileName, $path, $docId, $rev = NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1341
    $this->validateDocPath($path, TRUE);
1342
    $this->validateAndEncodeDocId($docId);
1343
1344
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".$fileName;
1345
1346
    $request = new Request(Request::HEAD_METHOD, $path);
1347
1348
    // In case we want retrieve a specific document revision.
1349
    if (!empty($rev))
1350
      $request->setQueryParam("rev", (string)$rev);
1351
1352
    return $this->send($request);
1353
  }
1354
1355
1356
  /**
1357
   * @brief Retrieves the attachment from the specified document.
1358
   * @param string $dbName The database name.
1359
   * @param string $fileName The attachment's name.
1360
   * @param string $docId The document's identifier.
1361
   * @param string $path The document's path.
1362
   * @param string $rev (optional) The document's revision.
1363
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#get--db-docid-attname
1364
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#http-range-requests
1365
   * @todo Add support for Range request, using header "Range: bytes=0-12".
1366
   */
1367 View Code Duplication
  public function getAttachment($dbName, $fileName, $path, $docId, $rev = NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1368
    $this->validateDocPath($path, TRUE);
1369
    $this->validateAndEncodeDocId($docId);
1370
1371
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".$fileName;
1372
1373
    $request = new Request(Request::GET_METHOD, $path);
1374
1375
    // In case we want retrieve a specific document revision.
1376
    if (!empty($rev))
1377
      $request->setQueryParam("rev", (string)$rev);
1378
1379
    return $this->send($request)->getBody();
1380
  }
1381
1382
1383
  /**
1384
   * @brief Inserts or updates an attachment to the specified document.
1385
   * @param string $dbName The database name.
1386
   * @param string $fileName The attachment's name.
1387
   * @param string $docId The document's identifier.
1388
   * @param string $path The document's path.
1389
   * @param string $rev (optional) The document's revision.
1390
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#put--db-docid-attname
1391
   */
1392
  public function putAttachment($dbName, $fileName, $path, $docId, $rev = NULL) {
1393
    $this->validateDocPath($path, TRUE);
1394
    $this->validateAndEncodeDocId($docId);
1395
1396
    $attachment = Doc\Attachment\Attachment::fromFile($fileName);
1397
1398
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".rawurlencode($attachment->getName());
1399
1400
    $request = new Request(Request::PUT_METHOD, $path);
1401
    $request->setHeaderField(Request::CONTENT_LENGTH_HF, $attachment->getContentLength());
1402
    $request->setHeaderField(Request::CONTENT_TYPE_HF, $attachment->getContentType());
1403
    $request->setBody(base64_encode($attachment->getData()));
1404
1405
    // In case of adding or updating an existence document.
1406
    if (!empty($rev))
1407
      $request->setQueryParam("rev", (string)$rev);
1408
1409
    return $this->send($request);
1410
  }
1411
1412
1413
  /**
1414
   * @brief Deletes an attachment from the document.
1415
   * @param string $dbName The database name.
1416
   * @param string $fileName The attachment's name.
1417
   * @param string $docId The document's identifier.
1418
   * @param string $path The document's path.
1419
   * @param string $rev The document's revision.
1420
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#delete--db-docid-attname
1421
   */
1422 View Code Duplication
  public function deleteAttachment($dbName, $fileName, $path, $docId, $rev) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1423
    $this->validateDocPath($path, TRUE);
1424
    $this->validateAndEncodeDocId($docId);
1425
1426
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".rawurlencode($fileName);
1427
1428
    $request = new Request(Request::DELETE_METHOD, $path);
1429
    $request->setQueryParam("rev", (string)$rev);
1430
1431
    return $this->send($request);
1432
  }
1433
1434
  //!@}
1435
1436
1437
  /** @name Special Design Documents Management Methods */
1438
  //!@{
1439
1440
1441
  /**
1442
   * @brief Returns basic information about the design document and his views.
1443
   * @param string $dbName The database name.
1444
   * @param string $docName The design document's name.
1445
   * @return array An associative array
1446
   * @see http://docs.couchdb.org/en/latest/api/ddoc/common.html#get--db-_design-ddoc-_info
1447
   */
1448 View Code Duplication
  public function getDesignDocInfo($dbName, $docName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1449
    $this->validateAndEncodeDocId($docName);
1450
1451
    $path = "/".rawurlencode($this->prefix.$dbName)."/".self::DESIGN_DOC_PATH.$docName."/_info";
1452
1453
    $request = new Request(Request::GET_METHOD, $path);
1454
1455
    return $this->send($request)->getBodyAsArray();
1456
  }
1457
1458
  //!@}
1459
1460
}