Completed
Push — master ( aada72...743397 )
by Arthur
21s queued 10s
created

Payload::addCustomValue()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Pushok package.
5
 *
6
 * (c) Arthur Edamov <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Pushok;
13
14
use Countable;
15
use Pushok\Payload\Alert;
16
use Pushok\Payload\Sound;
17
18
// Polyfill for PHP 7.2
19
if (!function_exists('is_countable')) {
20
    function is_countable($var)
21
    {
22
        return (is_array($var) || $var instanceof Countable);
23
    }
24
}
25
26
/**
27
 * Class Payload
28
 *
29
 * @package Pushok
30
 *
31
 * @see     http://bit.ly/payload-key-reference
32
 */
33
class Payload implements \JsonSerializable
34
{
35
    const PAYLOAD_ROOT_KEY = 'aps';
36
    const PAYLOAD_ALERT_KEY = 'alert';
37
    const PAYLOAD_BADGE_KEY = 'badge';
38
    const PAYLOAD_SOUND_KEY = 'sound';
39
    const PAYLOAD_CONTENT_AVAILABLE_KEY = 'content-available';
40
    const PAYLOAD_MUTABLE_CONTENT_KEY = 'mutable-content';
41
    const PAYLOAD_CATEGORY_KEY = 'category';
42
    const PAYLOAD_THREAD_ID_KEY = 'thread-id';
43
    const PAYLOAD_URL_ARGS_KEY = 'url-args';
44
45
    const PAYLOAD_HTTP2_REGULAR_NOTIFICATION_MAXIMUM_SIZE = 4096;
46
    const PAYLOAD_HTTP2_VOIP_NOTIFICATION_MAXIMUM_SIZE = 5120;
47
    const PAYLOAD_BINARY_REGULAR_NOTIFICATION_MAXIMUM_SIZE = 2048;
48
49
50
    /**
51
     * The notification settings for your app on the user’s device determine whether an alert or banner is displayed.
52
     *
53
     * @var Alert|string
54
     */
55
    private $alert;
56
57
    /**
58
     * The number to display as the badge of the app icon.
59
     * If this property is absent, the badge is not changed.
60
     *
61
     * @var int
62
     */
63
    private $badge;
64
65
    /**
66
     * The name of a sound file in the app bundle or in the Library/Sounds folder of the app’s data container.
67
     *
68
     * @var Sound|string
69
     */
70
    private $sound;
71
72
    /**
73
     * Include this key with a value of true to configure a silent notification.
74
     *
75
     * @var bool
76
     */
77
    private $contentAvailable;
78
79
    /**
80
     * Include this key with a value of true to configure a mutable content notification.
81
     *
82
     * @var bool
83
     */
84
    private $mutableContent;
85
86
    /**
87
     * Provide this key with a string value that represents the notification’s type.
88
     *
89
     * @var string
90
     */
91
    private $category;
92
93
    /**
94
     * Provide this key with a string value that represents the app-specific identifier for grouping notifications.
95
     *
96
     * @var string
97
     */
98
    private $threadId;
99
100
    /**
101
     * Provide this key with an array value that represents the url-args for Safari notifications.
102
     *
103
     * @var string
104
     */
105
    private $urlArgs;
106
107
    /**
108
     * Payload custom values.
109
     *
110
     * @var array
111
     */
112
    private $customValues;
113
114
    /**
115
     * Push notification type
116
     *
117
     * https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns#2947607
118
     *
119
     * @var string
120
     */
121
    private $pushType;
122
123
    protected function __construct()
124
    {
125
    }
126
127
    /**
128
     * @return Payload
129
     */
130
    public static function create(): Payload
131
    {
132
        return new self();
133
    }
134
135
    /**
136
     * Get Alert.
137
     *
138
     * @return Alert|string|array|null
139
     */
140
    public function getAlert()
141
    {
142
        return $this->alert;
143
    }
144
145
    /**
146
     * Set Alert.
147
     *
148
     * @param Alert|string $alert|array $alert
0 ignored issues
show
Bug introduced by
There is no parameter named $alert|array. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
149
     *
150
     * @return Payload
151
     */
152
    public function setAlert($alert): Payload
153
    {
154
        if ($alert instanceof Alert || is_string($alert) || is_array($alert)) {
155
            $this->alert = $alert;
0 ignored issues
show
Documentation Bug introduced by
It seems like $alert can also be of type array. However, the property $alert is declared as type object<Pushok\Payload\Alert>|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
156
        }
157
158
        return $this;
159
    }
160
161
    /**
162
     * Get badge.
163
     *
164
     * @return int|null
165
     */
166
    public function getBadge()
167
    {
168
        return $this->badge;
169
    }
170
171
    /**
172
     * Set badge.
173
     *
174
     * @param int $value
175
     *
176
     * @return Payload
177
     */
178
    public function setBadge(int $value): Payload
179
    {
180
        $this->badge = $value;
181
182
        return $this;
183
    }
184
185
    /**
186
     * Get sound.
187
     *
188
     * @return string|null
189
     */
190
    public function getSound()
191
    {
192
        return $this->sound;
193
    }
194
195
    /**
196
     * Set sound.
197
     *
198
     * @param Sound|string $sound
199
     *
200
     * @return Payload
201
     */
202
    public function setSound($sound): Payload
203
    {
204
        if ($sound instanceof Sound || is_string($sound)) {
205
            $this->sound = $sound;
206
        }
207
208
        return $this;
209
    }
210
211
    /**
212
     * Set content availability.
213
     *
214
     * @param bool $value
215
     *
216
     * @return Payload
217
     */
218
    public function setContentAvailability(bool $value): Payload
219
    {
220
        $this->contentAvailable = $value;
221
222
        return $this;
223
    }
224
225
    /**
226
     * Get content availability.
227
     *
228
     * @return bool|null
229
     */
230
    public function isContentAvailable()
231
    {
232
        return $this->contentAvailable;
233
    }
234
235
    /**
236
     * Set the mutable-content key for Notification Service Extensions on iOS10.
237
     *
238
     * @see http://bit.ly/mutable-content
239
     *
240
     * @param bool $value
241
     *
242
     * @return Payload
243
     */
244
    public function setMutableContent(bool $value): Payload
245
    {
246
        $this->mutableContent = $value;
247
248
        return $this;
249
    }
250
251
    /**
252
     * Is content mutable.
253
     *
254
     * @return bool|null
255
     */
256
    public function hasMutableContent()
257
    {
258
        return $this->mutableContent;
259
    }
260
261
    /**
262
     * Get category.
263
     *
264
     * @return string|null
265
     */
266
    public function getCategory()
267
    {
268
        return $this->category;
269
    }
270
271
    /**
272
     * Set category.
273
     *
274
     * @param string $value
275
     *
276
     * @return Payload
277
     */
278
    public function setCategory(string $value): Payload
279
    {
280
        $this->category = $value;
281
282
        return $this;
283
    }
284
285
    /**
286
     * Get thread-id.
287
     *
288
     * @return string|null
289
     */
290
    public function getThreadId()
291
    {
292
        return $this->threadId;
293
    }
294
295
    /**
296
     * Set thread-id.
297
     *
298
     * @param string $value
299
     *
300
     * @return Payload
301
     */
302
    public function setThreadId(string $value): Payload
303
    {
304
        $this->threadId = $value;
305
306
        return $this;
307
    }
308
309
    /**
310
     * Get url-args.
311
     *
312
     * @return array|null
313
     */
314
    public function getUrlArgs()
315
    {
316
        return $this->urlArgs;
317
    }
318
319
    /**
320
     * Set url-args.
321
     *
322
     * @param array $value
323
     *
324
     * @return Payload
325
     */
326
    public function setUrlArgs(array $value): Payload
327
    {
328
        $this->urlArgs = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type array is incompatible with the declared type string of property $urlArgs.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
329
330
        return $this;
331
    }
332
333
    /**
334
     * Set custom value for Payload.
335
     *
336
     * @param string $key
337
     * @param mixed $value
338
     *
339
     * @return Payload
340
     * @throws InvalidPayloadException
341
     */
342
    public function setCustomValue(string $key, $value): Payload
343
    {
344
        if ($key === self::PAYLOAD_ROOT_KEY) {
345
            throw InvalidPayloadException::reservedKey();
346
        }
347
348
        $this->customValues[$key] = $value;
349
350
        return $this;
351
    }
352
    
353
    /**
354
     * Merges custom value for Payload.
355
     *
356
     * @param string $key
357
     * @param mixed $value
358
     *
359
     * @return Payload
360
     * @throws InvalidPayloadException
361
     */
362
    public function addCustomValue(string $key, $value): Payload
363
    {
364
        $this->customValues = array_merge_recursive($this->customValues ? $this->customValues : [], [$key => $value]);
365
366
        return $this;
367
    }
368
369
    /**
370
     * Get custom value.
371
     *
372
     * @param $key
373
     *
374
     * @return mixed
375
     * @throws InvalidPayloadException
376
     */
377
    public function getCustomValue($key)
378
    {
379
        if (!array_key_exists($key, $this->customValues)) {
380
            throw InvalidPayloadException::notExistingCustomValue($key);
381
        }
382
383
        return $this->customValues[$key];
384
    }
385
386
    /**
387
     * Set push type for Payload.
388
     *
389
     * @param string $pushType
390
     * @return Payload
391
     */
392
    public function setPushType($pushType) {
393
        $this->pushType = $pushType;
394
395
        return $this;
396
    }
397
398
    /**
399
     * Get push type for Payload.
400
     *
401
     * @return string
402
     */
403
    public function getPushType() {
404
        return $this->pushType;
405
    }
406
407
    /**
408
     * Convert Payload to JSON.
409
     *
410
     * @return string
411
     */
412
    public function toJson(): string
413
    {
414
        $str = json_encode($this, JSON_UNESCAPED_UNICODE);
415
416
        $this->checkPayloadSize($str);
417
418
        return $str;
419
    }
420
421
    /**
422
     * Specify data which should be serialized to JSON.
423
     *
424
     * @return array
425
     * @link   http://php.net/manual/en/jsonserializable.jsonserialize.php
426
     */
427
    public function jsonSerialize()
428
    {
429
        $payload = self::getDefaultPayloadStructure();
430
431
        if ($this->alert instanceof Alert) {
432
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_ALERT_KEY} = $this->alert;
433
        } elseif(is_string($this->alert)){
434
            $json = json_decode($this->alert, true);
435
            if($json){
436
                $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_ALERT_KEY} = $json;
437
            } else {
438
                $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_ALERT_KEY} = $this->alert;
439
            }
440
        } elseif (is_array($this->alert)){
441
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_ALERT_KEY} = $this->alert;
442
        }
443
444
        if (is_int($this->badge)) {
445
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_BADGE_KEY} = $this->badge;
446
        }
447
448
        if ($this->sound instanceof Sound || is_string($this->sound)) {
449
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_SOUND_KEY} = $this->sound;
450
        }
451
452
        if (is_bool($this->contentAvailable)) {
453
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_CONTENT_AVAILABLE_KEY} = (int)$this->contentAvailable;
454
        }
455
456
        if (is_bool($this->mutableContent)) {
457
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_MUTABLE_CONTENT_KEY} = (int)$this->mutableContent;
458
        }
459
460
        if (is_string($this->category)) {
461
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_CATEGORY_KEY} = $this->category;
462
        }
463
464
        if (is_string($this->threadId)) {
465
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_THREAD_ID_KEY} = $this->threadId;
466
        }
467
468
        if (is_array($this->urlArgs)) {
469
            $payload[self::PAYLOAD_ROOT_KEY]->{self::PAYLOAD_URL_ARGS_KEY} = $this->urlArgs;
470
        }
471
472
        if (is_countable($this->customValues) && count($this->customValues)) {
473
            $payload = array_merge($payload, $this->customValues);
474
        }
475
476
        return $payload;
477
    }
478
479
    /**
480
     * Get default payload structure.
481
     *
482
     * @return array
483
     */
484
    private static function getDefaultPayloadStructure()
485
    {
486
        return [self::PAYLOAD_ROOT_KEY => new \stdClass];
487
    }
488
489
    /**
490
     * @param $jsonPayload
491
     * @return void
492
     * @throws InvalidPayloadException
493
     */
494
    private function checkPayloadSize($jsonPayload)
495
    {
496
        $strLength = strlen($jsonPayload);
497
        if ('voip' === $this->getPushType()) {
498
            if ($strLength > self::PAYLOAD_HTTP2_VOIP_NOTIFICATION_MAXIMUM_SIZE) {
499
                throw new InvalidPayloadException('Voip Payload size limit exceeded');
500
            }
501
        } elseif ($strLength > self::PAYLOAD_HTTP2_REGULAR_NOTIFICATION_MAXIMUM_SIZE) {
502
            throw new InvalidPayloadException('Payload size limit exceeded');
503
        }
504
    }
505
}
506