Passed
Push — master ( 26ea1a...43abdf )
by Matthew
07:12
created

TrendsController   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 417
Duplicated Lines 0 %

Test Coverage

Coverage 87.88%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 256
c 1
b 1
f 0
dl 0
loc 417
ccs 232
cts 264
cp 0.8788
rs 3.04
wmc 67

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getJobTImingsOdmMapReduce() 0 31 3
B calculateTimings() 0 43 6
A getJobTimingsOdm() 0 44 4
A getDateFormat() 0 15 6
B addJobTimingsDateInfo() 0 39 6
A getOrmGroupBy() 0 20 6
A getTimingsAction() 0 15 4
B getAggregationResultDateStr() 0 24 6
A formatOrmDateTime() 0 25 5
A getRegexDate() 0 20 6
A getJobTimingsOrm() 0 34 3
A getTimingsDatesAdjusted() 0 17 4
A setTimingsData() 0 10 5
A strPadLeft() 0 3 1
A trendsAction() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like TrendsController 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 TrendsController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Dtc\QueueBundle\Controller;
4
5
use Doctrine\ODM\MongoDB\DocumentManager;
6
use Doctrine\ORM\EntityManager;
7
use Dtc\QueueBundle\Doctrine\DoctrineJobTimingManager;
8
use Dtc\QueueBundle\Model\JobTiming;
9
use Dtc\QueueBundle\ODM\JobManager;
10
use Dtc\QueueBundle\ODM\JobTimingManager;
11
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
12
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
13
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
14
use Symfony\Component\HttpFoundation\JsonResponse;
15
use Symfony\Component\HttpFoundation\Request;
16
17
class TrendsController extends Controller
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Bundle\Framework...e\Controller\Controller has been deprecated: since Symfony 4.2, use "Symfony\Bundle\FrameworkBundle\Controller\AbstractController" instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

17
class TrendsController extends /** @scrutinizer ignore-deprecated */ Controller
Loading history...
18
{
19
    use ControllerTrait;
20
21
    /**
22
     * Show a graph of job trends.
23
     *
24
     * @Route("/trends", name="dtc_queue_trends")
25
     * @Template("@DtcQueue/Queue/trends.html.twig")
26
     */
27 1
    public function trendsAction()
28
    {
29 1
        $recordTimings = $this->container->getParameter('dtc_queue.timings.record');
30 1
        $foundYearFunction = class_exists('Oro\ORM\Query\AST\Platform\Functions\Mysql\Year') || class_exists('DoctrineExtensions\Query\Mysql\Year');
31 1
        $params = ['record_timings' => $recordTimings, 'states' => JobTiming::getStates(), 'found_year_function' => $foundYearFunction];
32 1
        $this->addCssJs($params);
33
34 1
        return $params;
35
    }
36
37
    /**
38
     * @Route("/timings", name="dtc_queue_timings")
39
     */
40 1
    public function getTimingsAction(Request $request)
41
    {
42 1
        $begin = $request->query->get('begin');
43 1
        $end = $request->query->get('end');
44 1
        $type = $request->query->get('type', 'HOUR');
45 1
        $beginDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $begin, new \DateTimeZone(date_default_timezone_get())) ?: null;
46 1
        $endDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $end, new \DateTimeZone(date_default_timezone_get())) ?: \Dtc\QueueBundle\Util\Util::getMicrotimeDateTime();
47
48 1
        $recordTimings = $this->container->getParameter('dtc_queue.timings.record');
49 1
        $params = [];
50 1
        if ($recordTimings) {
51 1
            $params = $this->calculateTimings($type, $beginDate, $endDate);
52
        }
53
54 1
        return new JsonResponse($params);
55
    }
56
57
    /**
58
     * @param \DateTime|null $beginDate
59
     * @param \DateTime      $endDate
60
     */
61 1
    protected function calculateTimings($type, $beginDate, $endDate)
62
    {
63 1
        $params = [];
64 1
        $this->validateJobTimingManager();
65
66
        /** @var DoctrineJobTimingManager $jobTimingManager */
67 1
        $jobTimingManager = $this->get('dtc_queue.manager.job_timing');
68 1
        if ($jobTimingManager instanceof JobTimingManager) {
69 1
            $timings = $this->getJobTimingsOdm($type, $endDate, $beginDate);
70
        } else {
71 1
            $timings = $this->getJobTimingsOrm($type, $endDate, $beginDate);
72
        }
73
74 1
        $timingStates = JobTiming::getStates();
75 1
        $timingsDates = [];
76 1
        foreach (array_keys($timingStates) as $state) {
77 1
            if (!isset($timings[$state])) {
78 1
                continue;
79
            }
80 1
            $timingsData = $timings[$state];
81 1
            $timingsDates = array_unique(array_merge(array_keys($timingsData), $timingsDates));
82
        }
83
84 1
        $format = $this->getDateFormat($type);
85 1
        usort($timingsDates, function ($date1str, $date2str) use ($format) {
86
            $date1 = \DateTime::createFromFormat($format, $date1str, new \DateTimeZone(date_default_timezone_get()));
87
            $date2 = \DateTime::createFromFormat($format, $date2str, new \DateTimeZone(date_default_timezone_get()));
88
            if (!$date2) {
89
                return false;
90
            }
91
            if (!$date1) {
92
                return false;
93
            }
94
95
            return $date1 > $date2;
96 1
        });
97
98 1
        $timingsDatesAdjusted = $this->getTimingsDatesAdjusted($timingsDates, $format);
99 1
        $this->setTimingsData($timingStates, $timings, $timingsDates, $params);
100 1
        $params['timings_dates'] = $timingsDates;
101 1
        $params['timings_dates_rfc3339'] = $timingsDatesAdjusted;
102
103 1
        return $params;
104
    }
105
106
    /**
107
     * Timings offset by timezone if necessary.
108
     *
109
     * @param array  $timingsDates
110
     * @param string $format
111
     *
112
     * @return array
113
     */
114 1
    protected function getTimingsDatesAdjusted(array $timingsDates, $format)
115
    {
116 1
        $timezoneOffset = $this->container->getParameter('dtc_queue.timings.timezone_offset');
117 1
        $timingsDatesAdjusted = [];
118 1
        foreach ($timingsDates as $dateStr) {
119 1
            $date = \DateTime::createFromFormat($format, $dateStr, new \DateTimeZone(date_default_timezone_get()));
120 1
            if (0 !== $timezoneOffset) {
121
                // This may too simplistic in areas that observe DST - does the database or PHP code observe DST?
122
                $date->setTimestamp($date->getTimestamp() + ($timezoneOffset * 3600));
123
            }
124 1
            if (false === $date) {
125
                throw new \InvalidArgumentException("'$date' is not in the right format: ".DATE_RFC3339);
126
            }
127 1
            $timingsDatesAdjusted[] = $date->format(DATE_RFC3339);
128
        }
129
130 1
        return $timingsDatesAdjusted;
131
    }
132
133 1
    protected function setTimingsData(array $timingStates, array $timings, array $timingsDates, array &$params)
134
    {
135 1
        foreach (array_keys($timingStates) as $state) {
136 1
            if (!isset($timings[$state])) {
137 1
                continue;
138
            }
139
140 1
            $timingsData = $timings[$state];
141 1
            foreach ($timingsDates as $date) {
142 1
                $params['timings_data_'.$state][] = isset($timingsData[$date]) ? $timingsData[$date] : 0;
143
            }
144
        }
145 1
    }
146
147
    /**
148
     * @param string $type
149
     * @param \Doctrine\ODM\MongoDB\Aggregation\Expr $expr
150
     * @return mixed
151
     */
152 1
    protected function addJobTimingsDateInfo($type, $expr) {
153 1
        switch ($type) {
154 1
            case 'YEAR':
155 1
                return $expr->field('year')
156 1
                    ->year('$finishedAt');
157 1
            case 'MONTH':
158 1
                return $expr->field('year')
159 1
                    ->year('$finishedAt')
160 1
                    ->field('month')
161 1
                    ->month('$finishedAt');
162 1
            case 'DAY':
163 1
                return $expr->field('year')
164 1
                    ->year('$finishedAt')
165 1
                    ->field('month')
166 1
                    ->month('$finishedAt')
167 1
                    ->field('day')
168 1
                    ->dayOfMonth('$finishedAt');
169 1
            case 'HOUR':
170 1
                return $expr->field('year')
171 1
                    ->year('$finishedAt')
172 1
                    ->field('month')
173 1
                    ->month('$finishedAt')
174 1
                    ->field('day')
175 1
                    ->dayOfMonth('$finishedAt')
176 1
                    ->field('hour')
177 1
                    ->hour('$finishedAt');
178 1
            case 'MINUTE':
179 1
                return $expr->field('year')
180 1
                    ->year('$finishedAt')
181 1
                    ->field('month')
182 1
                    ->month('$finishedAt')
183 1
                    ->field('day')
184 1
                    ->dayOfMonth('$finishedAt')
185 1
                    ->field('hour')
186 1
                    ->hour('$finishedAt')
187 1
                    ->field('minute')
188 1
                    ->minute('$finishedAt');
189
            default:
190
                throw new \InvalidArgumentException("Unknown type $type");
191
        }
192
    }
193
194
    protected function getJobTImingsOdmMapReduce($builder, $type, \DateTime $end, \DateTime $begin = null) {
195
        $regexInfo = $this->getRegexDate($type);
196
        if (!$begin) {
197
            $begin = clone $end;
198
            $begin->sub($regexInfo['interval']);
199
        }
200
201
        $mapFunc = 'function() {
202
            var dateStr = this.finishedAt.toISOString();
203
            dateStr = dateStr.replace(/'.$regexInfo['replace']['regex']."/,'".$regexInfo['replace']['replacement']."');
204
            var dateBegin = new Date('".$begin->format('c')."');
205
            var dateEnd = new Date('".$end->format('c')."');
206
            if (this.finishedAt >= dateBegin && this.finishedAt <= dateEnd) {
207
                var result = {};
208
                result[dateStr] = 1;
209
                emit(this.status, result);
210
            }
211
        }";
212
213
        // Run a map reduce function get worker and status break down
214
        $reduceFunc = JobManager::REDUCE_FUNCTION;
215
        $builder->map($mapFunc)
216
            ->reduce($reduceFunc);
217
        $query = $builder->getQuery();
218
        $results = $query->execute();
219
        $resultHash = [];
220
        foreach ($results as $info) {
221
            $resultHash[$info['_id']] = $info['value'];
222
        }
223
224
        return $resultHash;
225
    }
226
227 1
    protected function getJobTimingsOdm($type, \DateTime $end, \DateTime $begin = null)
228
    {
229
        /** @var JobTimingManager $runManager */
230 1
        $jobTimingManager = $this->get('dtc_queue.manager.job_timing');
231 1
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
232
233
        /** @var DocumentManager $documentManager */
234 1
        $documentManager = $jobTimingManager->getObjectManager();
235
236 1
        $builder = $documentManager->createQueryBuilder($jobTimingClass);
237 1
        if (method_exists($builder, 'map')) {
238
            return $this->getJobTimingsOdmMapReduce($builder, $type, $end, $begin);
239
        }
240
241 1
        $regexInfo = $this->getRegexDate($type);
242 1
        if (!$begin) {
243 1
            $begin = clone $end;
244 1
            $begin->sub($regexInfo['interval']);
245
        }
246
247 1
        $aggregationBuilder = $documentManager->createAggregationBuilder($jobTimingClass);
248 1
        $expr = $this->addJobTimingsDateInfo($type, $aggregationBuilder->expr());
249 1
        $expr = $expr->field('status')
250 1
                ->expression('$status');
251
252
        $aggregationBuilder
253 1
            ->match()
254 1
                ->field('finishedAt')
255 1
                ->gte($begin)
256 1
                ->lte($end)
257 1
            ->group()
258 1
                    ->field('id')
259 1
                    ->expression($expr)
260 1
                    ->field('value')
261 1
                    ->sum(1);
262
263 1
        $results = $aggregationBuilder->execute();
264 1
        $resultHash = [];
265 1
        foreach ($results as $result) {
266 1
            $key = $result['_id']['status'];
267 1
            $dateStr = $this->getAggregationResultDateStr($type, $result['_id']);
268 1
            $resultHash[$key][$dateStr] = $result['value'];
269
        }
270 1
        return $resultHash;
271
    }
272
273
    /**
274
     * Formats the aggregation result into the desired date string format.
275
     * @param string $type
276
     * @param array $result
277
     * @return string
278
     */
279 1
    protected function getAggregationResultDateStr($type, array $result)
280
    {
281 1
        switch ($type) {
282 1
            case 'YEAR':
283 1
                return $result['year'];
284 1
            case 'MONTH':
285 1
                return "{$result['year']}-" . str_pad($result['month'], 2, "0", STR_PAD_LEFT);
286 1
            case 'DAY':
287 1
                $str = "{$result['year']}-" . str_pad($result['month'], 2, "0", STR_PAD_LEFT);
288 1
                $str .= '-' . str_pad($result['day'], 2, "0", STR_PAD_LEFT);
289 1
                return $str;
290 1
            case 'HOUR':
291 1
                $str = "{$result['year']}-" . str_pad($result['month'], 2, "0", STR_PAD_LEFT);
292 1
                $str .= '-' . str_pad($result['day'], 2, "0", STR_PAD_LEFT);
293 1
                $str .= ' ' . str_pad($result['hour'], 2, "0", STR_PAD_LEFT);
294 1
                return $str;
295 1
            case 'MINUTE':
296 1
                $str = "{$result['year']}-" . str_pad($result['month'], 2, "0", STR_PAD_LEFT);
297 1
                $str .= '-' . str_pad($result['day'], 2, "0", STR_PAD_LEFT);
298 1
                $str .= ' ' . str_pad($result['hour'], 2, "0", STR_PAD_LEFT);
299 1
                $str .= ':' . str_pad($result['minute'], 2, "0", STR_PAD_LEFT);
300 1
                return $str;
301
            default:
302
                throw new \InvalidArgumentException("Invalid date format type '$type''");
303
        }
304
    }
305
306 1
    protected function getDateFormat($type)
307
    {
308 1
        switch ($type) {
309 1
            case 'YEAR':
310 1
                return 'Y';
311 1
            case 'MONTH':
312 1
                return 'Y-m';
313 1
            case 'DAY':
314 1
                return 'Y-m-d';
315 1
            case 'HOUR':
316 1
                return 'Y-m-d H';
317 1
            case 'MINUTE':
318 1
                return 'Y-m-d H:i';
319
            default:
320
                throw new \InvalidArgumentException("Invalid date format type '$type''");
321
        }
322
    }
323
324 1
    protected function getRegexDate($type)
325
    {
326 1
        switch ($type) {
327 1
            case 'YEAR':
328 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1'],
329 1
                    'interval' => new \DateInterval('P10Y'), ];
330 1
            case 'MONTH':
331 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2'],
332 1
                    'interval' => new \DateInterval('P12M'), ];
333 1
            case 'DAY':
334 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3'],
335 1
                    'interval' => new \DateInterval('P31D'), ];
336 1
            case 'HOUR':
337 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4'],
338 1
                    'interval' => new \DateInterval('PT24H'), ];
339 1
            case 'MINUTE':
340 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4:$5'],
341 1
                    'interval' => new \DateInterval('PT3600S'), ];
342
        }
343
        throw new \InvalidArgumentException("Invalid type $type");
344
    }
345
346 1
    protected function getOrmGroupBy($type)
347
    {
348 1
        switch ($type) {
349 1
            case 'YEAR':
350 1
                return ['groupby' => 'YEAR(j.finishedAt)',
351 1
                    'interval' => new \DateInterval('P10Y'), ];
352 1
            case 'MONTH':
353 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt))',
354 1
                    'interval' => new \DateInterval('P12M'), ];
355 1
            case 'DAY':
356 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt))',
357 1
                    'interval' => new \DateInterval('P31D'), ];
358 1
            case 'HOUR':
359 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt))',
360 1
                    'interval' => new \DateInterval('PT24H'), ];
361 1
            case 'MINUTE':
362 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt),\':\',MINUTE(j.finishedAt))',
363 1
                    'interval' => new \DateInterval('PT3600S'), ];
364
        }
365
        throw new \InvalidArgumentException("Invalid type $type");
366
    }
367
368 1
    protected function getJobTimingsOrm($type, \DateTime $end, \DateTime $begin = null)
369
    {
370
        /** @var JobTimingManager $jobTimingManager */
371 1
        $jobTimingManager = $this->get('dtc_queue.manager.job_timing');
372 1
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
373
        /** @var EntityManager $entityManager */
374 1
        $entityManager = $jobTimingManager->getObjectManager();
375
376 1
        $groupByInfo = $this->getOrmGroupBy($type);
377
378 1
        if (!$begin) {
379 1
            $begin = clone $end;
380 1
            $begin->sub($groupByInfo['interval']);
381
        }
382
383 1
        $queryBuilder = $entityManager->createQueryBuilder()->select("j.status as status, count(j.finishedAt) as thecount, {$groupByInfo['groupby']} as thedate")
384 1
            ->from($jobTimingClass, 'j')
385 1
            ->where('j.finishedAt <= :end')
386 1
            ->andWhere('j.finishedAt >= :begin')
387 1
            ->setParameter(':end', $end)
388 1
            ->setParameter(':begin', $begin)
389 1
            ->groupBy('status')
390 1
            ->addGroupBy('thedate');
391
392
        $result = $queryBuilder
393 1
            ->getQuery()->getArrayResult();
394
395 1
        $resultHash = [];
396 1
        foreach ($result as $row) {
397 1
            $date = $this->formatOrmDateTime($type, $row['thedate']);
398 1
            $resultHash[$row['status']][$date] = intval($row['thecount']);
399
        }
400
401 1
        return $resultHash;
402
    }
403
404 1
    protected function strPadLeft($str)
405
    {
406 1
        return str_pad($str, 2, '0', STR_PAD_LEFT);
407
    }
408
409 1
    protected function formatOrmDateTime($type, $str)
410
    {
411 1
        switch ($type) {
412 1
            case 'MONTH':
413 1
                $parts = explode('-', $str);
414
415 1
                return $parts[0].'-'.$this->strPadLeft($parts[1]);
416 1
            case 'DAY':
417 1
                $parts = explode('-', $str);
418
419 1
                return $parts[0].'-'.$this->strPadLeft($parts[1]).'-'.$this->strPadLeft($parts[2]);
420 1
            case 'HOUR':
421 1
                $parts = explode(' ', $str);
422 1
                $dateParts = explode('-', $parts[0]);
423
424 1
                return $dateParts[0].'-'.$this->strPadLeft($dateParts[1]).'-'.$this->strPadLeft($dateParts[2]).' '.$this->strPadLeft($parts[1]);
425 1
            case 'MINUTE':
426 1
                $parts = explode(' ', $str);
427 1
                $dateParts = explode('-', $parts[0]);
428 1
                $timeParts = explode(':', $parts[1]);
429
430 1
                return $dateParts[0].'-'.$this->strPadLeft($dateParts[1]).'-'.$this->strPadLeft($dateParts[2]).' '.$this->strPadLeft($timeParts[0]).':'.$this->strPadLeft($timeParts[1]);
431
        }
432
433 1
        return $str;
434
    }
435
}
436