Completed
Pull Request — 8.x-2.x (#24)
by Frédéric G.
02:38
created

TopController::buildMainTableRows()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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