Passed
Push — swagger-docs ( 6837d7...7bc867 )
by MusikAnimal
11:22
created

TopEdits::__construct()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 6
nop 10
dl 0
loc 28
rs 9.7666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace App\Model;
6
7
use App\Helper\AutomatedEditsHelper;
8
use App\Repository\TopEditsRepository;
9
10
/**
11
 * TopEdits returns the top-edited pages by a user.
12
 */
13
class TopEdits extends Model
14
{
15
    protected AutomatedEditsHelper $autoEditsHelper;
16
17
    /** @var string[]|Edit[] Top edits, either to a page or across namespaces. */
18
    protected array $topEdits = [];
19
20
    /** @var int Number of bytes added across all top edits. */
21
    protected int $totalAdded = 0;
22
23
    /** @var int Number of bytes removed across all top edits. */
24
    protected int $totalRemoved = 0;
25
26
    /** @var int Number of top edits marked as minor. */
27
    protected int $totalMinor = 0;
28
29
    /** @var int Number of automated top edits. */
30
    protected int $totalAutomated = 0;
31
32
    /** @var int Number of reverted top edits. */
33
    protected int $totalReverted = 0;
34
35
    /** @var int Which page of results to show. */
36
    protected int $pagination = 0;
37
38
    private const DEFAULT_LIMIT_SINGLE_NAMESPACE = 1000;
39
    private const DEFAULT_LIMIT_ALL_NAMESPACES = 20;
40
41
    /**
42
     * TopEdits constructor.
43
     * @param TopEditsRepository $repository
44
     * @param AutomatedEditsHelper $autoEditsHelper
45
     * @param Project $project
46
     * @param User $user
47
     * @param Page|null $page
48
     * @param string|int $namespace Namespace ID or 'all'.
49
     * @param int|false $start Start date as Unix timestamp.
50
     * @param int|false $end End date as Unix timestamp.
51
     * @param int|null $limit Number of rows to fetch. This defaults to DEFAULT_LIMIT_SINGLE_NAMESPACE if
52
     *   $this->namespace is a single namespace (int), and DEFAULT_LIMIT_ALL_NAMESPACES if $this->namespace is 'all'.
53
     * @param int $pagination Which page of results to show.
54
     */
55
    public function __construct(
56
        TopEditsRepository $repository,
57
        AutomatedEditsHelper $autoEditsHelper,
58
        Project $project,
59
        User $user,
60
        ?Page $page = null,
61
        $namespace = 0,
62
        $start = false,
63
        $end = false,
64
        ?int $limit = null,
65
        int $pagination = 0
66
    ) {
67
        $this->repository = $repository;
68
        $this->autoEditsHelper = $autoEditsHelper;
69
        $this->project = $project;
70
        $this->user = $user;
71
        $this->page = $page;
72
        $this->namespace = 'all' === $namespace ? 'all' : (int)$namespace;
73
        $this->start = $start;
74
        $this->end = $end;
75
        $this->pagination = $pagination;
76
77
        if (null !== $limit) {
78
            $this->limit = $limit;
79
        } else {
80
            $this->limit = 'all' === $this->namespace
81
                ? self::DEFAULT_LIMIT_ALL_NAMESPACES
82
                : self::DEFAULT_LIMIT_SINGLE_NAMESPACE;
83
        }
84
    }
85
86
    /**
87
     * Which page of results we're showing.
88
     * @return int
89
     */
90
    public function getPagination(): int
91
    {
92
        return $this->pagination;
93
    }
94
95
    /**
96
     * Get total number of bytes added.
97
     * @return int
98
     */
99
    public function getTotalAdded(): int
100
    {
101
        return $this->totalAdded;
102
    }
103
104
    /**
105
     * Get total number of bytes removed.
106
     * @return int
107
     */
108
    public function getTotalRemoved(): int
109
    {
110
        return $this->totalRemoved;
111
    }
112
113
    /**
114
     * Get total number of edits marked as minor.
115
     * @return int
116
     */
117
    public function getTotalMinor(): int
118
    {
119
        return $this->totalMinor;
120
    }
121
122
    /**
123
     * Get total number of automated edits.
124
     * @return int
125
     */
126
    public function getTotalAutomated(): int
127
    {
128
        return $this->totalAutomated;
129
    }
130
131
    /**
132
     * Get total number of edits that were reverted.
133
     * @return int
134
     */
135
    public function getTotalReverted(): int
136
    {
137
        return $this->totalReverted;
138
    }
139
140
    /**
141
     * Get the top edits data.
142
     * @return array|Edit[]
143
     */
144
    public function getTopEdits(): array
145
    {
146
        return $this->topEdits;
147
    }
148
149
    /**
150
     * Get the total number of top edits.
151
     * @return int
152
     */
153
    public function getNumTopEdits(): int
154
    {
155
        return count($this->topEdits);
156
    }
157
158
    /**
159
     * Get the average time between edits (in days).
160
     * @return float
161
     */
162
    public function getAtbe(): float
163
    {
164
        $firstDateTime = $this->topEdits[0]->getTimestamp();
165
        $lastDateTime = end($this->topEdits)->getTimestamp();
166
        $secs = $firstDateTime->getTimestamp() - $lastDateTime->getTimestamp();
167
        $days = $secs / (60 * 60 * 24);
168
        return $days / count($this->topEdits);
169
    }
170
171
    /**
172
     * Set the Page on the TopEdits instance.
173
     * @param Page $page
174
     */
175
    public function setPage(Page $page): void
176
    {
177
        $this->page = $page;
178
    }
179
180
    /**
181
     * Fetch and store all the data we need to show the TopEdits view.
182
     * This is the public method that should be called before using
183
     * the getter methods.
184
     * @param bool $format Whether to format the results, including stats for
185
     *     number of reverts, etc. This is set to false for the API endpoints.
186
     */
187
    public function prepareData(bool $format = true): void
188
    {
189
        if (isset($this->page)) {
190
            $this->topEdits = $this->getTopEditsPage($format);
191
        } else {
192
            $this->topEdits = $this->getTopEditsNamespace($format);
193
        }
194
    }
195
196
    /**
197
     * Get the top edits by a user in the given namespace, or 'all' namespaces.
198
     * @param bool $format Whether to format the results, including stats for
199
     *     number of reverts, etc. This is set to false for the API endpoint.
200
     * @return string[] Results keyed by namespace.
201
     */
202
    private function getTopEditsNamespace(bool $format): array
203
    {
204
        if ('all' === $this->namespace) {
205
            $pages = $this->repository->getTopEditsAllNamespaces(
0 ignored issues
show
Bug introduced by
The method getTopEditsAllNamespaces() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

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

205
            /** @scrutinizer ignore-call */ 
206
            $pages = $this->repository->getTopEditsAllNamespaces(
Loading history...
206
                $this->project,
207
                $this->user,
208
                $this->start,
209
                $this->end,
210
                $this->limit
211
            );
212
        } else {
213
            $pages = $this->repository->getTopEditsNamespace(
0 ignored issues
show
Bug introduced by
The method getTopEditsNamespace() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

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

213
            /** @scrutinizer ignore-call */ 
214
            $pages = $this->repository->getTopEditsNamespace(
Loading history...
214
                $this->project,
215
                $this->user,
216
                $this->namespace,
217
                $this->start,
218
                $this->end,
219
                $this->limit,
220
                $this->pagination
221
            );
222
        }
223
224
        if ($format) {
225
            return $this->formatTopPagesNamespace($pages);
226
        } else {
227
            return $pages;
228
        }
229
    }
230
231
    /**
232
     * Get the total number of pages edited in the namespace.
233
     * @return int|null
234
     */
235
    public function getNumPagesNamespace(): ?int
236
    {
237
        if ('all' === $this->namespace) {
238
            return null;
239
        }
240
241
        return (int)$this->repository->countEditsNamespace(
0 ignored issues
show
Bug introduced by
The method countEditsNamespace() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

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

241
        return (int)$this->repository->/** @scrutinizer ignore-call */ countEditsNamespace(
Loading history...
242
            $this->project,
243
            $this->user,
244
            $this->namespace,
245
            $this->start,
246
            $this->end
247
        );
248
    }
249
250
    /**
251
     * Get the top edits to the given page.
252
     * @param bool $format Whether to format the results, including stats for
253
     *     number of reverts, etc. This is set to false for the API endpoint.
254
     * @return Edit[]
255
     */
256
    private function getTopEditsPage(bool $format = true): array
257
    {
258
        $revs = $this->repository->getTopEditsPage(
0 ignored issues
show
Bug introduced by
The method getTopEditsPage() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

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

258
        /** @scrutinizer ignore-call */ 
259
        $revs = $this->repository->getTopEditsPage(
Loading history...
259
            $this->page,
260
            $this->user,
261
            $this->start,
262
            $this->end
263
        );
264
265
        if ($format) {
266
            return $this->formatTopEditsPage($revs);
267
        } else {
268
            return array_map(function ($rev) {
269
                $rev['minor'] = (bool)$rev['minor'];
270
                $rev['reverted'] = (bool)$rev['reverted'];
271
                return $rev;
272
            }, $revs);
273
        }
274
    }
275
276
    /**
277
     * Format the results for top edits to a single page. This method also computes
278
     * totals for added/removed text, automated and reverted edits.
279
     * @param array[] $revs As returned by TopEditsRepository::getTopEditsPage.
280
     * @return Edit[]
281
     */
282
    private function formatTopEditsPage(array $revs): array
283
    {
284
        $edits = [];
285
286
        foreach ($revs as $revision) {
287
            // Check if the edit was reverted based on the edit summary of the following edit.
288
            // If so, update $revision so that when an Edit is instantiated, it will have the 'reverted' option set.
289
            if ($this->autoEditsHelper->isRevert($revision['parent_comment'], $this->project)) {
290
                $revision['reverted'] = 1;
291
            }
292
293
            $edits[] = $this->getEditAndIncrementCounts($revision);
294
        }
295
296
        return $edits;
297
    }
298
299
    /**
300
     * Create an Edit instance for the given revision, and increment running totals.
301
     * This is used by self::formatTopEditsPage().
302
     * @param string[] $revision Revision row as retrieved from the database.
303
     * @return Edit
304
     */
305
    private function getEditAndIncrementCounts(array $revision): Edit
306
    {
307
        $edit = $this->repository->getEdit($this->page, $revision);
0 ignored issues
show
Bug introduced by
The method getEdit() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository or App\Repository\ArticleInfoRepository. ( Ignorable by Annotation )

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

307
        /** @scrutinizer ignore-call */ 
308
        $edit = $this->repository->getEdit($this->page, $revision);
Loading history...
308
309
        if ($edit->isAutomated()) {
310
            $this->totalAutomated++;
311
        }
312
313
        if ($edit->isMinor()) {
314
            $this->totalMinor++;
315
        }
316
317
        if ($edit->isReverted()) {
318
            $this->totalReverted++;
319
        } else {
320
            // Length changes don't count if they were reverted.
321
            if ($revision['length_change'] > 0) {
322
                $this->totalAdded += (int)$revision['length_change'];
323
            } else {
324
                $this->totalRemoved += (int)$revision['length_change'];
325
            }
326
        }
327
328
        return $edit;
329
    }
330
331
    /**
332
     * Format the results to be keyed by namespace.
333
     * @param array $pages As returned by TopEditsRepository::getTopEditsNamespace()
334
     *   or TopEditsRepository::getTopEditsAllNamespaces().
335
     * @return array Same results but keyed by namespace.
336
     */
337
    private function formatTopPagesNamespace(array $pages): array
338
    {
339
        /** @var string[] $topEditedPages The top edited pages, keyed by namespace ID. */
340
        $topEditedPages = [];
341
342
        foreach ($pages as $page) {
343
            $nsId = (int)$page['namespace'];
344
345
            // FIXME: needs refactoring, done in PagesController::getPagepileResult() and AppExtension::titleWithNs().
346
            if (0 === $nsId) {
347
                $page['full_page_title'] = $page['page_title'];
348
            } else {
349
                $page['full_page_title'] = (
350
                    $this->project->getNamespaces()[$page['namespace']] ?? ''
351
                ).':'.$page['page_title'];
352
            }
353
354
            if (array_key_exists('pa_class', $page)) {
355
                $page['assessment'] = array_merge(
356
                    ['class' => $page['pa_class'] ?: 'Unknown'],
357
                    $this->project->getPageAssessments()->getClassAttrs($page['pa_class'] ?: 'Unknown')
358
                );
359
                unset($page['pa_class']);
360
            }
361
362
            if (isset($topEditedPages[$nsId])) {
363
                $topEditedPages[$nsId][] = $page;
364
            } else {
365
                $topEditedPages[$nsId] = [$page];
366
            }
367
        }
368
369
        return $topEditedPages;
370
    }
371
}
372