AlertCountsEndpointController   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 243
Duplicated Lines 6.58 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 16
loc 243
rs 10
c 0
b 0
f 0
wmc 25
lcom 1
cbo 6

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getVictories() 0 4 1
A getDominations() 0 4 1
B getCountData() 0 39 5
B getDailyTotals() 8 27 3
B getDailyTotalsByServer() 8 30 4
B getDailyMetrics() 0 41 4
B generateFactionCaseSql() 0 29 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Ps2alerts\Api\Controller\Endpoint\Alerts;
4
5
use Ps2alerts\Api\Repository\AlertRepository;
6
use Ps2alerts\Api\Transformer\AlertTotalTransformer;
7
use Ps2alerts\Api\Transformer\AlertTransformer;
8
use Ps2alerts\Api\Exception\InvalidArgumentException;
9
use Psr\Http\Message\ResponseInterface;
10
11
class AlertCountsEndpointController extends AlertEndpointController
12
{
13
    /**
14
     * Construct
15
     *
16
     * @param AlertRepository   $repository
17
     * @param AlertTransformer $transformer
18
     */
19
    public function __construct(
20
        AlertRepository  $repository,
21
        AlertTransformer $transformer
22
    ) {
23
        $this->repository  = $repository;
24
        $this->transformer = $transformer;
25
    }
26
27
    /**
28
     * Returns the victories of each faction and the totals
29
     *
30
     * @return ResponseInterface
31
     */
32
    public function getVictories()
33
    {
34
        return $this->getCountData('victories');
35
    }
36
37
    /**
38
     * Returns the dominations of each faction and the totals
39
     *
40
     * @return ResponseInterface
41
     */
42
    public function getDominations()
43
    {
44
        return $this->getCountData('dominations');
45
    }
46
47
    /**
48
     * Gets the required count data and returns
49
     *
50
     * @param  string $mode The type of data we're getting (victory / domination)
51
     *
52
     * @return \Psr\Http\Message\ResponseInterface
53
     */
54
    public function getCountData($mode)
0 ignored issues
show
Coding Style introduced by
getCountData uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
55
    {
56
        try {
57
            $servers = $this->validateQueryStringArguments($_GET['servers'], 'servers');
58
            $zones   = $this->validateQueryStringArguments($_GET['zones'], 'zones');
59
            $dates   = $this->validateQueryStringArguments($_GET['dates'], 'dates');
60
        } catch (InvalidArgumentException $e) {
61
            return $this->respondWithError($e->getMessage(), self::CODE_WRONG_ARGS);
62
        }
63
64
        $counts = [];
65
        $serversExploded = explode(',', $servers);
66
        $fractal = $this->getContainer()->get('FractalUtility');
67
68
        foreach ($serversExploded as $server) {
69
            $query = $this->repository->newQuery();
70
71
            $sql = $this->generateFactionCaseSql($server, $zones, $mode);
72
73
            $query->cols([$sql]);
74
75
            if (! empty($dates)) {
76
                $this->addDateRangeWhereClause($dates, $query, true);
77
            }
78
79
            $data = $this->repository->readRaw($query->getStatement(), true);
80
            $data['total'] = array_sum($data);
81
82
            if ($mode === 'domination') {
83
                $data['draw'] = null; // Since domination draws are not possible, set it to null
84
            }
85
86
            // Build each section of the final response using the transformer
87
            $counts['data'][$server] = $fractal->createItem($data, new AlertTotalTransformer);
88
        }
89
90
        // Return the now formatted array to the response
91
        return $this->respondWithData($counts);
92
    }
93
94
    /**
95
     * Get Daily totals over a range of dates
96
     *
97
     * @throws \Ps2alerts\Api\Exception\InvalidArgumentException
98
     * @return \Psr\Http\Message\ResponseInterface
99
     */
100
    public function getDailyTotals()
0 ignored issues
show
Coding Style introduced by
getDailyTotals uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
101
    {
102
        try {
103
            $servers = $this->validateQueryStringArguments($_GET['servers'], 'servers');
104
            $zones   = $this->validateQueryStringArguments($_GET['zones'], 'zones');
105
            $dates   = $this->validateQueryStringArguments($_GET['dates'], 'dates');
106
        } catch (InvalidArgumentException $e) {
107
            return $this->respondWithError($e->getMessage(), self::CODE_WRONG_ARGS);
108
        }
109
110
        $data = [];
111
112
        $metrics = $this->getDailyMetrics($servers, $zones, $dates);
113
        $fractal = $this->getContainer()->get('FractalUtility');
114
115 View Code Duplication
        foreach ($metrics as $row) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
116
            $date = $row['dateIndex'];
117
            unset($row['dateIndex']);
118
            $row['total'] = array_sum($row);
119
120
            // Build each section of the final response using the transformer
121
            $data['data'][$date] = $fractal->createItem($row, new AlertTotalTransformer);
122
        }
123
124
        // Return the now formatted array to the response
125
        return $this->respondWithData($data);
126
    }
127
128
    /**
129
     * Get Daily totals over a range of dates broken down by server
130
     *
131
     * @return \Psr\Http\Message\ResponseInterface
132
     */
133
    public function getDailyTotalsByServer()
0 ignored issues
show
Coding Style introduced by
getDailyTotalsByServer uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
134
    {
135
        try {
136
            $servers = $this->validateQueryStringArguments($_GET['servers'], 'servers');
137
            $zones   = $this->validateQueryStringArguments($_GET['zones'], 'zones');
138
            $dates   = $this->validateQueryStringArguments($_GET['dates'], 'dates');
139
        } catch (InvalidArgumentException $e) {
140
            return $this->respondWithError($e->getMessage(), self::CODE_WRONG_ARGS);
141
        }
142
143
        $data = [];
144
        $serversExploded = explode(',', $servers);
145
        $fractal = $this->getContainer()->get('FractalUtility');
146
147
        foreach ($serversExploded as $server) {
148
            $metrics = $this->getDailyMetrics($server, $zones, $dates);
149
150 View Code Duplication
            foreach ($metrics as $row) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
151
                $date = $row['dateIndex'];
152
                unset($row['dateIndex']);
153
                $row['total'] = array_sum($row);
154
155
                // Build each section of the final response using the transformer
156
                $data['data'][$server][$date] = $fractal->createItem($row, new AlertTotalTransformer);
157
            }
158
        }
159
160
        // Return the now formatted array to the response
161
        return $this->respondWithData($data);
162
    }
163
164
    /**
165
     * Gets raw daily metrics which can be further processed
166
     *
167
     * @param  string $server
168
     * @param  string $zones
169
     * @param  string $dates
170
     *
171
     * @return array
172
     */
173
    public function getDailyMetrics($server, $zones, $dates)
174
    {
175
        $query = $this->repository->newQuery();
176
177
        $sql = $this->generateFactionCaseSql($server, $zones);
178
        $sql .= ', DATE(ResultDateTime) AS dateIndex';
179
        $query->cols([$sql]);
180
181
        $query->where('ResultDateTime IS NOT NULL');
182
        if (! empty($dates)) {
183
            $this->addDateRangeWhereClause($dates, $query, true);
184
        }
185
        $query->groupBy(['dateIndex']);
186
187
        $metrics = $this->repository->readRaw($query->getStatement());
188
189
        // Regenerate the data with a date range to add 0s to where there was no data for those dates
190
191
        $begin = new \DateTime('2014-10-29');
192
        $end = new \DateTime('today');
193
        $interval = new \DateInterval('P1D');
194
        $daterange = new \DatePeriod($begin, $interval, $end);
195
196
        $data = [];
197
198
        foreach ($daterange as $date) {
199
            $data[$date->format('Y-m-d')] = [
200
                'vs' => 0,
201
                'nc' => 0,
202
                'tr' => 0,
203
                'draw' => 0,
204
                'dateIndex' => $date->format('Y-m-d')
205
            ];
206
        }
207
208
        foreach ($metrics as $key => $metric) {
0 ignored issues
show
Bug introduced by
The expression $metrics of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
209
            $data[$metric['dateIndex']] = $metrics[$key];
210
        }
211
212
        return $data;
213
    }
214
215
    /**
216
     * Generates the SELECT CASE statements required to filter down by Faction and Server
217
     *
218
     * @param  string $server
219
     * @param  string $zones
220
     * @param  string $mode
221
     *
222
     * @return string
223
     */
224
    public function generateFactionCaseSql($server = null, $zones = null, $mode = null)
225
    {
226
        $sql = '';
227
228
        foreach ($this->getConfigItem('factions') as $faction) {
0 ignored issues
show
Bug introduced by
The expression $this->getConfigItem('factions') of type string is not traversable.
Loading history...
229
            $factionAbv = strtoupper($faction);
230
231
            $sql .= "SUM(CASE WHEN `ResultWinner` = '{$factionAbv}' ";
232
            if (! empty($server)) {
233
                $sql .= "AND `ResultServer` IN ({$server}) ";
234
            }
235
236
            if (! empty($zones)) {
237
                $sql .= "AND `ResultAlertCont` IN ({$zones}) ";
238
            }
239
240
            if ($mode === 'dominations') {
241
                $sql .= "AND `ResultDomination` = 1 ";
242
            }
243
244
            $sql .= "THEN 1 ELSE 0 END) AS {$faction}";
245
246
            if ($factionAbv !== 'DRAW') {
247
                $sql .= ", ";
248
            }
249
        }
250
251
        return $sql;
252
    }
253
}
254