Passed
Push — master ( 1afe5c...e9653b )
by Matthew
11:56 queued 05:32
created

TrendsController   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Test Coverage

Coverage 91.94%

Importance

Changes 0
Metric Value
eloc 168
dl 0
loc 298
ccs 171
cts 186
cp 0.9194
rs 8.4
c 0
b 0
f 0
wmc 50

12 Methods

Rating   Name   Duplication   Size   Complexity  
A strPadLeft() 0 3 1
A getOrmGroupBy() 0 20 6
A formatOrmDateTime() 0 25 5
A getRegexDate() 0 20 6
A setTimingsData() 0 10 5
A trendsAction() 0 7 1
B calculateTimings() 0 43 6
A getJobTimingsOdm() 0 39 3
A getDateFormat() 0 15 6
A getTimingsAction() 0 15 4
A getJobTimingsOrm() 0 34 3
A getTimingsDatesAdjusted() 0 17 4

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 {@see 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
        $params = ['record_timings' => $recordTimings, 'states' => JobTiming::getStates()];
31 1
        $this->addCssJs($params);
32
33 1
        return $params;
34
    }
35
36
    /**
37
     * @Route("/timings", name="dtc_queue_timings")
38
     */
39 1
    public function getTimingsAction(Request $request)
40
    {
41 1
        $begin = $request->query->get('begin');
42 1
        $end = $request->query->get('end');
43 1
        $type = $request->query->get('type', 'HOUR');
44 1
        $beginDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $begin, new \DateTimeZone(date_default_timezone_get())) ?: null;
45 1
        $endDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $end, new \DateTimeZone(date_default_timezone_get())) ?: \Dtc\QueueBundle\Util\Util::getMicrotimeDateTime();
46
47 1
        $recordTimings = $this->container->getParameter('dtc_queue.timings.record');
48 1
        $params = [];
49 1
        if ($recordTimings) {
50 1
            $params = $this->calculateTimings($type, $beginDate, $endDate);
51 1
        }
52
53 1
        return new JsonResponse($params);
54
    }
55
56
    /**
57
     * @param \DateTime|null $beginDate
58
     * @param \DateTime      $endDate
59
     */
60 1
    protected function calculateTimings($type, $beginDate, $endDate)
61
    {
62 1
        $params = [];
63 1
        $this->validateJobTimingManager();
64
65
        /** @var DoctrineJobTimingManager $jobTimingManager */
66 1
        $jobTimingManager = $this->get('dtc_queue.manager.job_timing');
67 1
        if ($jobTimingManager instanceof JobTimingManager) {
68 1
            $timings = $this->getJobTimingsOdm($type, $endDate, $beginDate);
69 1
        } else {
70 1
            $timings = $this->getJobTimingsOrm($type, $endDate, $beginDate);
71
        }
72
73 1
        $timingStates = JobTiming::getStates();
74 1
        $timingsDates = [];
75 1
        foreach (array_keys($timingStates) as $state) {
76 1
            if (!isset($timings[$state])) {
77 1
                continue;
78
            }
79 1
            $timingsData = $timings[$state];
80 1
            $timingsDates = array_unique(array_merge(array_keys($timingsData), $timingsDates));
81 1
        }
82
83 1
        $format = $this->getDateFormat($type);
84 1
        usort($timingsDates, function ($date1str, $date2str) use ($format) {
85
            $date1 = \DateTime::createFromFormat($format, $date1str, new \DateTimeZone(date_default_timezone_get()));
86
            $date2 = \DateTime::createFromFormat($format, $date2str, new \DateTimeZone(date_default_timezone_get()));
87
            if (!$date2) {
88
                return false;
89
            }
90
            if (!$date1) {
91
                return false;
92
            }
93
94
            return $date1 > $date2;
95 1
        });
96
97 1
        $timingsDatesAdjusted = $this->getTimingsDatesAdjusted($timingsDates, $format);
98 1
        $this->setTimingsData($timingStates, $timings, $timingsDates, $params);
99 1
        $params['timings_dates'] = $timingsDates;
100 1
        $params['timings_dates_rfc3339'] = $timingsDatesAdjusted;
101
102 1
        return $params;
103
    }
104
105
    /**
106
     * Timings offset by timezone if necessary.
107
     *
108
     * @param array  $timingsDates
109
     * @param string $format
110
     *
111
     * @return array
112
     */
113 1
    protected function getTimingsDatesAdjusted(array $timingsDates, $format)
114
    {
115 1
        $timezoneOffset = $this->container->getParameter('dtc_queue.timings.timezone_offset');
116 1
        $timingsDatesAdjusted = [];
117 1
        foreach ($timingsDates as $dateStr) {
118 1
            $date = \DateTime::createFromFormat($format, $dateStr, new \DateTimeZone(date_default_timezone_get()));
119 1
            if (0 !== $timezoneOffset) {
120
                // This may too simplistic in areas that observe DST - does the database or PHP code observe DST?
121
                $date->setTimestamp($date->getTimestamp() + ($timezoneOffset * 3600));
122
            }
123 1
            if (false === $date) {
124
                throw new \InvalidArgumentException("'$date' is not in the right format: ".DATE_RFC3339);
125
            }
126 1
            $timingsDatesAdjusted[] = $date->format(DATE_RFC3339);
127 1
        }
128
129 1
        return $timingsDatesAdjusted;
130
    }
131
132 1
    protected function setTimingsData(array $timingStates, array $timings, array $timingsDates, array &$params)
133
    {
134 1
        foreach (array_keys($timingStates) as $state) {
135 1
            if (!isset($timings[$state])) {
136 1
                continue;
137
            }
138
139 1
            $timingsData = $timings[$state];
140 1
            foreach ($timingsDates as $date) {
141 1
                $params['timings_data_'.$state][] = isset($timingsData[$date]) ? $timingsData[$date] : 0;
142 1
            }
143 1
        }
144 1
    }
145
146 1
    protected function getJobTimingsOdm($type, \DateTime $end, \DateTime $begin = null)
147
    {
148
        /** @var JobTimingManager $runManager */
149 1
        $jobTimingManager = $this->get('dtc_queue.manager.job_timing');
150 1
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
151
152
        /** @var DocumentManager $documentManager */
153 1
        $documentManager = $jobTimingManager->getObjectManager();
154
155 1
        $regexInfo = $this->getRegexDate($type);
156 1
        if (!$begin) {
157 1
            $begin = clone $end;
158 1
            $begin->sub($regexInfo['interval']);
159 1
        }
160
161
        // Run a map reduce function get worker and status break down
162
        $mapFunc = 'function() {
163
            var dateStr = this.finishedAt.toISOString();
164 1
            dateStr = dateStr.replace(/'.$regexInfo['replace']['regex']."/,'".$regexInfo['replace']['replacement']."');
165 1
            var dateBegin = new Date('".$begin->format('c')."');
166 1
            var dateEnd = new Date('".$end->format('c')."');
167
            if (this.finishedAt >= dateBegin && this.finishedAt <= dateEnd) {
168
                var result = {};
169
                result[dateStr] = 1;
170
                emit(this.status, result);
171
            }
172 1
        }";
173 1
        $reduceFunc = JobManager::REDUCE_FUNCTION;
174 1
        $builder = $documentManager->createQueryBuilder($jobTimingClass);
175 1
        $builder->map($mapFunc)
176 1
            ->reduce($reduceFunc);
177 1
        $query = $builder->getQuery();
178 1
        $results = $query->execute();
179 1
        $resultHash = [];
180 1
        foreach ($results as $info) {
181 1
            $resultHash[$info['_id']] = $info['value'];
182 1
        }
183
184 1
        return $resultHash;
185
    }
186
187 1
    protected function getDateFormat($type)
188
    {
189
        switch ($type) {
190 1
            case 'YEAR':
191 1
                return 'Y';
192 1
            case 'MONTH':
193 1
                return 'Y-m';
194 1
            case 'DAY':
195 1
                return 'Y-m-d';
196 1
            case 'HOUR':
197 1
                return 'Y-m-d H';
198 1
            case 'MINUTE':
199 1
                return 'Y-m-d H:i';
200
            default:
201
                throw new \InvalidArgumentException("Invalid date format type '$type''");
202
        }
203
    }
204
205 1
    protected function getRegexDate($type)
206
    {
207
        switch ($type) {
208 1
            case 'YEAR':
209 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1'],
210 1
                    'interval' => new \DateInterval('P10Y'), ];
211 1
            case 'MONTH':
212 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2'],
213 1
                    'interval' => new \DateInterval('P12M'), ];
214 1
            case 'DAY':
215 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3'],
216 1
                    'interval' => new \DateInterval('P31D'), ];
217 1
            case 'HOUR':
218 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4'],
219 1
                    'interval' => new \DateInterval('PT24H'), ];
220 1
            case 'MINUTE':
221 1
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4:$5'],
222 1
                    'interval' => new \DateInterval('PT3600S'), ];
223
        }
224
        throw new \InvalidArgumentException("Invalid type $type");
225
    }
226
227 1
    protected function getOrmGroupBy($type)
228
    {
229
        switch ($type) {
230 1
            case 'YEAR':
231 1
                return ['groupby' => 'YEAR(j.finishedAt)',
232 1
                    'interval' => new \DateInterval('P10Y'), ];
233 1
            case 'MONTH':
234 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt))',
235 1
                    'interval' => new \DateInterval('P12M'), ];
236 1
            case 'DAY':
237 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt))',
238 1
                    'interval' => new \DateInterval('P31D'), ];
239 1
            case 'HOUR':
240 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt))',
241 1
                    'interval' => new \DateInterval('PT24H'), ];
242 1
            case 'MINUTE':
243 1
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt),\':\',MINUTE(j.finishedAt))',
244 1
                    'interval' => new \DateInterval('PT3600S'), ];
245
        }
246
        throw new \InvalidArgumentException("Invalid type $type");
247
    }
248
249 1
    protected function getJobTimingsOrm($type, \DateTime $end, \DateTime $begin = null)
250
    {
251
        /** @var JobTimingManager $jobTimingManager */
252 1
        $jobTimingManager = $this->get('dtc_queue.manager.job_timing');
253 1
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
254
        /** @var EntityManager $entityManager */
255 1
        $entityManager = $jobTimingManager->getObjectManager();
256
257 1
        $groupByInfo = $this->getOrmGroupBy($type);
258
259 1
        if (!$begin) {
260 1
            $begin = clone $end;
261 1
            $begin->sub($groupByInfo['interval']);
262 1
        }
263
264 1
        $queryBuilder = $entityManager->createQueryBuilder()->select("j.status as status, count(j.finishedAt) as thecount, {$groupByInfo['groupby']} as thedate")
265 1
            ->from($jobTimingClass, 'j')
266 1
            ->where('j.finishedAt <= :end')
267 1
            ->andWhere('j.finishedAt >= :begin')
268 1
            ->setParameter(':end', $end)
269 1
            ->setParameter(':begin', $begin)
270 1
            ->groupBy('status')
271 1
            ->addGroupBy('thedate');
272
273
        $result = $queryBuilder
274 1
            ->getQuery()->getArrayResult();
275
276 1
        $resultHash = [];
277 1
        foreach ($result as $row) {
278 1
            $date = $this->formatOrmDateTime($type, $row['thedate']);
279 1
            $resultHash[$row['status']][$date] = intval($row['thecount']);
280 1
        }
281
282 1
        return $resultHash;
283
    }
284
285 1
    protected function strPadLeft($str)
286
    {
287 1
        return str_pad($str, 2, '0', STR_PAD_LEFT);
288
    }
289
290 1
    protected function formatOrmDateTime($type, $str)
291
    {
292
        switch ($type) {
293 1
            case 'MONTH':
294 1
                $parts = explode('-', $str);
295
296 1
                return $parts[0].'-'.$this->strPadLeft($parts[1]);
297 1
            case 'DAY':
298 1
                $parts = explode('-', $str);
299
300 1
                return $parts[0].'-'.$this->strPadLeft($parts[1]).'-'.$this->strPadLeft($parts[2]);
301 1
            case 'HOUR':
302 1
                $parts = explode(' ', $str);
303 1
                $dateParts = explode('-', $parts[0]);
304
305 1
                return $dateParts[0].'-'.$this->strPadLeft($dateParts[1]).'-'.$this->strPadLeft($dateParts[2]).' '.$this->strPadLeft($parts[1]);
306 1
            case 'MINUTE':
307 1
                $parts = explode(' ', $str);
308 1
                $dateParts = explode('-', $parts[0]);
309 1
                $timeParts = explode(':', $parts[1]);
310
311 1
                return $dateParts[0].'-'.$this->strPadLeft($dateParts[1]).'-'.$this->strPadLeft($dateParts[2]).' '.$this->strPadLeft($timeParts[0]).':'.$this->strPadLeft($timeParts[1]);
312
        }
313
314 1
        return $str;
315
    }
316
}
317