Completed
Push — watchdog-config ( 593fab )
by Frédéric G.
8s
created

mongodb_module.php ➔ mongodb()   C

Complexity

Conditions 8
Paths 52

Size

Total Lines 44
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 30
nc 52
nop 1
dl 0
loc 44
rs 5.3846
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * @file
5
 * Contains the main module connecting Drupal to MongoDB.
6
 */
7
8
use Drupal\Core\Routing\RouteMatchInterface;
9
10
/**
11
 * Implements hook_help().
12
 */
13
function mongodb_help($route_name, RouteMatchInterface $route_match) {
0 ignored issues
show
Unused Code introduced by
The parameter $route_match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
14
  switch ($route_name) {
15
    case 'help.page.mongodb':
16
      return '<p>' . t('The Drupal <a href=":project">MongoDB</a> project implements a generic interface to the <a href=":mongo">MongoDB</a> database server.', [
17
        ':project' => 'http://drupal.org/project/mongodb',
18
        ':mongo' => 'http://www.mongodb.org/',
19
      ]);
20
  }
21
}
22
23
/* ==== Broken below this line ============================================== */
24
25
/**
26
 * Returns an MongoDB object.
27
 *
28
 * @param string $alias
29
 *   The name of a MongoDB connection alias. If it is not passed, or if the
30
 *   alias does not match a connection definition, the function will fall back
31
 *   to the 'default' alias.
32
 *
33
 * @return \MongoDB|\MongoDummy
34
 *   A MongoDummy is returned in case a MongoConnectionException is thrown.
35
 *
36
 * @throws \InvalidArgumentException
37
 *   If the database cannot be selected.
38
 * @throws \MongoDB\Driver\Exception\ConnectionException
0 ignored issues
show
introduced by
Comment missing or not on the next line for @throws tag in function comment
Loading history...
39
 *   If the connection cannot be established.
40
 *
41
 * @see mongoDummy
42
 */
43
function mongodb($alias = 'default') {
44
  static $mongo_objects;
45
  $connections = variable_get('mongodb_connections', array());
46
  if (!isset($connections[$alias])) {
47
    $alias = 'default';
48
  }
49
  $connection = $connections[$alias] ?? [];
1 ignored issue
show
introduced by
Expected 1 space after "?"; 0 found
Loading history...
50
  $connection += array(
51
    'host' => 'localhost',
52
    'db' => 'drupal',
53
    'connection_options' => array(),
54
  );
55
  $host = $connection['host'];
56
  $options = $connection['connection_options'] + array('connect' => TRUE);
57
  $db = $connection['db'];
58
  if (!isset($mongo_objects[$host][$db])) {
59
    try {
60
      // Use the 1.3 client if available.
61
      if (class_exists('MongoClient')) {
62
        $mongo = new MongoClient($host, $options);
63
        // Enable read preference and tags if provided. This can also be
64
        // controlled on a per query basis at the cursor level if more control
65
        // is required.
66
        if (!empty($connection['read_preference'])) {
67
          $tags = !empty($connection['read_preference']['tags']) ? $connection['read_preference']['tags'] : array();
68
          $mongo->setReadPreference($connection['read_preference']['preference'], $tags);
69
        }
70
      }
71
      else {
72
        $mongo = new Mongo($host, $options);
73
        if (!empty($connection['slave_ok'])) {
74
          $mongo->setSlaveOkay(TRUE);
75
        }
76
      }
77
      $mongo_objects[$host][$db] = $mongo->selectDB($db);
78
      $mongo_objects[$host][$db]->connection = $mongo;
79
    }
80
    catch (MongoConnectionException $e) {
81
      $mongo_objects[$host][$db] = new MongoDummy();
82
      throw $e;
83
    }
84
  }
85
  return $mongo_objects[$host][$db];
86
}
87
88
/**
89
 * Returns a MongoCollection object.
90
 *
91
 * @return \MongoCollection|\MongoDebugCollection|\MongoDummy
92
 *   Return a MongoCollection in normal situations, a MongoDebugCollection if
93
 *   mongodb_debug is enabled, or a MongoDummy if the MongoDB connection could
94
 *   not be established.
95
 *
96
 * @throws \InvalidArgumentException
97
 *   If the database cannot be selected.
98
 * @throws \MongoDB\Driver\Exception\ConnectionException
0 ignored issues
show
introduced by
Comment missing or not on the next line for @throws tag in function comment
Loading history...
99
 *   If the connection cannot be established.
100
 */
101
function mongodb_collection() {
102
  $args = array_filter(func_get_args());
103
  if (is_array($args[0])) {
104
    list($collection_name, $prefixed) = $args[0];
105
    $prefixed .= $collection_name;
106
  }
107
  else {
108
    // Avoid something. collection names if NULLs are passed in.
109
    $collection_name = implode('.', array_filter($args));
110
    $prefixed = mongodb_collection_name($collection_name);
0 ignored issues
show
Unused Code introduced by
$prefixed is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
111
  }
112
  $collections = variable_get('mongodb_collections', array());
113
  if (isset($collections[$collection_name])) {
114
    // We might be dealing with an array or string because of need to preserve
115
    // backwards compatibility.
116
    $alias = is_array($collections[$collection_name]) && !empty($collections[$collection_name]['db_connection'])
117
      ? $collections[$collection_name]['db_connection']
118
      : $collections[$collection_name];
119
  }
120
  else {
121
    $alias = 'default';
122
  }
123
  // Prefix the collection name for simpletest. It will be in the same DB as the
124
  // non-prefixed version so it's enough to prefix after choosing the mongodb
125
  // object.
126
  $mongodb_object = mongodb($alias);
127
  $collection = $mongodb_object->selectCollection(mongodb_collection_name($collection_name));
0 ignored issues
show
Unused Code introduced by
The call to MongoDummy::selectCollection() has too many arguments starting with mongodb_collection_name($collection_name).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
128
  // Enable read preference and tags at a collection level if we have 1.3
129
  // client.
130
  if (!empty($collections[$alias]['read_preference']) && get_class($mongodb_object->connection) == 'MongoClient') {
131
    $tags = !empty($collections[$alias]['read_preference']['tags']) ? $collections[$alias]['read_preference']['tags'] : array();
132
    $collection->setReadPreference($collections[$alias]['read_preference']['preference'], $tags);
133
  }
134
  $collection->connection = $mongodb_object->connection;
135
  return variable_get('mongodb_debug', FALSE) ? new MongoDebugCollection($collection) : $collection;
136
}
137
138
/**
139
 * Class MongoDebugCollection is a debug decorator for MongoCollection.
140
 */
141
class MongoDebugCollection {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
142
143
  protected $collection;
144
145
  /**
146
   * Constructor.
147
   *
148
   * @param string $collection
149
   *   The collection to decorate.
150
   */
151
  public function __construct($collection) {
152
    $this->collection = $collection;
153
  }
154
155
  /**
156
   * A decorator for the MongoCollection::find() method adding debug info.
157
   *
158
   * @param array $query
159
   *   A MongoCollection;:find()-compatible query array.
160
   * @param array $fields
161
   *   A MongoCollection;:find()-compatible fields array.
162
   *
163
   * @return \MongoDebugCursor
164
   *   A debug cursor wrapping the decorated find() results.
165
   */
166
  public function find($query = array(), $fields = array()) {
167
    debug('find');
168
    debug($query);
169
    debug($fields);
170
    return new MongoDebugCursor($this->collection->find($query, $fields));
0 ignored issues
show
Bug introduced by
The method find cannot be called on $this->collection (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
171
  }
172
173
  /**
174
   * Decorates the standard __call() by debug()-ing its arguments.
175
   *
176
   * @param string $name
177
   *   The name of the called method.
178
   * @param array $arguments
179
   *   The arguments for the decorated __call().
180
   *
181
   * @return mixed
182
   *   The result of the decorated __call().
183
   */
184
  public function __call($name, array $arguments) {
185
    debug($name);
186
    debug($arguments);
187
    return call_user_func_array(array($this->collection, $name), $arguments);
188
  }
189
190
}
191
192
/**
193
 * Class MongoDebugCursor is a debug decorator for MongoCollection::find().
194
 */
195
class MongoDebugCursor {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
196
197
  protected $collection;
198
199
  /**
200
   * Constructor.
201
   *
202
   * @param string $collection
203
   *   The name of the collection on which the cursor applies.
204
   *
205
   * @see mongoDebugCollection::find()
206
   */
207
  public function __construct($collection) {
208
    $this->collection = $collection;
209
  }
210
211
  /**
212
   * Decorates the standard __call() by debug()-ing its arguments.
213
   *
214
   * @param string $name
215
   *   The name of the called method.
216
   * @param array $arguments
217
   *   The arguments for the decorated __call().
218
   *
219
   * @return mixed
220
   *   The result of the decorated __call().
221
   */
222
  public function __call($name, array $arguments) {
223
    debug($name);
224
    debug($arguments);
225
    return call_user_func_array(array($this->collection, $name), $arguments);
226
  }
227
228
}
229
230
/**
231
 * Class MongoDummy is a fake object accepting any method and doing nothing.
232
 */
233
class MongoDummy {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
234
235
  public $connection;
236
237
  /**
238
   * Pretend to return a collection.
239
   *
240
   * @return \MongoDummy
241
   *   The returned value is actually a new MongoDummy instance.
242
   */
243
  public function selectCollection() {
244
    return new MongoDummy();
245
  }
246
247
  /**
248
   * Magic __call accepting any method name and doing nothing.
249
   *
250
   * @param string $name
251
   *   Ignored.
252
   * @param array $arguments
253
   *   All arguments are ignored.
254
   */
255
  public function __call($name, array $arguments) {
256
  }
257
258
}
259
260
/**
261
 * Returns the name to use for the collection.
262
 *
263
 * Also works with prefixes and simpletest.
264
 */
265
function mongodb_collection_name($name) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
266
  global $db_prefix;
0 ignored issues
show
introduced by
Unused global variable $db_prefix.
Loading history...
267
  static $simpletest_prefix;
268
  // We call this function earlier than the database is initalized so we would
269
  // read the parent collection without this.
270
  if (!isset($simpletest_prefix)) {
271
    if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal /^(simpletest\d+);/ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
272
      $simpletest_prefix = $matches[1];
273
    }
274
    else {
275
      $simpletest_prefix = '';
276
    }
277
  }
278
  // However, once the test information is initialized, simpletest_prefix
279
  // is no longer needed.
280
  if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
281
    $simpletest_prefix = $GLOBALS['drupal_test_info']['test_run_id'];
282
  }
283
  return "${simpletest_prefix}${name}";
284
}
285
286
/**
287
 * Implements hook_test_group_finished().
288
 *
289
 * @throws \InvalidArgumentException
290
 *   If the database cannot be selected.
291
 *
292
 * @throws \MongoDB\Driver\Exception\ConnectionException
293
 *   If the connection cannot be established.
294
 */
295
function mongodb_test_group_finished() {
296
  $aliases = variable_get('mongodb_connections', array());
297
  $aliases['default'] = TRUE;
298
  foreach (array_keys($aliases) as $alias) {
299
    $db = mongodb($alias);
300
    foreach ($db->listCollections() as $collection) {
0 ignored issues
show
Bug introduced by
The method listCollections does only exist in MongoDB, but not in MongoDummy.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
301
      if (preg_match('/\.simpletest\d+/', $collection)) {
302
        $db->dropCollection($collection);
0 ignored issues
show
Bug introduced by
The method dropCollection does only exist in MongoDB, but not in MongoDummy.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
303
      }
304
    }
305
  }
306
}
307
308
/**
309
 * Allow for the database connection we are using to be changed.
310
 *
311
 * @param string $alias
312
 *   The alias that we want to change the connection for.
313
 * @param string $connection_name
314
 *   The name of the connection we will use.
315
 */
316
function mongodb_set_active_connection($alias, $connection_name = 'default') {
317
  // No need to check if the connection is valid as mongodb() does this.
318
  $alias_exists = isset($GLOBALS['conf']['mongodb_collections'][$alias]) && is_array($GLOBALS['conf']['mongodb_collections'][$alias]);
319
  if ($alias_exists & !empty($GLOBALS['conf']['mongodb_collections'][$alias]['db_connection'])) {
320
    $GLOBALS['conf']['mongodb_collections'][$alias]['db_connection'] = $connection_name;
321
  }
322
  else {
323
    $GLOBALS['conf']['mongodb_collections'][$alias] = $connection_name;
324
  }
325
}
326
327
/**
328
 * Return the next id in a sequence.
329
 *
330
 * @param string $name
331
 *   The name of the sequence.
332
 * @param int $existing_id
333
 *   An existing id.
334
 *
335
 * @return int
336
 *   The next id in the sequence.
337
 *
338
 * @throws \MongoConnectionException
339
 *   If the connection cannot be established.
340
 */
341
function mongodb_next_id($name, $existing_id = 0) {
342
  // Atomically get the next id in the sequence.
343
  $mongo = mongodb();
344
  $cmd = array(
345
    'findandmodify' => mongodb_collection_name('sequence'),
346
    'query' => array('_id' => $name),
347
    'update' => array('$inc' => array('value' => 1)),
348
    'new' => TRUE,
349
  );
350
  // It's very likely that this is not necessary as command returns an array
351
  // not an exception. The increment will, however, will fix the problem of
352
  // the sequence not existing. Still, better safe than sorry.
353
  try {
354
    $sequence = $mongo->command($cmd);
0 ignored issues
show
Bug introduced by
The method command does only exist in MongoDB, but not in MongoDummy.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
355
    $value = isset($sequence['value']['value']) ? $sequence['value']['value'] : 0;
356
  }
357
  catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
358
  }
359
  if (0 < $existing_id - $value + 1) {
360
    $cmd = array(
361
      'findandmodify' => mongodb_collection_name('sequence'),
362
      'query' => array('_id' => $name),
363
      'update' => array('$inc' => array('value' => $existing_id - $value + 1)),
0 ignored issues
show
Bug introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
364
      'upsert' => TRUE,
365
      'new' => TRUE,
366
    );
367
    $sequence = $mongo->command($cmd);
368
    $value = isset($sequence['value']['value']) ? $sequence['value']['value'] : 0;
369
  }
370
  return $value;
371
}
372
373
/**
374
 * Returns default options for MongoDB write operations.
375
 *
376
 * @param bool $safe
377
 *   Set it to FALSE for "fire and forget" write operation.
378
 *
379
 * @return array
380
 *   Default options for Mongo write operations.
381
 */
382
function mongodb_default_write_options($safe = TRUE) {
383
  if ($safe) {
384 View Code Duplication
    if (version_compare(phpversion('mongo'), '1.5.0') == -1) {
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...
385
      return array('safe' => TRUE);
386
    }
387
    else {
388
      return variable_get('mongodb_write_safe_options', array('w' => 1));
389
    }
390
  }
391 View Code Duplication
  else {
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...
392
    if (version_compare(phpversion('mongo'), '1.3.0') == -1) {
393
      return array();
394
    }
395
    else {
396
      return variable_get('mongodb_write_nonsafe_options', array('w' => 0));
397
    }
398
  }
399
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
400