Completed
Push — 8.x-2.x ( f0939b...910a76 )
by Frédéric G.
03:36
created

Logger::ensureSchema()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 44
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 2
Metric Value
cc 1
eloc 18
c 3
b 0
f 2
nc 1
nop 0
dl 0
loc 44
rs 8.8571
1
<?php
2
3
namespace Drupal\mongodb_watchdog;
4
5
use Drupal\Component\Render\MarkupInterface;
6
use Drupal\Component\Utility\Unicode;
7
use Drupal\Component\Utility\Xss;
8
use Drupal\Core\Config\ConfigFactoryInterface;
9
use Drupal\Core\Logger\LogMessageParserInterface;
10
use Drupal\Core\Logger\RfcLogLevel;
11
use MongoDB\Database;
12
use MongoDB\Driver\Exception\InvalidArgumentException;
13
use MongoDB\Driver\Exception\RuntimeException;
14
use Psr\Log\AbstractLogger;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
17
/**
18
 * Class Logger is a PSR/3 Logger using a MongoDB data store.
19
 *
20
 * @package Drupal\mongodb_watchdog
21
 */
22
class Logger extends AbstractLogger {
23
  const CONFIG_NAME = 'mongodb_watchdog.settings';
24
25
  const TRACKER_COLLECTION = 'watchdog_tracker';
26
  const TEMPLATE_COLLECTION = 'watchdog';
27
  const EVENT_COLLECTION_PREFIX = 'watchdog_event_';
28
  const EVENT_COLLECTIONS_PATTERN = '^watchdog_event_[[:xdigit:]]{32}$';
29
30
  const LEGACY_TYPE_MAP = [
31
    'typeMap' => [
32
      'array' => 'array',
33
      'document' => 'array',
34
      'root' => 'array',
35
    ],
36
  ];
37
38
  /**
39
   * The logger storage.
40
   *
41
   * @var \MongoDB\Database
42
   */
43
  protected $database;
44
45
  /**
46
   * The limit for the capped event collections.
47
   *
48
   * @var int
49
   */
50
  protected $items;
51
52
  /**
53
   * The minimum logging level.
54
   *
55
   * @var int
56
   *
57
   * @see https://drupal.org/node/1355808
58
   */
59
  protected $limit = RfcLogLevel::DEBUG;
60
61
  /**
62
   * The message's placeholders parser.
63
   *
64
   * @var \Drupal\Core\Logger\LogMessageParserInterface
65
   */
66
  protected $parser;
67
68
  /**
69
   * The "requests" setting.
70
   *
71
   * @var int
72
   */
73
  protected $requests;
74
75
  /**
76
   * The request_stack service.
77
   *
78
   * @var \Symfony\Component\HttpFoundation\RequestStack
79
   */
80
  protected $requestStack;
81
82
  /**
83
   * An array of templates already used in this request.
84
   *
85
   * Used only with request tracking enabled.
86
   *
87
   * @var string[]
88
   */
89
  protected $templates = [];
90
91
  /**
92
   * A sequence number for log events during a request.
93
   *
94
   * @var int
95
   */
96
  protected $sequence = 0;
97
98
  /**
99
   * Logger constructor.
100
   *
101
   * @param \MongoDB\Database $database
102
   *   The database object.
103
   * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
104
   *   The parser to use when extracting message variables.
105
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
106
   *   The core config_factory service.
107
   * @param \Symfony\Component\HttpFoundation\RequestStack $stack
108
   *   The core request_stack service.
109
   */
110
  public function __construct(Database $database, LogMessageParserInterface $parser, ConfigFactoryInterface $config_factory, RequestStack $stack) {
111
    $this->database = $database;
112
    $this->parser = $parser;
113
    $this->requestStack = $stack;
114
115
    $config = $config_factory->get(static::CONFIG_NAME);
116
    $this->limit = $config->get('limit');
117
    $this->items = $config->get('items');
118
    $this->requests = $config->get('requests');
119
    $this->requestTracking = $config->get('request_tracking');
0 ignored issues
show
Bug introduced by
The property requestTracking does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
120
  }
121
122
  /**
123
   * Fill in the log_entry function, file, and line.
124
   *
125
   * @param array $log_entry
126
   *   An event information to be logger.
127
   * @param array $backtrace
128
   *   A call stack.
129
   */
130
  protected function enhanceLogEntry(array &$log_entry, array $backtrace) {
131
    // Create list of functions to ignore in backtrace.
132
    static $ignored = array(
133
      'call_user_func_array' => 1,
134
      '_drupal_log_error' => 1,
135
      '_drupal_error_handler' => 1,
136
      '_drupal_error_handler_real' => 1,
137
      'Drupal\mongodb_watchdog\Logger::log' => 1,
138
      'Drupal\Core\Logger\LoggerChannel::log' => 1,
139
      'Drupal\Core\Logger\LoggerChannel::alert' => 1,
140
      'Drupal\Core\Logger\LoggerChannel::critical' => 1,
141
      'Drupal\Core\Logger\LoggerChannel::debug' => 1,
142
      'Drupal\Core\Logger\LoggerChannel::emergency' => 1,
143
      'Drupal\Core\Logger\LoggerChannel::error' => 1,
144
      'Drupal\Core\Logger\LoggerChannel::info' => 1,
145
      'Drupal\Core\Logger\LoggerChannel::notice' => 1,
146
      'Drupal\Core\Logger\LoggerChannel::warning' => 1,
147
    );
148
149
    foreach ($backtrace as $bt) {
150
      if (isset($bt['function'])) {
151
        $function = empty($bt['class']) ? $bt['function'] : $bt['class'] . '::' . $bt['function'];
152
        if (empty($ignored[$function])) {
153
          $log_entry['%function'] = $function;
154
          /* Some part of the stack, like the line or file info, may be missing.
155
           *
156
           * @see http://goo.gl/8s75df
157
           *
158
           * No need to fetch the line using reflection: it would be redundant
159
           * with the name of the function.
160
           */
161
          $log_entry['%line'] = isset($bt['line']) ? $bt['line'] : NULL;
162
          if (empty($bt['file'])) {
163
            $reflected_method = new \ReflectionMethod($function);
164
            $bt['file'] = $reflected_method->getFileName();
165
          }
166
167
          $log_entry['%file'] = $bt['file'];
168
          break;
169
        }
170
        elseif ($bt['function'] == '_drupal_exception_handler') {
171
          $e = $bt['args'][0];
172
          $this->enhanceLogEntry($log_entry, $e->getTrace());
173
        }
174
      }
175
    }
176
  }
177
178
  /**
179
   * {@inheritdoc}
180
   *
181
   * @see https://drupal.org/node/1355808
182
   */
183
  public function log($level, $template, array $context = []) {
184
    if ($level > $this->limit) {
185
      return;
186
    }
187
188
    // Convert PSR3-style messages to SafeMarkup::format() style, so they can be
189
    // translated too in runtime.
190
    $message_placeholders = $this->parser->parseMessagePlaceholders($template, $context);
191
192
    // If code location information is all present, as for errors/exceptions,
193
    // then use it to build the message template id.
194
    $type = $context['channel'];
195
    $location_info = [
196
      '%type' => 1,
197
      '@message' => 1,
198
      '%function' => 1,
199
      '%file' => 1,
200
      '%line' => 1,
201
    ];
202
    if (!empty(array_diff_key($location_info, $message_placeholders))) {
203
      $this->enhanceLogEntry($message_placeholders, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10));
204
    }
205
    $file = $message_placeholders['%file'];
206
    $line = $message_placeholders['%line'];
207
    $function = $message_placeholders['%function'];
208
    $key = "${type}:${level}:${file}:${line}:${function}";
209
    $template_id = md5($key);
210
211
    $selector = ['_id' => $template_id];
212
    $update = [
213
      '$inc' => ['count' => 1],
214
      '$set' => [
215
        '_id' => $template_id,
216
        'message' => $template,
217
        'severity' => $level,
218
        'changed' => time(),
219
        'type' => Unicode::substr($context['channel'], 0, 64),
220
      ],
221
    ];
222
    $options = ['upsert' => TRUE];
223
    $template_result = $this->database
224
      ->selectCollection(static::TEMPLATE_COLLECTION)
225
      ->updateOne($selector, $update, $options);
226
227
    // Only insert each template once per request.
228
    if ($this->requestTracking && !isset($this->templates[$template_id])) {
229
      $request_id = $this->requestStack
230
        ->getCurrentRequest()
231
        ->server
232
        ->get('UNIQUE_ID');
233
234
      $this->templates[$template_id] = 1;
235
      $track = [
236
        'request_id' => $request_id,
237
        'template_id' => $template_id,
238
      ];
239
      $this->trackerCollection()->insertOne($track);
240
    }
241
242
    $event_collection = $this->eventCollection($template_id);
243
    if ($template_result->getUpsertedCount()) {
244
      // Capped collections are actually size-based, not count-based, so "items"
245
      // is only a maximum, assuming event documents weigh 1kB, but the actual
246
      // number of items stored may be lower if items are heavier.
247
      // We do not use 'autoindexid' for greater speed, because:
248
      // - it does not work on replica sets,
249
      // - it is deprecated in MongoDB 3.2 and going away in 3.4.
250
      $options = [
251
        'capped' => TRUE,
252
        'size' => $this->items * 1024,
253
        'max' => $this->items,
254
      ];
255
      $this->database->createCollection($event_collection->getCollectionName(), $options);
256
257
      // Do not create this index by default, as its cost is useless if request
258
      // tracking is not enabled.
259
      if ($this->requestTracking) {
260
        $key = ['requestTracking_id' => 1];
261
        $options = ['name' => 'admin-by-request'];
262
        $event_collection->createIndex($key, $options);
263
      }
264
    }
265
266
    foreach ($message_placeholders as &$placeholder) {
267
      if ($placeholder instanceof MarkupInterface) {
0 ignored issues
show
Bug introduced by
The class Drupal\Component\Render\MarkupInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
268
        $placeholder = Xss::filterAdmin($placeholder);
269
      }
270
    }
271
    $event = [
272
      'hostname' => Unicode::substr($context['ip'], 0, 128),
273
      'link' => $context['link'],
274
      'location' => $context['request_uri'],
275
      'referer' => $context['referer'],
276
      'timestamp' => $context['timestamp'],
277
      'user' => ['uid' => $context['uid']],
278
      'variables' => $message_placeholders,
279
    ];
280
    if ($this->requestTracking) {
281
      // Fetch the current request on each event to support subrequest nesting.
282
      $event['requestTracking_id'] = $request_id;
0 ignored issues
show
Bug introduced by
The variable $request_id 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...
283
      $event['requestTracking_sequence'] = $this->sequence;
284
      $this->sequence++;
285
    }
286
    $event_collection->insertOne($event);
287
  }
288
289
  /**
290
   * Ensure a collection is capped with the proper size.
291
   *
292
   * @param string $name
293
   *   The collection name.
294
   * @param int $inboundSize
295
   *   The collection size cap.
296
   *
297
   * @return \MongoDB\Collection
298
   *   The collection, usable for additional commands like index creation.
299
   *
300
   * @TODO support sharded clusters: convertToCapped does not support them.
301
   *
302
   * @see https://docs.mongodb.com/manual/reference/command/convertToCapped
303
   *
304
   * Note that MongoDB 3.2 still misses a proper exists() command, which is the
305
   * reason for the weird try/catch logic.
306
   *
307
   * @see https://jira.mongodb.org/browse/SERVER-1938
308
   */
309
  public function ensureCappedCollection($name, $inboundSize) {
310
    if ($inboundSize == 0) {
311
      drupal_set_message(t('Abnormal size 0 ensuring capped collection, defaulting.'), 'error');
312
      $size = 100000;
313
    }
314
    else {
315
      $size = $inboundSize;
316
    }
317
318
    try {
319
      $command = [
320
        'collStats' => $name,
321
      ];
322
      $stats = $this->database->command($command, static::LEGACY_TYPE_MAP)->toArray()[0];
323
    }
324
    catch (RuntimeException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\RuntimeException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
325
      // 59 is expected if the collection was not found. Other values are not.
326
      if ($e->getCode() !== 59) {
327
        throw $e;
328
      }
329
330
      $this->database->createCollection($name);
331
      $stats = $this->database->command([
332
        'collStats' => $name,
333
      ], static::LEGACY_TYPE_MAP)->toArray()[0];
334
    }
335
336
    $collection = $this->database->selectCollection($name);
337
    if (!empty($stats['capped'])) {
338
      return $collection;
339
    }
340
341
    $command = [
342
      'convertToCapped' => $name,
343
      'size' => $size,
344
    ];
345
    $this->database->command($command);
346
    return $collection;
347
  }
348
349
  /**
350
   * Ensure indexes are set on the collections and tracker collection is capped.
351
   *
352
   * First index is on <line, timestamp> instead of <function, line, timestamp>,
353
   * because we write to this collection a lot, and the smaller index on two
354
   * numbers should be much faster to create than one with a string included.
355
   */
356
  public function ensureSchema() {
357
    $trackerCollection = $this->ensureCappedCollection(static::TRACKER_COLLECTION, $this->requests * 1024);
358
    $indexes = [
359
      [
360
        'name' => 'tracker-request',
361
        'key' => ['request_id' => 1],
362
      ],
363
    ];
364
    $trackerCollection->createIndexes($indexes);
365
366
    $indexes = [
367
      // Index for adding/updating increments.
368
      [
369
        'name' => 'for-increments',
370
        'key' => ['line' => 1, 'changed' => -1],
371
      ],
372
373
      // Index for overview page without filters.
374
      [
375
        'name' => 'overview-no-filters',
376
        'key' => ['changed' => -1],
377
      ],
378
379
      // Index for overview page filtering by type.
380
      [
381
        'name' => 'overview-by-type',
382
        'key' => ['type' => 1, 'changed' => -1],
383
      ],
384
385
      // Index for overview page filtering by severity.
386
      [
387
        'name' => 'overview-by-severity',
388
        'key' => ['severity' => 1, 'changed' => -1],
389
      ],
390
391
      // Index for overview page filtering by type and severity.
392
      [
393
        'name' => 'overview-by-both',
394
        'key' => ['type' => 1, 'severity' => 1, 'changed' => -1],
395
      ],
396
    ];
397
398
    $this->templateCollection()->createIndexes($indexes);
399
  }
400
401
  /**
402
   * Return a collection, given its template id.
403
   *
404
   * @param string $template_id
405
   *   The string representation of a template \MongoId.
406
   *
407
   * @return \MongoDB\Collection
408
   *   A collection object for the specified template id.
409
   */
410
  public function eventCollection($template_id) {
411
    $collection_name = static::EVENT_COLLECTION_PREFIX . $template_id;
412
    if (!preg_match('/' . static::EVENT_COLLECTIONS_PATTERN . '/', $collection_name)) {
413
      throw new InvalidArgumentException(t('Invalid watchdog template id `@id`.', [
414
        '@id' => $collection_name,
415
      ]));
416
    }
417
    $collection = $this->database->selectCollection($collection_name);
418
    return $collection;
419
  }
420
421
  /**
422
   * List the event collections.
423
   *
424
   * @return \MongoDB\Collection[]
425
   *   The collections with a name matching the event pattern.
426
   */
427
  public function eventCollections() {
428
    $options = [
429
      'filter' => [
430
        'name' => ['$regex' => static::EVENT_COLLECTIONS_PATTERN],
431
      ],
432
    ];
433
    $result = iterator_to_array($this->database->listCollections($options));
434
    return $result;
435
  }
436
437
  /**
438
   * Return the number of events for a template.
439
   *
440
   * @param \Drupal\mongodb_watchdog\EventTemplate $template
441
   *   A template for which to count events.
442
   *
443
   * @return int
444
   *   The number of matching events.
445
   */
446
  public function eventCount(EventTemplate $template) {
447
    return $this->eventCollection($template->_id)->count();
448
  }
449
450
  /**
451
   * Return the events having occurred during a given request.
452
   *
453
   * @param string $requestId
454
   *   The request unique_id.
455
   * @param int $skip
456
   *   The number of events to skip in the result.
457
   * @param int $limit
458
   *   The maximum number of events to return.
459
   *
460
   * @return array<\Drupal\mongodb_watchdog\EventTemplate\Drupal\mongodb_watchdog\Event[]>
461
   *   An array of [template, event] arrays, ordered by occurrence order.
462
   */
463
  public function requestEvents($requestId, $skip = 0, $limit = 0) {
464
    $templates = $this->requestTemplates($requestId);
465
    $selector = [
466
      'requestTracking_id' => $requestId,
467
      'requestTracking_sequence' => [
468
        '$gte' => $skip,
469
        '$lt' => $skip + $limit,
470
      ],
471
    ];
472
    $events = [];
473
    $options = [
474
      'typeMap' => [
475
        'array' => 'array',
476
        'document' => 'array',
477
        'root' => '\Drupal\mongodb_watchdog\Event',
478
      ],
479
    ];
480
481
    /**
482
     * @var string $template_id
483
     * @var \Drupal\mongodb_watchdog\EventTemplate $template
484
     */
485
    foreach ($templates as $template_id => $template) {
486
      $event_collection = $this->eventCollection($template_id);
487
      $cursor = $event_collection->find($selector, $options);
488
      /** @var \Drupal\mongodb_watchdog\Event $event */
489
      foreach ($cursor as $event) {
490
        $events[$event->requestTracking_sequence] = [
491
          $template,
492
          $event,
493
        ];
494
      }
495
    }
496
497
    ksort($events);
498
    return $events;
499
  }
500
501
  /**
502
   * Count events matching a request unique_id.
503
   *
504
   * XXX This implementation may be very inefficient in case of a request gone
505
   * bad generating non-templated varying messages: #requests is O(#templates).
506
   *
507
   * @param string $requestId
508
   *   The unique_id of the request.
509
   *
510
   * @return int
511
   *   The number of events matching the unique_id.
512
   */
513
  public function requestEventsCount($requestId) {
514
    if (empty($requestId)) {
515
      return 0;
516
    }
517
518
    $templates = $this->requestTemplates($requestId);
519
    $count = 0;
520
    foreach ($templates as $template) {
521
      $eventCollection = $this->eventCollection($template->_id);
522
      $selector = [
523
        'requestTracking_id' => $requestId,
524
      ];
525
      $count += $eventCollection->count($selector);
526
    }
527
528
    return $count;
529
  }
530
531
  /**
532
   * Return the number of event templates.
533
   */
534
  public function templatesCount() {
535
    return $this->templateCollection()->count([]);
536
  }
537
538
  /**
539
   * Return an array of templates uses during a given request.
540
   *
541
   * @param string $unsafe_request_id
542
   *   A request "unique_id".
543
   *
544
   * @return \Drupal\mongodb_watchdog\EventTemplate[]
545
   *   An array of EventTemplate instances.
546
   */
547
  public function requestTemplates($unsafe_request_id) {
548
    $request_id = "${unsafe_request_id}";
549
    $selector = [
550
      'request_id' => $request_id,
551
    ];
552
553
    $cursor = $this
554
      ->trackerCollection()
555
      ->find($selector, static::LEGACY_TYPE_MAP + [
556
        'projection' => [
557
          '_id' => 0,
558
          'template_id' => 1,
559
        ],
560
      ]);
561
    $template_ids = [];
562
    foreach ($cursor as $request) {
563
      $template_ids[] = $request['template_id'];
564
    }
565
    if (empty($template_ids)) {
566
      return [];
567
    }
568
569
    $selector = ['_id' => ['$in' => $template_ids]];
570
    $options = [
571
      'typeMap' => [
572
        'array' => 'array',
573
        'document' => 'array',
574
        'root' => '\Drupal\mongodb_watchdog\EventTemplate',
575
      ],
576
    ];
577
    $templates = [];
578
    $cursor = $this->templateCollection()->find($selector, $options);
579
    /** @var \Drupal\mongodb_watchdog\EventTemplate $template */
580
    foreach ($cursor as $template) {
581
      $templates[$template->_id] = $template;
582
    }
583
    return $templates;
584
  }
585
586
  /**
587
   * Return the request events tracker collection.
588
   *
589
   * @return \MongoDB\Collection
590
   *   The collection.
591
   */
592
  public function trackerCollection() {
593
    return $this->database->selectCollection(static::TRACKER_COLLECTION);
594
  }
595
596
  /**
597
   * Return the event templates collection.
598
   *
599
   * @return \MongoDB\Collection
600
   *   The collection.
601
   */
602
  public function templateCollection() {
603
    return $this->database->selectCollection(static::TEMPLATE_COLLECTION);
604
  }
605
606
  /**
607
   * Return templates matching type and level criteria.
608
   *
609
   * @param string[] $types
610
   *   An array of EventTemplate types. May be a hash.
611
   * @param string[]|int[] $levels
612
   *   An array of severity levels.
613
   * @param int $skip
614
   *   The number of templates to skip before the first one displayed.
615
   * @param int $limit
616
   *   The maximum number of templates to return.
617
   *
618
   * @return \MongoDB\Driver\Cursor
619
   *   A query result for the templates.
620
   */
621
  public function templates(array $types = [], array $levels = [], $skip = 0, $limit = 0) {
622
    $selector = [];
623
    if (!empty($types)) {
624
      $selector['type'] = ['$in' => array_values($types)];
625
    }
626
    if (!empty($levels) && count($levels) !== count(RfcLogLevel::getLevels())) {
627
      // Severity levels come back from the session as strings, not integers.
628
      $selector['severity'] = ['$in' => array_values(array_map('intval', $levels))];
629
    }
630
    $options = [
631
      'sort' => [
632
        'count' => -1,
633
        'changed' => -1,
634
      ],
635
      'typeMap' => [
636
        'array' => 'array',
637
        'document' => 'array',
638
        'root' => '\Drupal\mongodb_watchdog\EventTemplate',
639
      ],
640
    ];
641
    if ($skip) {
642
      $options['skip'] = $skip;
643
    }
644
    if ($limit) {
645
      $options['limit'] = $limit;
646
    }
647
648
    $cursor = $this->templateCollection()->find($selector, $options);
649
    return $cursor;
650
  }
651
652
  /**
653
   * Return the template types actually present in storage.
654
   *
655
   * @return string[]
656
   *   An array of distinct EventTemplate types.
657
   */
658
  public function templateTypes() {
659
    $ret = $this->templateCollection()->distinct('type');
660
    return $ret;
661
  }
662
663
}
664