Completed
Push — 8.x-2.x ( 74e505...ec080d )
by Frédéric G.
03:07
created

TopController::group()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 12
c 2
b 0
f 1
nc 1
nop 5
dl 0
loc 15
rs 9.4285
1
<?php
2
3
namespace Drupal\mongodb_watchdog\Controller;
4
5
use Drupal\Core\Config\ImmutableConfig;
6
use Drupal\mongodb_watchdog\Logger;
7
use MongoDB\BSON\Javascript;
8
use MongoDB\Collection;
9
use MongoDB\Database;
10
use Psr\Log\LoggerInterface;
11
use Symfony\Component\DependencyInjection\ContainerInterface;
12
use Symfony\Component\HttpFoundation\Request;
13
14
/**
15
 * The Top403/Top404 controllers.
16
 */
17
class TopController extends ControllerBase {
18
  const TYPES = [
19
    'page not found',
20
    'access denied',
21
  ];
22
23
  /**
24
   * The database holding the logger collections.
25
   *
26
   * @var \MongoDB\Database
27
   */
28
  protected $database;
29
30
  /**
31
   * TopController constructor.
32
   *
33
   * @param \Psr\Log\LoggerInterface $logger
34
   *   The logger service, to log intervening events.
35
   * @param \Drupal\mongodb_watchdog\Logger $watchdog
36
   *   The MongoDB logger, to load stored events.
37
   * @param \Drupal\Core\Config\ImmutableConfig $config
38
   *   The module configuration.
39
   * @param \MongoDB\Database $database
40
   *   Needed because there is no group() command in phplib yet.
41
   *
42
   * @see https://jira.mongodb.org/browse/PHPLIB-177
43
   */
44
  public function __construct(
45
    LoggerInterface $logger,
46
    Logger $watchdog,
47
    ImmutableConfig $config,
48
    Database $database) {
49
    parent::__construct($logger, $watchdog, $config);
50
51
    $this->database = $database;
52
  }
53
54
  /**
55
   * Controller.
56
   *
57
   * @param \Symfony\Component\HttpFoundation\Request $request
58
   *   The current request.
59
   * @param string $type
60
   *   The type of top report to produce.
61
   *
62
   * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
63
   *   A render array.
64
   */
65 View Code Duplication
  public function build(Request $request, $type) {
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...
66
    $top = $this->getTop();
67
68
    $rows = $this->getRowData($request, $type);
69
    $main = empty($rows)
70
      ? $this->buildEmpty(t('No "%type" message found', ['%type' => $type]))
71
      : $this->buildMainTable($rows);
72
73
    $ret = $this->buildDefaults($main, $top);
74
    return $ret;
75
  }
76
77
  /**
78
   * Build the main table.
79
   *
80
   * @param array $rows
81
   *   The event data.
82
   *
83
   * @return array<string,string|array>
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,\Drupal\Cor...g,array|string>|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
84
   *   A render array for the main table.
85
   */
86 View Code Duplication
  protected function buildMainTable(array $rows) {
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...
87
    $ret = [
88
      '#header' => $this->buildMainTableHeader(),
89
      '#rows' => $this->buildMainTableRows($rows),
90
      '#type' => 'table',
91
    ];
92
    return $ret;
93
  }
94
95
  /**
96
   * Build the main table header.
97
   *
98
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
99
   *   A table header array.
100
   */
101
  protected function buildMainTableHeader() {
102
    $header = [
103
      t('#'),
104
      t('Paths'),
105
    ];
106
107
    return $header;
108
  }
109
110
  /**
111
   * Build the main table rows.
112
   *
113
   * @param array[] $counts
114
   *   The array of counts per 403/404 page.
115
   *
116
   * @return array<string,array|string>
117
   *   A render array for a table.
118
   */
119
  protected function buildMainTableRows($counts) {
0 ignored issues
show
introduced by
Type hint "array" missing for $counts
Loading history...
120
    $rows = [];
121
    foreach ($counts as $count) {
122
      $row = [
123
        $count['count'],
124
        $count['variables.@uri'],
125
      ];
126
      $rows[] = $row;
127
    }
128
129
    return $rows;
130
  }
131
132
  /**
133
   * {@inheritdoc}
134
   */
135 View Code Duplication
  public static function create(ContainerInterface $container) {
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...
136
    /** @var \Psr\Log\LoggerInterface $logger */
137
    $logger = $container->get('logger.channel.mongodb_watchdog');
138
139
    /** @var \Drupal\mongodb_watchdog\Logger $watchdog */
140
    $watchdog = $container->get('mongodb.logger');
141
142
    /** @var \Drupal\Core\Config\ImmutableConfig $config */
143
    $config = $container->get('config.factory')->get('mongodb_watchdog.settings');
144
145
    /** @var \MongoDB\Database $database */
146
    $database = $container->get('mongodb.watchdog_storage');
147
148
    return new static($logger, $watchdog, $config, $database);
149
  }
150
151
  /**
152
   * Obtain the data from the logger.
153
   *
154
   * @param \Symfony\Component\HttpFoundation\Request $request
155
   *   The current request. Needed for paging.
156
   * @param string $type
157
   *   The type of top list to retrieve.
158
   *
159
   * @return array
160
   *   The data array.
161
   */
162
  protected function getRowData(Request $request, $type) {
163
    // Find _id for the error type.
164
    $templateCollection = $this->watchdog->templateCollection();
165
    $template = $templateCollection->findOne(['type' => $type], ['_id']);
166
    if (empty($template)) {
167
      return [];
168
    }
169
170
    // Find occurrences of error type.
171
    $collectionName = $template['_id'];
172
    $eventCollection = $this->watchdog->eventCollection($collectionName);
173
174
    $key = ['variables.@uri' => 1];
175
    $cond = [];
176
    $reducer = <<<JAVASCRIPT
177
function reducer(doc, accumulator) { 
178
  accumulator.count++; 
179
}
180
JAVASCRIPT;
181
182
    $initial = ['count' => 0];
183
    $counts = $this->group($eventCollection, $key, $cond, $reducer, $initial);
184
    if (empty($counts['ok'])) {
185
      return [];
186
    }
187
188
    $counts = $counts['retval'];
189
    usort($counts, [$this, 'topSort']);
190
191
    $page = $this->setupPager($request, count($counts));
192
    $skip = $page * $this->itemsPerPage;
193
    $counts = array_slice($counts, $skip, $this->itemsPerPage);
194
195
    return $counts;
196
  }
197
198
  /**
199
   * Command wrapper for missing MongoDB group() implementation in PHPlib.
200
   *
201
   * @param \MongoDB\Collection $collection
202
   *   The collection on which to perform the command.
203
   * @param object $key
204
   *   The grouping key.
205
   * @param object $cond
206
   *   The condition.
207
   * @param string $reduce
208
   *   The reducer function: must be valid JavaScript code.
209
   * @param object $initial
210
   *   The initial document.
211
   *
212
   * @return array|void
213
   *   Void in case of error, otherwise an array with the following keys:
214
   *   - waitedMS: time spent waiting
215
   *   - retval: an array of command results, containing at least the key
216
   *   - count: the total number of documents matched
217
   *   - keys: the number of different keys, normally matching count(retval)
218
   *   - ok: 1.0 in case of success.
219
   */
220
  public function group(Collection $collection, $key, $cond, $reduce, $initial) {
221
    $cursor = $this->database->command([
222
      'group' => [
223
        'ns' => $collection->getCollectionName(),
224
        'key' => $key,
225
        'cond' => $cond,
226
        'initial' => $initial,
227
        '$reduce' => new Javascript($reduce),
228
      ],
229
    ], Logger::LEGACY_TYPE_MAP);
230
231
    $ret = $cursor->toArray();
232
    $ret = reset($ret);
233
    return $ret;
234
  }
235
236
  /**
237
   * Callback for usort() to sort top entries returned from a group query.
238
   *
239
   * @param array $first
240
   *   The first value to compare.
241
   * @param array $second
242
   *   The second value to compare.
243
   *
244
   * @return int
245
   *   The comparison result.
246
   *
247
   * @see \Drupal\mongodb_watchdog\Controller\TopController::build()
248
   */
249
  protected function topSort(array $first, array $second) {
250
    return $second['count'] <=> $first['count'];
251
  }
252
253
}
254