Completed
Push — feature/sass-standard ( 554da1...a5bd48 )
by Vladimir
12:37 queued 09:02
created

Notification   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 274
Duplicated Lines 7.3 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 51.61%

Importance

Changes 0
Metric Value
wmc 26
lcom 2
cbo 6
dl 20
loc 274
ccs 48
cts 93
cp 0.5161
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A assignResult() 8 8 1
A getNotifications() 0 12 2
A getReceiver() 0 4 1
A getCategory() 0 4 1
A getEvent() 0 4 1
A markAsRead() 0 8 2
A push() 0 4 1
A getActions() 0 11 3
A getActiveStatuses() 0 4 1
A getQueryBuilder() 0 10 1
A newNotification() 12 12 1
A countUnreadNotifications() 0 6 1
A initializeAdapters() 0 17 4
A isRead() 0 4 1
B pushEvent() 0 29 5

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
 * This file contains functionality to keep track of notifications for players and communicate with external push services
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
9
use BZIon\Event\Event;
10
use BZIon\Event\Events;
11
use BZIon\Model\Column\Timestamp;
12
use BZIon\NotificationAdapter\NotificationAdapter;
13
14
/**
15
 * A notification to a player
16
 * @package    BZiON\Models
17
 */
18
class Notification extends Model
19
{
20
    use Timestamp;
21
22
    /**
23
     * The id of the notified player
24
     * @var int
25
     */
26
    protected $receiver;
27
28
    /**
29
     * The type of the notification
30
     *
31
     * Can be one of the constants in BZIon\Event\Events
32
     *
33
     * @var int
34
     */
35
    protected $type;
36
37
    /**
38
     * The event of the notification
39
     * @var Event
40
     */
41
    protected $event;
42
43
    /**
44
     * The status of the notification (unread, read, deleted)
45
     * @var string
46
     */
47
    protected $status;
48
49
    /**
50
     * When the notification was sent
51
     * @var TimeDate
52
     */
53
54
    /**
55
     * Services that will be notified when a new notification is created
56
     * @var NotificationAdapter[]
57
     */
58
    private static $adapters = array();
59
60
    /**
61
     * The name of the database table used for queries
62
     */
63
    const TABLE = "notifications";
64
65
    /**
66
     * {@inheritdoc}
67
     */
68 1 View Code Duplication
    protected function assignResult($notification)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
69
    {
70 1
        $this->receiver  = $notification['receiver'];
71 1
        $this->type      = $notification['type'];
72 1
        $this->event     = unserialize($notification['event']);
73 1
        $this->status    = $notification['status'];
74 1
        $this->timestamp = TimeDate::fromMysql($notification['timestamp']);
75 1
    }
76
77
    /**
78
     * Enter a new notification into the database
79
     * @param  int          $receiver  The receiver's ID
80
     * @param  string       $type      The type of the notification
81
     * @param  Event        $event     The event of the notification
82
     * @param  string       $timestamp The timestamp of the notification
83
     * @param  string       $status    The status of the notification (unread, read, deleted)
84
     * @return Notification An object representing the notification that was just entered
85
     */
86 1 View Code Duplication
    public static function newNotification($receiver, $type, $event, $timestamp = "now", $status = "unread")
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
87
    {
88 1
        $notification = self::create(array(
89 1
            "receiver"  => $receiver,
90 1
            "type"      => $type,
91 1
            "event"     => serialize($event),
92 1
            "timestamp" => TimeDate::from($timestamp)->toMysql(),
93
            "status"    => $status
94 1
        ));
95
96 1
        return $notification;
97
    }
98
99
    /**
100
     * Show a list of notifications for a specific user
101
     * @param  int            $receiver   The ID of the recipient of the notifications
102
     * @param  bool           $onlyUnread False to show both unread & read notifications
103
     * @return Notification[]
104
     */
105
    public static function getNotifications($receiver, $onlyUnread = false)
106
    {
107
        $statuses = array('unread');
108
        if (!$onlyUnread) {
109
            $statuses[] = 'read';
110
        }
111
112
        return self::getQueryBuilder()
113
            ->where('status')->isOneOf($statuses)
114
            ->where('receiver')->is($receiver)
115
            ->getModels();
116
    }
117
118
    /**
119
     * Show the number of notifications the user hasn't read yet
120
     * @param  int $receiver
121
     * @return int
122
     */
123 1
    public static function countUnreadNotifications($receiver)
124
    {
125 1
        return self::fetchCount("WHERE receiver = ? AND status = 'unread'",
126
            $receiver
0 ignored issues
show
Documentation introduced by
$receiver is of type integer, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127 1
        );
128
    }
129
130
    /**
131
     * Get the receiving player of the notification
132
     * @return Player
133
     */
134 1
    public function getReceiver()
135
    {
136 1
        return Player::get($this->receiver);
137
    }
138
    /**
139
     * Get the type of the notification
140
     *
141
     * Do not use Notification::getType(), as it returns the name of the class
142
     * (i.e. notification)
143
     *
144
     * @return int
145
     */
146 1
    public function getCategory()
147
    {
148 1
        return $this->type;
149
    }
150
151
    /**
152
     * Get the event of the notification
153
     * @return Event
154
     */
155 1
    public function getEvent()
156
    {
157 1
        return $this->event;
158
    }
159
160
    /**
161
     * Finds if the notification has been read by the user
162
     *
163
     * This returns true even if the notification is deleted
164
     * @return bool
165
     */
166 1
    public function isRead()
167
    {
168 1
        return $this->status != "unread";
169
    }
170
171
    /**
172
     * Mark the notification as read by the user
173
     * @return void
174
     */
175 1
    public function markAsRead()
176
    {
177 1
        if ($this->status == "deleted") {
178
            return;
179
        }
180
181 1
        $this->update('status', $this->status = "read");
182 1
    }
183
184
    /**
185
     * Make sure that the user is shown the notification immediately if they are
186
     * browsing
187
     */
188
    public function push()
189
    {
190
        self::pushEvent('notification', $this);
191
    }
192
193
    /**
194
     * Get the available actions for the notification
195
     *
196
     * @param  bool $email Whether actions should be formatted for e-mails
197
     * @return array
198
     */
199
    public function getActions($email = false)
200
    {
201
        switch ($this->type) {
202
            case Events::TEAM_INVITE:
203
                return array(
204
                    ($email) ? 'Accept invitation' : 'Accept' => $this->event->getInvitation()->getUrl('accept', $email)
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class BZIon\Event\Event as the method getInvitation() does only exist in the following sub-classes of BZIon\Event\Event: BZIon\Event\TeamInviteEvent. 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...
205
                );
206
            default:
207
                return array();
208
        }
209
    }
210
211
    /**
212
     * Push an event to the event adapters
213
     * @param  string $type The type of the event
214
     * @param  mixed  $data The data for the event
215
     * @return void
216
     */
217 1
    public static function pushEvent($type, $data = null)
218
    {
219
        switch ($type) {
220 1
            case 'message':
221
                $message = array(
222 1
                    'conversation' => $data['message']->getConversation()->getId(),
223 1
                    'message'      => $data['message']->getId(),
224 1
                    'author'       => $data['message']->getAuthor()->getId(),
225 1
                    'recipients'   => $data['recipients']
226 1
                );
227 1
                break;
228
            case 'notification':
229
                $message = array(
230
                    'type'         => $data->getType(),
231
                    'receiver'     => $data->getReceiver()->getId(),
232
                    'notification' => $data->getId()
233
                );
234
                break;
235
            case 'blank':
236
                $message = null;
237
                break;
238
            default:
239
                $message = $data;
240
        }
241
242 1
        foreach (self::$adapters as $adapter) {
243
            $adapter->trigger($type, $message);
244 1
        }
245 1
    }
246
247
    /**
248
     * Initialize the external push adapters
249
     * @return void
250
     */
251 1
    public static function initializeAdapters()
252
    {
253 1
        if (self::$adapters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$adapters of type BZIon\NotificationAdapter\NotificationAdapter[] 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...
254
            // The adapters have already been initialized, no need to do anything!
255
            return;
256
        }
257
258
        $adapters = array(
259
            'BZIon\NotificationAdapter\WebSocketAdapter'
260 1
        );
261
262 1
        foreach ($adapters as $adapter) {
263 1
            if ($adapter::isEnabled()) {
264
                self::$adapters[] = new $adapter();
265
            }
266 1
        }
267 1
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272
    public static function getActiveStatuses()
273
    {
274
        return array('read', 'unread');
275
    }
276
277
    /**
278
     * Get a query builder for notifications
279
     * @return NotificationQueryBuilder
280
     */
281
    public static function getQueryBuilder()
282
    {
283
        return new NotificationQueryBuilder('Notification', array(
284
            'columns' => array(
285
                'receiver'  => 'receiver',
286
                'timestamp' => 'timestamp',
287
                'status'    => 'status'
288
            )
289
        ));
290
    }
291
}
292