Total Complexity | 67 |
Total Lines | 656 |
Duplicated Lines | 0 % |
Changes | 23 | ||
Bugs | 0 | Features | 0 |
Complex classes like TablesController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TablesController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class TablesController extends Controller |
||
31 | { |
||
32 | // Constants |
||
33 | // ========================================================================= |
||
34 | |||
35 | const SORT_MAP = [ |
||
36 | 'DESC' => SORT_DESC, |
||
37 | 'ASC' => SORT_ASC, |
||
38 | ]; |
||
39 | |||
40 | const ALLOWED_PAGE_INDEX_SORT_FIELDS = [ |
||
41 | 'url', |
||
42 | 'pageLoad', |
||
43 | 'craftDbCnt', |
||
44 | 'craftTwigCnt', |
||
45 | 'craftOtherCnt', |
||
46 | 'craftTotalMemory', |
||
47 | 'cnt', |
||
48 | ]; |
||
49 | |||
50 | const ALLOWED_PAGE_DETAIL_SORT_FIELDS = [ |
||
51 | 'dateCreated', |
||
52 | 'pageLoad', |
||
53 | 'craftDbCnt', |
||
54 | 'craftTwigCnt', |
||
55 | 'craftOtherCnt', |
||
56 | 'craftTotalMemory', |
||
57 | 'device', |
||
58 | 'os', |
||
59 | 'browser', |
||
60 | 'countryCode', |
||
61 | ]; |
||
62 | |||
63 | const ALLOWED_ERRORS_INDEX_SORT_FIELDS = [ |
||
64 | 'url', |
||
65 | 'latestErrorDate', |
||
66 | 'craftCount', |
||
67 | 'boomerangCount', |
||
68 | 'cnt', |
||
69 | ]; |
||
70 | |||
71 | const ALLOWED_ERRORS_DETAIL_SORT_FIELDS = [ |
||
72 | 'dateCreated', |
||
73 | 'pageErrors', |
||
74 | 'device', |
||
75 | 'os', |
||
76 | 'browser', |
||
77 | 'countryCode', |
||
78 | ]; |
||
79 | |||
80 | // Protected Properties |
||
81 | // ========================================================================= |
||
82 | |||
83 | /** |
||
84 | * @var bool|array |
||
85 | */ |
||
86 | protected $allowAnonymous = []; |
||
87 | |||
88 | // Public Methods |
||
89 | // ========================================================================= |
||
90 | |||
91 | /** |
||
92 | * Handle requests for the performance index table |
||
93 | * |
||
94 | * @param string $sort |
||
95 | * @param int $page |
||
96 | * @param int $per_page |
||
97 | * @param string $filter |
||
98 | * @param string $start |
||
99 | * @param string $end |
||
100 | * @param int $siteId |
||
101 | * |
||
102 | * @return Response |
||
103 | * @throws ForbiddenHttpException |
||
104 | * @throws BadRequestHttpException |
||
105 | */ |
||
106 | public function actionPagesIndex( |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * Handle requests for the performance detail table |
||
252 | * |
||
253 | * @param string $sort |
||
254 | * @param int $page |
||
255 | * @param int $per_page |
||
256 | * @param string $filter |
||
257 | * @param string $pageUrl |
||
258 | * @param string $start |
||
259 | * @param string $end |
||
260 | * @param int $siteId |
||
261 | * |
||
262 | * @return Response |
||
263 | * @throws ForbiddenHttpException |
||
264 | * @throws BadRequestHttpException |
||
265 | */ |
||
266 | public function actionPageDetail( |
||
267 | string $start = '', |
||
268 | string $end = '', |
||
269 | string $sort = 'pageLoad|DESC', |
||
270 | int $page = 1, |
||
271 | int $per_page = 20, |
||
272 | $filter = '', |
||
273 | $pageUrl = '', |
||
274 | $siteId = 0 |
||
275 | ): Response { |
||
276 | PermissionHelper::controllerPermissionCheck('webperf:performance'); |
||
277 | $data = []; |
||
278 | $sortField = 'pageLoad'; |
||
279 | $sortType = 'DESC'; |
||
280 | // Add a day since YYYY-MM-DD is really YYYY-MM-DD 00:00:00 |
||
281 | $end = date('Y-m-d', strtotime($end.'+1 day')); |
||
282 | $pageUrl = urldecode($pageUrl); |
||
283 | // Figure out the sorting type |
||
284 | if ($sort !== '') { |
||
285 | if (strpos($sort, '|') === false) { |
||
286 | $sortField = $sort; |
||
287 | } else { |
||
288 | list($sortField, $sortType) = explode('|', $sort); |
||
289 | } |
||
290 | } |
||
291 | $sortType = strtoupper($sortType); |
||
292 | $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC']; |
||
293 | // Validate untrusted data |
||
294 | if (!in_array($sortField, self::ALLOWED_PAGE_DETAIL_SORT_FIELDS, true)) { |
||
295 | throw new BadRequestHttpException(Craft::t('webperf', 'Invalid sort field specified.')); |
||
296 | } |
||
297 | // Query the db table |
||
298 | $offset = ($page - 1) * $per_page; |
||
299 | $query = (new Query()) |
||
300 | ->from(['{{%webperf_data_samples}}']) |
||
301 | ->offset($offset) |
||
302 | ->where(['url' => $pageUrl]) |
||
303 | ->andWhere(['between', 'dateCreated', $start, $end]) |
||
304 | ; |
||
305 | if ((int)$siteId !== 0) { |
||
306 | $query->andWhere(['siteId' => $siteId]); |
||
307 | } |
||
308 | if ($filter !== '') { |
||
309 | $query |
||
310 | ->andWhere(['like', 'device', $filter]) |
||
311 | ->orWhere(['like', 'os', $filter]) |
||
312 | ->orWhere(['like', 'browser', $filter]) |
||
313 | ->orWhere(['like', 'countryCode', $filter]) |
||
314 | ; |
||
315 | } |
||
316 | $query |
||
317 | ->orderBy([$sortField => $sortType]) |
||
318 | ->limit($per_page) |
||
319 | ; |
||
320 | $stats = $query->all(); |
||
321 | if ($stats) { |
||
322 | $user = Craft::$app->getUser()->getIdentity(); |
||
323 | // Compute the largest page load time |
||
324 | $maxTotalPageLoad = 0; |
||
325 | foreach ($stats as &$stat) { |
||
326 | // Determine the stat type |
||
327 | if (!empty($stat['pageLoad']) && !empty($stat['craftTotalMs'])) { |
||
328 | $stat['type'] = 'both'; |
||
329 | } |
||
330 | if (empty($stat['firstByte'])) { |
||
331 | $stat['type'] = 'craft'; |
||
332 | } |
||
333 | if (empty($stat['craftTotalMs'])) { |
||
334 | $stat['type'] = 'frontend'; |
||
335 | } |
||
336 | if ($stat['pageLoad'] > $maxTotalPageLoad) { |
||
337 | $maxTotalPageLoad = (int)$stat['pageLoad']; |
||
338 | } |
||
339 | } |
||
340 | // Massage the stats |
||
341 | foreach ($stats as &$stat) { |
||
342 | if (!empty($stats['dateCreated'])) { |
||
343 | $date = DateTimeHelper::toDateTime($stats['dateCreated']); |
||
344 | $stats['dateCreated'] = $date->format('Y-m-d H:i:s'); |
||
345 | } |
||
346 | $stat['mobile'] = (bool)$stat['mobile']; |
||
347 | $stat['maxTotalPageLoad'] = (int)$maxTotalPageLoad; |
||
348 | // Decode any emojis in the title |
||
349 | if (!empty($stat['title'])) { |
||
350 | $stat['title'] = html_entity_decode($stat['title'], ENT_NOQUOTES, 'UTF-8'); |
||
351 | } |
||
352 | $stat['deleteLink'] = UrlHelper::actionUrl('webperf/data-samples/delete-sample-by-id', [ |
||
353 | 'id' => $stat['id'] |
||
354 | ]); |
||
355 | // Override based on permissions |
||
356 | if (!$user->can('webperf:delete-data-samples')) { |
||
357 | $stat['deleteLink'] = ''; |
||
358 | } |
||
359 | } |
||
360 | // Format the data for the API |
||
361 | $data['data'] = $stats; |
||
362 | $query = (new Query()) |
||
363 | ->select(['[[url]]']) |
||
364 | ->from(['{{%webperf_data_samples}}']) |
||
365 | ->where(['url' => $pageUrl]) |
||
366 | ->andWhere(['between', 'dateCreated', $start, $end]) |
||
367 | ; |
||
368 | if ($filter !== '') { |
||
369 | $query |
||
370 | ->andWhere(['like', 'device', $filter]) |
||
371 | ->orWhere(['like', 'os', $filter]) |
||
372 | ->orWhere(['like', 'browser', $filter]) |
||
373 | ->orWhere(['like', 'countryCode', $filter]) |
||
374 | ; |
||
375 | } |
||
376 | $count = $query->count(); |
||
377 | $data['links']['pagination'] = [ |
||
378 | 'total' => $count, |
||
379 | 'per_page' => $per_page, |
||
380 | 'current_page' => $page, |
||
381 | 'last_page' => ceil($count / $per_page), |
||
382 | 'next_page_url' => null, |
||
383 | 'prev_page_url' => null, |
||
384 | 'from' => $offset + 1, |
||
385 | 'to' => $offset + ($count > $per_page ? $per_page : $count), |
||
386 | ]; |
||
387 | } |
||
388 | |||
389 | return $this->asJson($data); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Handle requests for the pages index table |
||
394 | * |
||
395 | * @param string $sort |
||
396 | * @param int $page |
||
397 | * @param int $per_page |
||
398 | * @param string $filter |
||
399 | * @param string $start |
||
400 | * @param string $end |
||
401 | * @param int $siteId |
||
402 | * |
||
403 | * @return Response |
||
404 | * @throws ForbiddenHttpException |
||
405 | * @throws BadRequestHttpException |
||
406 | */ |
||
407 | public function actionErrorsIndex( |
||
408 | string $start = '', |
||
409 | string $end = '', |
||
410 | string $sort = 'url|DESC', |
||
411 | int $page = 1, |
||
412 | int $per_page = 20, |
||
413 | $filter = '', |
||
414 | $siteId = 0 |
||
415 | ): Response { |
||
416 | PermissionHelper::controllerPermissionCheck('webperf:errors'); |
||
417 | $data = []; |
||
418 | $sortField = 'url'; |
||
419 | $sortType = 'DESC'; |
||
420 | // Add a day since YYYY-MM-DD is really YYYY-MM-DD 00:00:00 |
||
421 | $end = date('Y-m-d', strtotime($end.'+1 day')); |
||
422 | // Figure out the sorting type |
||
423 | if ($sort !== '') { |
||
424 | if (strpos($sort, '|') === false) { |
||
425 | $sortField = $sort; |
||
426 | } else { |
||
427 | list($sortField, $sortType) = explode('|', $sort); |
||
428 | } |
||
429 | } |
||
430 | $sortType = strtoupper($sortType); |
||
431 | $sortType = self::SORT_MAP[$sortType] ?? self::SORT_MAP['DESC']; |
||
432 | // Validate untrusted data |
||
433 | if (!in_array($sortField, self::ALLOWED_ERRORS_INDEX_SORT_FIELDS, true)) { |
||
434 | throw new BadRequestHttpException(Craft::t('webperf', 'Invalid sort field specified.')); |
||
435 | } |
||
436 | $db = Craft::$app->getDb(); |
||
437 | // Query the db table |
||
438 | $offset = ($page - 1) * $per_page; |
||
439 | $query = (new Query()) |
||
440 | ->select([ |
||
441 | '[[url]]', |
||
442 | 'MIN([[title]]) as [[title]]', |
||
443 | 'MAX([[dateCreated]]) as [[latestErrorDate]]', |
||
444 | 'COUNT([[url]]) AS cnt', |
||
445 | ]) |
||
446 | ->from(['{{%webperf_error_samples}}']) |
||
447 | ->offset($offset) |
||
448 | ->where(['between', 'dateCreated', $start, $end]) |
||
449 | ; |
||
450 | if ($db->getIsMysql()) { |
||
451 | $query |
||
452 | ->addSelect([ |
||
453 | 'SUM([[type]] = \'craft\') as [[craftCount]]', |
||
454 | 'SUM([[type]] = \'boomerang\') as [[boomerangCount]]', |
||
455 | ]); |
||
456 | } |
||
457 | if ($db->getIsPgsql()) { |
||
458 | $query |
||
459 | ->addSelect([ |
||
460 | 'SUM(case when [[type]] = \'craft\' then 1 else 0 end) as [[craftCount]]', |
||
461 | 'SUM(case when [[type]] = \'boomerang\' then 1 else 0 end) as [[boomerangCount]]', |
||
462 | ]); |
||
463 | } |
||
464 | if ((int)$siteId !== 0) { |
||
465 | $query->andWhere(['siteId' => $siteId]); |
||
466 | } |
||
467 | if ($filter !== '') { |
||
468 | $query |
||
469 | ->andWhere(['like', 'url', $filter]) |
||
470 | ->orWhere(['like', 'title', $filter]) |
||
471 | ->orWhere(['like', 'pageErrors', $filter]) |
||
472 | ; |
||
473 | } |
||
474 | $query |
||
475 | ->orderBy([$sortField => $sortType]) |
||
476 | ->groupBy('url') |
||
477 | ->limit($per_page) |
||
478 | ; |
||
479 | |||
480 | $stats = $query->all(); |
||
481 | if ($stats) { |
||
482 | $user = Craft::$app->getUser()->getIdentity(); |
||
483 | // Massage the stats |
||
484 | foreach ($stats as &$stat) { |
||
485 | $stat['cnt'] = (int)$stat['cnt']; |
||
486 | $stat['craftCount'] = (int)$stat['craftCount']; |
||
487 | $stat['boomerangCount'] = (int)$stat['boomerangCount']; |
||
488 | // Decode any emojis in the title |
||
489 | if (!empty($stat['title'])) { |
||
490 | $stat['title'] = html_entity_decode($stat['title'], ENT_NOQUOTES, 'UTF-8'); |
||
491 | } |
||
492 | // Set up the appropriate helper links |
||
493 | $stat['deleteLink'] = UrlHelper::actionUrl('webperf/error-samples/delete-samples-by-url', [ |
||
494 | 'pageUrl' => $stat['url'], |
||
495 | 'siteId' => $siteId |
||
496 | ]); |
||
497 | $stat['detailPageUrl'] = UrlHelper::cpUrl('webperf/errors/page-detail', [ |
||
498 | 'pageUrl' => $stat['url'], |
||
499 | 'siteId' => $siteId, |
||
500 | ]); |
||
501 | // Override based on permissions |
||
502 | if (!$user->can('webperf:delete-error-samples')) { |
||
503 | $stat['deleteLink'] = ''; |
||
504 | } |
||
505 | if (!$user->can('webperf:errors-detail')) { |
||
506 | $stat['detailPageUrl'] = ''; |
||
507 | } |
||
508 | } |
||
509 | // Format the data for the API |
||
510 | $data['data'] = $stats; |
||
511 | $query = (new Query()) |
||
512 | ->select(['[[url]]']) |
||
513 | ->from(['{{%webperf_error_samples}}']) |
||
514 | ->groupBy('[[url]]') |
||
515 | ->where(['between', 'dateCreated', $start, $end]) |
||
516 | ; |
||
517 | if ($filter !== '') { |
||
518 | $query |
||
519 | ->andWhere(['like', 'url', $filter]) |
||
520 | ->orWhere(['like', 'title', $filter]) |
||
521 | ->orWhere(['like', 'pageErrors', $filter]) |
||
522 | ; |
||
523 | } |
||
524 | $count = $query->count(); |
||
525 | $data['links']['pagination'] = [ |
||
526 | 'total' => $count, |
||
527 | 'per_page' => $per_page, |
||
528 | 'current_page' => $page, |
||
529 | 'last_page' => ceil($count / $per_page), |
||
530 | 'next_page_url' => null, |
||
531 | 'prev_page_url' => null, |
||
532 | 'from' => $offset + 1, |
||
533 | 'to' => $offset + ($count > $per_page ? $per_page : $count), |
||
534 | ]; |
||
535 | } |
||
536 | |||
537 | return $this->asJson($data); |
||
538 | } |
||
539 | |||
540 | |||
541 | /** |
||
542 | * Handle requests for the performance detail table |
||
543 | * |
||
544 | * @param string $sort |
||
545 | * @param int $page |
||
546 | * @param int $per_page |
||
547 | * @param string $filter |
||
548 | * @param string $pageUrl |
||
549 | * @param string $start |
||
550 | * @param string $end |
||
551 | * @param int $siteId |
||
552 | * |
||
553 | * @return Response |
||
554 | * @throws ForbiddenHttpException |
||
555 | * @throws BadRequestHttpException |
||
556 | */ |
||
557 | public function actionErrorsDetail( |
||
686 | } |
||
687 | |||
688 | // Protected Methods |
||
691 |