Passed
Push — 8.x-2.x ( bfb9ec...68110c )
by Frédéric G.
03:30
created

TopController::topSort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Drupal\mongodb_watchdog\Controller;
6
7
use Drupal\Core\Config\ImmutableConfig;
8
use Drupal\Core\Pager\PagerManagerInterface;
9
use Drupal\mongodb_watchdog\Logger;
10
use MongoDB\Collection;
11
use MongoDB\Database;
12
use Psr\Log\LoggerInterface;
13
use Symfony\Component\DependencyInjection\ContainerInterface;
14
use Symfony\Component\HttpFoundation\Request;
15
16
/**
17
 * The Top403/Top404 controllers.
18
 */
19
class TopController extends ControllerBase {
20
  const TYPES = [
21
    'page not found',
22
    'access denied',
23
  ];
24
25
  const TYPE_MAP = [
26
    'root' => TopResult::class,
27
  ];
28
29
30
  /**
31
   * The database holding the logger collections.
32
   *
33
   * @var \MongoDB\Database
34
   */
35
  protected $database;
36
37
  /**
38
   * TopController constructor.
39
   *
40
   * @param \Psr\Log\LoggerInterface $logger
41
   *   The logger service, to log intervening events.
42
   * @param \Drupal\mongodb_watchdog\Logger $watchdog
43
   *   The MongoDB logger, to load stored events.
44
   * @param \Drupal\Core\Config\ImmutableConfig $config
45
   *   The module configuration.
46
   * @param \MongoDB\Database $database
47
   *   Needed because there is no group() command in phplib yet.
48
   * @param \Drupal\Core\Pager\PagerManagerInterface $pagerManager
49
   *   The core pager.manager service.
50
   *
51
   * @see https://jira.mongodb.org/browse/PHPLIB-177
52
   */
53
  public function __construct(
54
    LoggerInterface $logger,
55
    Logger $watchdog,
56
    ImmutableConfig $config,
57
    Database $database,
58
    PagerManagerInterface $pagerManager) {
59
    parent::__construct($logger, $watchdog, $pagerManager, $config);
60
61
    $this->database = $database;
62
  }
63
64
  /**
65
   * Controller.
66
   *
67
   * @param \Symfony\Component\HttpFoundation\Request $request
68
   *   The current request.
69
   * @param string $type
70
   *   The type of top report to produce.
71
   *
72
   * @return array
73
   *   A render array.
74
   */
75 View Code Duplication
  public function build(Request $request, string $type): array {
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...
76
    $top = $this->getTop();
77
78
    $rows = $this->getRowData($request, $type);
79
    $main = empty($rows)
80
      ? $this->buildEmpty($this->t('No "%type" message found', ['%type' => $type]))
81
      : $this->buildMainTable($rows);
82
83
    $ret = $this->buildDefaults($main, $top);
84
    return $ret;
85
  }
86
87
  /**
88
   * Build the main table.
89
   *
90
   * @param array $rows
91
   *   The event data.
92
   *
93
   * @return array
94
   *   A render array for the main table.
95
   */
96 View Code Duplication
  protected function buildMainTable(array $rows): array {
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...
97
    $ret = [
98
      '#header' => $this->buildMainTableHeader(),
99
      '#rows' => $this->buildMainTableRows($rows),
100
      '#type' => 'table',
101
    ];
102
    return $ret;
103
  }
104
105
  /**
106
   * Build the main table header.
107
   *
108
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
109
   *   A table header array.
110
   */
111
  protected function buildMainTableHeader(): array {
112
    $header = [
113
      $this->t('#'),
114
      $this->t('Paths'),
115
    ];
116
117
    return $header;
118
  }
119
120
  /**
121
   * Build the main table rows.
122
   *
123
   * @param array[] $counts
124
   *   The array of counts per 403/404 page.
125
   *
126
   * @return array
127
   *   A render array for a table.
128
   */
129
  protected function buildMainTableRows(array $counts): array {
130
    $rows = [];
131
    /** @var \Drupal\mongodb_watchdog\Controller\TopResult $result */
132
    foreach ($counts as $result) {
133
      $row = [
134
        $result->count,
135
        $result->uri,
136
      ];
137
      $rows[] = $row;
138
    }
139
140
    return $rows;
141
  }
142
143
  /**
144
   * {@inheritdoc}
145
   */
146 View Code Duplication
  public static function create(ContainerInterface $container): self {
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...
147
    /** @var \Psr\Log\LoggerInterface $logger */
148
    $logger = $container->get('logger.channel.mongodb_watchdog');
149
150
    /** @var \Drupal\mongodb_watchdog\Logger $watchdog */
151
    $watchdog = $container->get(Logger::SERVICE_LOGGER);
152
153
    /** @var \Drupal\Core\Config\ImmutableConfig $config */
154
    $config = $container->get('config.factory')->get('mongodb_watchdog.settings');
155
156
    /** @var \MongoDB\Database $database */
157
    $database = $container->get('mongodb.watchdog_storage');
158
159
    /** @var \Drupal\Core\Pager\PagerManagerInterface $pagerManager */
160
    $pagerManager = $container->get('pager.manager');
161
162
    return new static($logger, $watchdog, $config, $database, $pagerManager);
163
  }
164
165
  /**
166
   * Obtain the data from the logger.
167
   *
168
   * @param \Symfony\Component\HttpFoundation\Request $request
169
   *   The current request. Needed for paging.
170
   * @param string $type
171
   *   The type of top list to retrieve.
172
   *
173
   * @return array
174
   *   The data array.
175
   */
176
  protected function getRowData(Request $request, string $type): array {
177
    // Find _id for the error type.
178
    $templateCollection = $this->watchdog->templateCollection();
179
    $template = $templateCollection->findOne(['type' => $type], ['_id']);
180
    if (empty($template)) {
181
      return [];
182
    }
183
184
    // Find occurrences of error type.
185
    $collectionName = $template['_id'];
186
    $eventCollection = $this->watchdog->eventCollection($collectionName);
187
188
    $counts = $this->group($eventCollection, 'variables.@uri', []);
189
190
    $page = $this->setupPager($request, count($counts));
191
    $skip = $page * $this->itemsPerPage;
192
    $counts = array_slice($counts, $skip, $this->itemsPerPage);
193
194
    return $counts;
195
  }
196
197
  /**
198
   * Command wrapper for removed MongoDB group() method/command.
199
   *
200
   * @param \MongoDB\Collection $collection
201
   *   The collection on which to perform the command.
202
   * @param string $key
203
   *   The grouping key.
204
   * @param array $cond
205
   *   The condition.
206
   *
207
   * @return array
208
   *   An array of stdClass rows with the following properties:
209
   *   - _id: the URL
210
   *   - count: the number of occurrences.
211
   *   It may be empty.
212
   *
213
   * @throws \MongoDB\Driver\Exception\RuntimeException
214
   * @throws \MongoDB\Exception\InvalidArgumentException
215
   * @throws \MongoDB\Exception\UnexpectedValueException
216
   * @throws \MongoDB\Exception\UnsupportedException
217
   */
218
  public function group(Collection $collection, string $key, array $cond): array {
219
    $pipeline = [];
220
    if (!empty($cond)) {
221
      $pipeline[] = ['$match' => $cond];
222
    }
223
    if (!empty($key)) {
224
      $pipeline[] = [
225
        '$group' => [
226
          '_id' => "\$${key}",
227
          'count' => ['$sum' => 1],
228
        ],
229
      ];
230
    }
231
    $pipeline[] = [
232
      '$sort' => [
233
        'count' => -1,
234
        '_id' => 1,
235
      ],
236
    ];
237
238
    // Aggregate always returns a cursor since MongoDB 3.6.
239
    /** @var \MongoDB\Driver\CursorInterface $res */
240
    $res = $collection->aggregate($pipeline);
241
    $res->setTypeMap(static::TYPE_MAP);
242
    $ret = $res->toArray();
243
    return $ret;
244
  }
245
246
}
247