Completed
Push — master ( 1a7130...a16686 )
by Sam
03:53
created

EditCounter::getRevisionCounts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
1
<?php
2
/**
3
 * This file contains only the EditCounter class.
4
 */
5
6
namespace Xtools;
7
8
use \DateTime;
9
10
/**
11
 * An EditCounter provides statistics about a user's edits on a project.
12
 */
13
class EditCounter extends Model
14
{
15
    
16
    /** @var Project */
17
    protected $project;
18
    
19
    /** @var User */
20
    protected $user;
21
22
    /** @var int[] */
23
    protected $revisionCounts;
24
25
    /** @var string[] */
26
    protected $revisionDates;
27
28
    /** @var int[] */
29
    protected $pageCounts;
30
    
31
    /** @var int[] */
32
    protected $logCounts;
33
34
    /** @var int[] Keys are project DB names. */
35
    protected $globalEditCounts;
36
37
    /**
38
     * EditCounter constructor.
39
     * @param Project $project The base project to count edits
40
     * @param User $user
41
     */
42
    public function __construct(Project $project, User $user)
43
    {
44
        $this->project = $project;
45
        $this->user = $user;
46
    }
47
48
    /**
49
     * Get revision count data.
50
     * @return int[]
51
     */
52
    protected function getRevisionCounts()
53
    {
54
        if (! is_array($this->revisionCounts)) {
55
            $this->revisionCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getRevisionCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
56
                ->getRevisionCounts($this->project, $this->user);
57
        }
58
        return $this->revisionCounts;
59
    }
60
61
    /**
62
     * Get revision dates.
63
     * @return int[]
64
     */
65
    protected function getRevisionDates()
66
    {
67
        if (! is_array($this->revisionDates)) {
68
            $this->revisionDates = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getRevisionDates() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
69
                ->getRevisionDates($this->project, $this->user);
70
        }
71
        return $this->revisionDates;
72
    }
73
74
    /**
75
     * Get page count data.
76
     * @return int[]
77
     */
78
    protected function getPageCounts()
79
    {
80
        if (! is_array($this->pageCounts)) {
81
            $this->pageCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getPageCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
82
                ->getPageCounts($this->project, $this->user);
83
        }
84
        return $this->pageCounts;
85
    }
86
87
    /**
88
     * Get revision dates.
89
     * @return int[]
90
     */
91
    protected function getLogCounts()
92
    {
93
        if (! is_array($this->logCounts)) {
94
            $this->logCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getLogCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
95
                ->getLogCounts($this->project, $this->user);
96
        }
97
        return $this->logCounts;
98
    }
99
100
    public function countLiveRevisions()
101
    {
102
        $revCounts = $this->getRevisionCounts();
103
        return isset($revCounts['live']) ? $revCounts['live'] : 0;
104
    }
105
106
    /**
107
     * Get the total number of revisions that have been deleted.
108
     * @return int
109
     */
110
    public function countDeletedRevisions()
111
    {
112
        $revCounts = $this->getRevisionCounts();
113
        return isset($revCounts['deleted']) ? $revCounts['deleted'] : 0;
114
    }
115
116
    /**
117
     * Get the total edit count (live + deleted).
118
     * @return int
119
     */
120
    public function countAllRevisions()
121
    {
122
        return $this->countLiveRevisions() + $this->countDeletedRevisions();
123
    }
124
125
    /**
126
     * Get the total number of revisions with comments.
127
     * @return int
128
     */
129
    public function countRevisionsWithComments()
130
    {
131
        $revCounts = $this->getRevisionCounts();
132
        return isset($revCounts['with_comments']) ? $revCounts['with_comments'] : 0;
133
    }
134
135
    /**
136
     * Get the total number of revisions without comments.
137
     * @return int
138
     */
139
    public function countRevisionsWithoutComments()
140
    {
141
        return $this->countAllRevisions() - $this->countRevisionsWithComments();
142
    }
143
144
    /**
145
     * Get the total number of revisions marked as 'minor' by the user.
146
     * @return int
147
     */
148
    public function countMinorRevisions()
149
    {
150
        $revCounts = $this->getRevisionCounts();
151
        return isset($revCounts['minor']) ? $revCounts['minor'] : 0;
152
    }
153
154
    /**
155
     * Get the total number of revisions under 20 bytes.
156
     */
157
    public function countSmallRevisions()
158
    {
159
        $revCounts = $this->getRevisionCounts();
160
        return isset($revCounts['small']) ? $revCounts['small'] : 0;
161
    }
162
163
    /**
164
     * Get the total number of revisions over 1000 bytes.
165
     */
166
    public function countLargeRevisions()
167
    {
168
        $revCounts = $this->getRevisionCounts();
169
        return isset($revCounts['large']) ? $revCounts['large'] : 0;
170
    }
171
172
    /**
173
     * Get the average revision size for the user.
174
     * @return float Size in bytes.
175
     */
176
    public function averageRevisionSize()
177
    {
178
        $revisionCounts = $this->getRevisionCounts();
179
        return round($revisionCounts['average_size'], 3);
180
    }
181
182
    /**
183
     * Get the total number of non-deleted pages edited by the user.
184
     * @return int
185
     */
186
    public function countLivePagesEdited()
187
    {
188
        $pageCounts = $this->getPageCounts();
189
        return isset($pageCounts['edited-live']) ? $pageCounts['edited-live'] : 0;
190
    }
191
192
    /**
193
     * Get the total number of deleted pages ever edited by the user.
194
     * @return int
195
     */
196
    public function countDeletedPagesEdited()
197
    {
198
        $pageCounts = $this->getPageCounts();
199
        return isset($pageCounts['edited-deleted']) ? $pageCounts['edited-deleted'] : 0;
200
    }
201
202
    /**
203
     * Get the total number of pages ever edited by this user (both live and deleted).
204
     * @return int
205
     */
206
    public function countAllPagesEdited()
207
    {
208
        return $this->countLivePagesEdited() + $this->countDeletedPagesEdited();
209
    }
210
211
    /**
212
     * Get the total number of pages (both still live and those that have been deleted) created
213
     * by the user.
214
     * @return int
215
     */
216
    public function countPagesCreated()
217
    {
218
        return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted();
219
    }
220
221
    /**
222
     * Get the total number of pages created by the user, that have not been deleted.
223
     * @return int
224
     */
225
    public function countCreatedPagesLive()
226
    {
227
        $pageCounts = $this->getPageCounts();
228
        return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0;
229
    }
230
231
    /**
232
     * Get the total number of pages created by the user, that have since been deleted.
233
     * @return int
234
     */
235
    public function countPagesCreatedDeleted()
236
    {
237
        $pageCounts = $this->getPageCounts();
238
        return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0;
239
    }
240
241
    /**
242
     * Get the total number of pages that have been deleted by the user.
243
     * @return int
244
     */
245
    public function countPagesDeleted()
246
    {
247
        $logCounts = $this->getLogCounts();
248
        return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0;
249
    }
250
251
    /**
252
     * Get the total number of pages moved by the user.
253
     * @return int
254
     */
255
    public function countPagesMoved()
256
    {
257
        $logCounts = $this->getLogCounts();
258
        return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0;
259
    }
260
261
    /**
262
     * Get the average number of edits per page (including deleted revisions and pages).
263
     * @return float
264
     */
265
    public function averageRevisionsPerPage()
266
    {
267
        if ($this->countAllPagesEdited() == 0) {
268
            return 0;
269
        }
270
        return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3);
271
    }
272
273
    /**
274
     * Average number of edits made per day.
275
     * @return float
276
     */
277
    public function averageRevisionsPerDay()
278
    {
279
        if ($this->getDays() == 0) {
280
            return 0;
281
        }
282
        return round($this->countAllRevisions() / $this->getDays(), 3);
283
    }
284
285
    /**
286
     * Get the total number of edits made by the user with semi-automating tools.
287
     */
288
    public function countAutomatedRevisions()
289
    {
290
        $autoSummary = $this->automatedRevisionsSummary();
291
        $count = 0;
292
        foreach ($autoSummary as $summary) {
293
            $count += $summary;
294
        }
295
        return $count;
296
    }
297
298
    /**
299
     * Get a summary of the numbers of edits made by the user with semi-automating tools.
300
     */
301
    public function automatedRevisionsSummary()
302
    {
303
        return $this->getRepository()->countAutomatedRevisions($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method countAutomatedRevisions() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
304
    }
305
306
    /**
307
     * Get the count of (non-deleted) edits made in the given timeframe to now.
308
     * @param string $time One of 'day', 'week', 'month', or 'year'.
309
     * @return int The total number of live edits.
310
     */
311
    public function countRevisionsInLast($time)
312
    {
313
        $revCounts = $this->getRevisionCounts();
314
        return isset($revCounts[$time]) ? $revCounts[$time] : 0;
315
    }
316
317
    /**
318
     * Get the date and time of the user's first edit.
319
     */
320
    public function datetimeFirstRevision()
321
    {
322
        $first = $this->getRevisionDates()['first'];
323
        return new DateTime($first);
324
    }
325
326
    /**
327
     * Get the date and time of the user's first edit.
328
     * @return DateTime
329
     */
330
    public function datetimeLastRevision()
331
    {
332
        $last = $this->getRevisionDates()['last'];
333
        return new DateTime($last);
334
    }
335
336
    /**
337
     * Get the number of days between the first and last edits.
338
     * If there's only one edit, this is counted as one day.
339
     * @return int
340
     */
341
    public function getDays()
342
    {
343
        $days = $this->datetimeLastRevision()->diff($this->datetimeFirstRevision())->days;
344
        return $days > 0 ? $days : 1;
345
    }
346
347
    public function countFilesUploaded()
348
    {
349
        $logCounts = $this->getLogCounts();
350
        return $logCounts['upload-upload'] ?: 0;
351
    }
352
353
    public function countFilesUploadedCommons()
354
    {
355
        $logCounts = $this->getLogCounts();
356
        return $logCounts['files_uploaded_commons'] ?: 0;
357
    }
358
359
    /**
360
     * Get the total number of revisions the user has sent thanks for.
361
     * @return int
362
     */
363
    public function thanks()
364
    {
365
        $logCounts = $this->getLogCounts();
366
        return $logCounts['thanks-thank'] ?: 0;
367
    }
368
369
    /**
370
     * Get the total number of approvals
371
     * @return int
372
     */
373
    public function approvals()
374
    {
375
        $logCounts = $this->getLogCounts();
376
        $total = $logCounts['review-approve'] +
377
        (!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) +
378
        (!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) +
379
        (!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0);
380
        return $total;
381
    }
382
383
    /**
384
     * @return int
385
     */
386
    public function patrols()
387
    {
388
        $logCounts = $this->getLogCounts();
389
        return $logCounts['patrol-patrol'] ?: 0;
390
    }
391
392
    /**
393
     * Get the given user's total edit counts per namespace.
394
     */
395
    public function namespaceTotals()
396
    {
397
        $counts = $this->getRepository()->getNamespaceTotals($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getNamespaceTotals() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
398
        arsort($counts);
399
        return $counts;
400
    }
401
402
    /**
403
     * Get a summary of the times of day and the days of the week that the user has edited.
404
     */
405
    public function timeCard()
406
    {
407
        return $this->getRepository()->getTimeCard($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getTimeCard() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
408
    }
409
410
    /**
411
     *
412
     */
413
    public function yearCounts()
414
    {
415
        $totals = $this->getRepository()->getYearCounts($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getYearCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
416
        $out = [
417
            'years' => [],
418
            'namespaces' => [],
419
            'totals' => [],
420
        ];
421
        foreach ($totals as $total) {
422
            $out['years'][$total['year']] = $total['year'];
423
            $out['namespaces'][$total['page_namespace']] = $total['page_namespace'];
424
            if (!isset($out['totals'][$total['page_namespace']])) {
425
                $out['totals'][$total['page_namespace']] = [];
426
            }
427
            $out['totals'][$total['page_namespace']][$total['year']] = $total['count'];
428
        }
429
430
        return $out;
431
    }
432
433
    /**
434
     *
435
     */
436
    public function monthCounts()
437
    {
438
        $totals = $this->getRepository()->getMonthCounts($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getMonthCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
439
        $out = [
440
            'years' => [],
441
            'namespaces' => [],
442
            'totals' => [],
443
        ];
444
        $out['max_year'] = 0;
445
        $out['min_year'] = date('Y');
446
        foreach ($totals as $total) {
447
            // Collect all applicable years and namespaces.
448
            $out['max_year'] = max($out['max_year'], $total['year']);
449
            $out['min_year'] = min($out['min_year'], $total['year']);
450
            // Collate the counts by namespace, and then year and month.
451
            $ns = $total['page_namespace'];
452
            if (!isset($out['totals'][$ns])) {
453
                $out['totals'][$ns] = [];
454
            }
455
            $out['totals'][$ns][$total['year'] . $total['month']] = $total['count'];
456
        }
457
        // Fill in the blanks (where no edits were made in a given month for a namespace).
458
        for ($y = $out['min_year']; $y <= $out['max_year']; $y++) {
459
            for ($m = 1; $m <= 12; $m++) {
460
                foreach ($out['totals'] as $nsId => &$total) {
461
                    if (!isset($total[$y . $m])) {
462
                        $total[$y . $m] = 0;
463
                    }
464
                }
465
            }
466
        }
467
        return $out;
468
    }
469
470
    /**
471
     * Get the total edit counts for the top n projects of this user.
472
     * @param int $numProjects
473
     * @return mixed[] Each element has 'total' and 'project' keys.
474
     */
475
    public function globalEditCountsTopN($numProjects = 10)
476
    {
477
        // Get counts.
478
        $editCounts = $this->globalEditCounts();
479
        // Sort.
480
        uasort($editCounts, function ($a, $b) {
481
            return $b['total'] - $a['total'];
482
        });
483
        // Truncate, and return.
1 ignored issue
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
484
        return array_slice($editCounts, 0, $numProjects);
485
    }
486
487
    /**
488
     * Get the grand total of all edits on all projects.
489
     * @return int
490
     */
491
    public function globalEditCount()
492
    {
493
        $total = 0;
494
        foreach ($this->globalEditCounts() as $editCount) {
495
            $total += $editCount['total'];
496
        }
497
        return $total;
498
    }
499
500
    /**
501
     * Get the total revision counts for all projects for this user.
502
     * @return mixed[] Each element has 'total' and 'project' keys.
503
     */
504
    public function globalEditCounts()
505
    {
506
        if (!$this->globalEditCounts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->globalEditCounts of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
507
            $this->globalEditCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method globalEditCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
508
                ->globalEditCounts($this->user, $this->project);
509
        }
510
        return $this->globalEditCounts;
511
    }
512
513
    /**
514
     * Get the most recent n revisions across all projects.
515
     * @param int $max
516
     * @return Edit[]
517
     */
518
    public function globalEdits($max) {
519
        // Go through each project that has any edits.
520
        $globalRevisions = [];
521
        $oldest = null;
522
523
        foreach ($this->globalEditCounts() as $editCount) {
524
            // Don't query revisions if there aren't any.
525
            if ($editCount['total'] == 0) {
526
                continue;
527
            }
528
            dump($editCount);
529
530
            // Get this project's revisions.
531
            $revisions = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getRevisions() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository, Xtools\PagesRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
532
                ->getRevisions($editCount['project'], $this->user, $oldest);
533
            foreach ($revisions as &$revision) {
534
                $revision['project'] = $editCount['project'];
535
                $revision['timestamp'] = DateTime::createFromFormat('U', $revision['unix_timestamp']);
536
                // If we've already got enough, only check for those newer than the current oldest.
537
                $enough = (count($globalRevisions) >= $max);
538
                $isOlder = ($oldest === null
539
                            || ($oldest !== null && $revision['unix_timestamp'] < $oldest));
540
                if ($enough && $isOlder) {
541
                    $oldest = $revision['unix_timestamp'];
542
                }
543
                $globalRevisions[$revision['unix_timestamp']] = $revision;
544
            }
545
            // Sort and prune, before adding more.
546
            krsort($globalRevisions);
547
            $globalRevisions = array_slice($globalRevisions, 0, $max);
548
        }
549
        dump($globalRevisions);exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method globalEdits() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
550
        return $globalRevisions;
0 ignored issues
show
Unused Code introduced by
return $globalRevisions; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
551
    }
552
}
553