Completed
Push — master ( 07e3a8...9561cd )
by William
16:58 queued 14:23
created

ReportsTable::getRelatedByField()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 72
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 39
nc 24
nop 5
dl 0
loc 72
ccs 36
cts 36
cp 1
crap 6
rs 8.6737
c 2
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Report model representing a group of incidents.
5
 *
6
 * phpMyAdmin Error reporting server
7
 * Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
8
 *
9
 * Licensed under The MIT License
10
 * For full copyright and license information, please see the LICENSE.txt
11
 * Redistributions of files must retain the above copyright notice.
12
 *
13
 * @copyright Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
14
 * @license   https://opensource.org/licenses/mit-license.php MIT License
15
 *
16
 * @see      https://www.phpmyadmin.net/
17
 */
18
19
namespace App\Model\Table;
20
21
use Cake\Datasource\EntityInterface;
22
use Cake\Model\Model;
0 ignored issues
show
Bug introduced by
The type Cake\Model\Model was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use Cake\ORM\Query;
24
use Cake\ORM\Table;
25
use Cake\ORM\TableRegistry;
26
use Cake\Routing\Router;
27
28
/**
29
 * A report a representing a group of incidents.
30
 */
31
class ReportsTable extends Table
32
{
33
    /**
34
     * @var array
35
     *
36
     * @see http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany
37
     * @see Cake::Model::$hasMany
38
     */
39
    public $hasMany = [
40
        'Incidents' => ['dependant' => true],
41
    ];
42
43
    /**
44
     * @var array
45
     *
46
     * @see http://book.cakephp.org/2.0/en/models/model-attributes.html#validate
47
     * @see http://book.cakephp.org/2.0/en/models/data-validation.html
48
     * @see Model::$validate
49
     */
50
    public $validate = [
51
        'error_message' => [
52
            'rule' => 'notEmpty',
53
            'required' => true,
54
        ],
55
    ];
56
57
    /**
58
     * List of valid finder method options, supplied as the first parameter to find().
59
     *
60
     * @var array
61
     *
62
     * @see Model::$findMethods
63
     */
64
    public $findMethods = [
65
        'allDataTable' => true,
66
        'arrayList' => true,
67
    ];
68
69
    /**
70
     * List of valid finder method options, supplied as the first parameter to find().
71
     *
72
     * @var array
73
     */
74
    public $status = [
75
        'new' => 'New',
76
        'invalid' => 'Invalid',
77
        'resolved' => 'Resolved',
78
        'forwarded' => 'Forwarded',
79
    ];
80
81 104
    public function initialize(array $config): void
82
    {
83 104
        $this->hasMany('Incidents', ['dependent' => true]);
84 104
    }
85
86
    /**
87
     * Retrieves the incident records that are related to the current report.
88
     *
89
     * @return Query the list of incidents ordered by creation date desc
90
     */
91 20
    public function getIncidents(): Query
92
    {
93 20
        return TableRegistry::getTableLocator()->get('Incidents')->find('all', [
94 20
            'limit' => 50,
95 20
            'conditions' => $this->relatedIncidentsConditions(),
96 20
            'order' => 'Incidents.created desc',
97
        ]);
98
    }
99
100
    /**
101
     * Retrieves the report records that are related to the current report.
102
     *
103
     * @return Query the list of related reports
104
     */
105 8
    public function getRelatedReports(): Query
106
    {
107 8
        return $this->find('all', [
108 8
            'conditions' => $this->relatedReportsConditions(),
109
        ]);
110
    }
111
112
    /**
113
     * Retrieves the incident records that are related to the current report that
114
     * also have a description.
115
     *
116
     * @return Query the list of incidents ordered by description lenght desc
117
     */
118 12
    public function getIncidentsWithDescription(): Query
119
    {
120 12
        return TableRegistry::getTableLocator()->get('Incidents')->find('all', [
121 6
            'conditions' => [
122 6
                'NOT' => ['Incidents.steps is null'],
123 12
                $this->relatedIncidentsConditions(),
124
            ],
125 12
            'order' => 'Incidents.steps desc',
126
        ]);
127
    }
128
129
    /**
130
     * Retrieves the incident records that are related to the current report that
131
     * that have a different stacktrace.
132
     *
133
     * @return Query the list of incidents
134
     */
135 12
    public function getIncidentsWithDifferentStacktrace(): Query
136
    {
137 12
        return TableRegistry::getTableLocator()->get('Incidents')->find('all', [
138 6
            'fields' => [
139 6
                'DISTINCT Incidents.stackhash',
140
                'Incidents.stacktrace',
141
                'Incidents.full_report',
142
                'Incidents.exception_type',
143
            ],
144 12
            'conditions' => $this->relatedIncidentsConditions(),
145 12
            'group' => 'Incidents.stackhash',
146
        ]);
147
    }
148
149
    /**
150
     * Removes a report from a group of related reports.
151
     *
152
     * @param EntityInterface $report The report instance
153
     * @return void Nothing
154
     */
155 4
    public function removeFromRelatedGroup(EntityInterface $report): void
156
    {
157 4
        $report->related_to = null;
0 ignored issues
show
Bug introduced by
Accessing related_to on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
158 4
        $this->save($report);
159
160 4
        $rel_report = $this->findByRelatedTo($report->id)->first();
0 ignored issues
show
Bug introduced by
The method findByRelatedTo() does not exist on App\Model\Table\ReportsTable. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

160
        $rel_report = $this->/** @scrutinizer ignore-call */ findByRelatedTo($report->id)->first();
Loading history...
161 4
        if ($rel_report) {
162
            $this->updateAll(
163
                ['related_to' => $rel_report->id],
164
                ['related_to' => $report->id]
165
            );
166
        }
167
168
        // remove all redundant self-groupings
169 4
        $this->updateAll(
170 4
            ['related_to' => null],
171 4
            ['reports.related_to = reports.id']
172
        );
173 4
    }
174
175
    /**
176
     * Adds a report to a group of related reports.
177
     *
178
     * @param EntityInterface $report     The report instance
179
     * @param int             $related_to The report Id
180
     * @return void Nothing
181
     */
182 8
    public function addToRelatedGroup(EntityInterface $report, int $related_to): void
183
    {
184 8
        $dup_report = $this->get($related_to);
185
186 8
        if ($dup_report && $dup_report->related_to) {
0 ignored issues
show
Bug introduced by
Accessing related_to on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
187
            $report->related_to = $dup_report->related_to;
188
        } else {
189 8
            $report->related_to = $related_to;
190
        }
191 8
        $report->status = $dup_report->status;
0 ignored issues
show
Bug introduced by
Accessing status on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
192
193 8
        $this->save($report);
194 8
    }
195
196
    /**
197
     * Returns the full url to the current report.
198
     *
199
     * @return string url
200
     */
201 4
    public function getUrl(): string
202
    {
203 4
        return Router::url(['controller' => 'reports', 'action' => 'view',
204 4
            $this->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on App\Model\Table\ReportsTable. Since you implemented __get, consider adding a @property annotation.
Loading history...
205 4
        ], true);
206
    }
207
208
    /**
209
     * groups related incidents by distinct values of a field. It may also provide
210
     * the number of groups, whether to only include incidents that are related
211
     * to the current report and also to only limit the search to incidents
212
     * submited after a certain date.
213
     *
214
     * @param string $fieldName the name of the field to group by
215
     * @param int    $limit     the max number of groups to return
216
     * @param bool   $count     whether to return the number of distinct groups
217
     * @param bool   $related   whether to limit the search to only related incidents
218
     * @param string $timeLimit the date at which to start the search
219
     *
220
     * @return array|object the groups with the count of each group and possibly the number
221
     *               of groups. Ex: array('Apache' => 2) or array(array('Apache' => 2), 1)
222
     */
223 16
    public function getRelatedByField(
224
        string $fieldName,
225
        int $limit = 10,
226
        bool $count = false,
227
        bool $related = true,
228
        ?string $timeLimit = null
229
    ) {
230 16
        $fieldAlias = 'Incidents__' . $fieldName;
231
        $queryDetails = [
232
            'conditions' => [
233 16
                'NOT' => ['Incidents.' . $fieldName . ' is null'],
234
            ],
235 16
            'limit' => $limit,
236
        ];
237
238 16
        if ($related) {
239 12
            $queryDetails['conditions'][] = $this->relatedIncidentsConditions();
240
        }
241
242 16
        if ($timeLimit) {
243 8
            $queryDetails['conditions'][] = ['Incidents.created >=' => $timeLimit];
244
        }
245
246 16
        $groupedCount = TableRegistry::getTableLocator()->get('Incidents')->find('all', $queryDetails);
247
248
        /* Ommit version number in case of browser and server_software fields.
249
         * In case of browser field, version number is seperated by space,
250
         * for example,'FIREFOX 47', hence split the value using space.
251
         * In case of server_software field, version number is seperated by /
252
         * for example, 'nginx/1.7', hence split the value using /.
253
         * See http://book.cakephp.org/3.0/en/orm/query-builder.html#using-sql-functionsp://book.cakephp.org/3.0/en/orm/query-builder.html#using-sql-functions
254
         * for how to use Sql functions with cake
255
         */
256 12
        switch ($fieldName) {
257 16
            case 'browser':
258
                // SUBSTRING(browser, 1, LOCATE(' ', Incidents.browser)-1))
259 12
                $field = $groupedCount->func()->substring([
260 12
                    $fieldName => 'literal',
261 12
                    '1' => 'literal',
262 12
                    "Locate(' ', Incidents.browser)-1" => 'literal',
263
                ]);
264 12
                break;
265 16
            case 'server_software':
266
                // SUBSTRING(server_software, 1, LOCATE('/', Incidents.server_software)-1))
267 12
                $field = $groupedCount->func()->substring([
268 12
                    $fieldName => 'literal',
269 12
                    '1' => 'literal',
270 12
                    "Locate('/', Incidents.server_software)-1" => 'literal',
271
                ]);
272 12
                break;
273
            default:
274 16
                $field = $fieldName;
275
        }
276 16
        $groupedCount->select([
277 16
            'count' => $groupedCount->func()->count('*'),
278 16
            $fieldAlias => $field,
279 16
        ])->group($fieldAlias)->distinct(['' . $fieldAlias . ''])
280 16
          ->order('count')->toArray();
281
282 16
        if ($count) {
283 12
            $queryDetails['fields'] = ['' . $fieldName . ''];
284 12
            $queryDetails['limit'] = null;
285 12
            $queryDetails['group'] = 'Incidents.' . $fieldName;
286 12
            $totalCount = TableRegistry::getTableLocator()->get('Incidents')->find('all', $queryDetails)->count();
287
288
            return [
289 12
                $groupedCount,
290 12
                $totalCount,
291
            ];
292
        }
293
294 8
        return $groupedCount;
295
    }
296
297
    /**
298
     * Updates the linked reports to a Github issue to newly received status
299
     *
300
     * @param string $issueNumber Github Issue number
301
     * @param string $status      New status to be set
302
     *
303
     * @return int Number of Linked reports updated
304
     */
305 4
    public function setLinkedReportStatus(string $issueNumber, string $status): int
306
    {
307 4
        $conditions = ['sourceforge_bug_id' => $issueNumber];
308 4
        $fields = ['status' => $status];
309
310 4
        return $this->updateAll($fields, $conditions);
311
    }
312
313
    /**
314
     * returns an array of conditions that would return all related incidents to the
315
     * current report.
316
     *
317
     * @return array the related incidents conditions
318
     */
319 32
    protected function relatedIncidentsConditions(): array
320
    {
321
        $conditions = [
322 32
            ['Incidents.report_id = ' . $this->id],
0 ignored issues
show
Bug introduced by
Are you sure $this->id of type Cake\ORM\Association can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

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

322
            ['Incidents.report_id = ' . /** @scrutinizer ignore-type */ $this->id],
Loading history...
Bug Best Practice introduced by
The property id does not exist on App\Model\Table\ReportsTable. Since you implemented __get, consider adding a @property annotation.
Loading history...
323
        ];
324 32
        $report = $this->get($this->id);
325 32
        if ($report->related_to) { //TODO: fix when fix related reports
0 ignored issues
show
Bug introduced by
Accessing related_to on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
326 8
            $conditions[] = ['Incidents.report_id = ' . $report->related_to];
327
        }
328
329 32
        return ['OR' => $conditions];
330
    }
331
332
    /**
333
     * returns an array of conditions that would return all related reports to the
334
     * current report.
335
     *
336
     * @return array the related reports conditions
337
     */
338 8
    protected function relatedReportsConditions(): array
339
    {
340 8
        $conditions = [['related_to' => $this->id]];
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on App\Model\Table\ReportsTable. Since you implemented __get, consider adding a @property annotation.
Loading history...
341 8
        $report = $this->get($this->id);
342 8
        if ($report->related_to) { //TODO: fix related to
0 ignored issues
show
Bug introduced by
Accessing related_to on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
343
            $conditions[] = ['related_to' => $report->related_to];
344
            $conditions[] = ['id' => $report->related_to];
345
        }
346 8
        $conditions = [['OR' => $conditions]];
347 8
        $conditions[] = ['Reports.id !=' => $this->id];
348
349 8
        return $conditions;
350
    }
351
}
352