Completed
Push — master ( 5b50d6...dd84b4 )
by Romain
10s
created

FcmAdapter::getPayload()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 4
nop 0
1
<?php
2
namespace ker0x\Push\Adapter;
3
4
use Cake\Core\Configure;
5
use Cake\Core\InstanceConfigTrait;
6
use Cake\Http\Client;
7
use Cake\Utility\Hash;
8
use ker0x\Push\AdapterInterface;
9
use ker0x\Push\Exception\InvalidAdapterException;
10
use ker0x\Push\Exception\InvalidDataException;
11
use ker0x\Push\Exception\InvalidNotificationException;
12
use ker0x\Push\Exception\InvalidParametersException;
13
use ker0x\Push\Exception\InvalidTokenException;
14
15
class FcmAdapter implements AdapterInterface
16
{
17
18
    use InstanceConfigTrait;
19
20
    /**
21
     * Array for devices's token
22
     *
23
     * @var array
24
     */
25
    protected $tokens = [];
26
27
    /**
28
     * Array for the notification
29
     *
30
     * @var array
31
     */
32
    protected $notification = [];
33
34
    /**
35
     * Array of datas
36
     *
37
     * @var array
38
     */
39
    protected $datas = [];
40
41
    /**
42
     * Array of request parameters
43
     *
44
     * @var array
45
     */
46
    protected $parameters = [];
47
48
    /**
49
     * Array of payload
50
     *
51
     * @var array
52
     */
53
    protected $payload = [];
54
55
    /**
56
     * Response of the request
57
     *
58
     * @var \Cake\Http\Client\Response
59
     */
60
    protected $response;
61
62
    /**
63
     * Default config
64
     *
65
     * @var array
66
     */
67
    protected $_defaultConfig = [
68
        'parameters' => [
69
            'collapse_key' => null,
70
            'priority' => 'normal',
71
            'dry_run' => false,
72
            'time_to_live' => 0,
73
            'restricted_package_name' => null
74
        ],
75
        'http' => []
76
    ];
77
78
    /**
79
     * List of keys allowed to be used in notification array.
80
     *
81
     * @var array
82
     */
83
    protected $_allowedNotificationKeys = [
84
        'title',
85
        'body',
86
        'icon',
87
        'sound',
88
        'badge',
89
        'tag',
90
        'color',
91
        'click_action',
92
        'body_loc_key',
93
        'body_loc_args',
94
        'title_loc_key',
95
        'title_loc_args',
96
    ];
97
98
    /**
99
     * FcmAdapter constructor.
100
     *
101
     * @throws \ker0x\Push\Exception\InvalidAdapterException
102
     */
103
    public function __construct()
104
    {
105
        $config = Configure::read('Push.adapters.Fcm');
106
        $this->config($config);
107
108
        if ($this->config('api.key') === null) {
109
            throw new InvalidAdapterException("No API key set. Push not triggered");
110
        }
111
    }
112
113
    /**
114
     * Send the request
115
     *
116
     * @return bool
117
     */
118
    public function send()
119
    {
120
        return $this->_executePush();
121
    }
122
123
    /**
124
     * Display the response of the request
125
     *
126
     * @return \Cake\Http\Client\Response
127
     */
128
    public function response()
129
    {
130
        return $this->response;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->response; (Cake\Http\Client\Response) is incompatible with the return type declared by the interface ker0x\Push\AdapterInterface::response of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
131
    }
132
133
    /**
134
     * Getter for tokens
135
     *
136
     * @return array
137
     */
138
    public function getTokens()
139
    {
140
        return $this->tokens;
141
    }
142
143
    /**
144
     * Setter for tokens
145
     *
146
     * @param array $tokens Array of devices's token
147
     * @return $this
148
     */
149
    public function setTokens(array $tokens)
150
    {
151
        $this->_checkTokens($tokens);
152
        $this->tokens = $tokens;
153
154
        return $this;
155
    }
156
157
    /**
158
     * Getter for notification
159
     *
160
     * @return array
161
     */
162
    public function getNotification()
163
    {
164
        return $this->notification;
165
    }
166
167
    /**
168
     * Setter for notification
169
     *
170
     * @param array $notification Array of keys for the notification
171
     * @return $this
172
     */
173
    public function setNotification(array $notification)
174
    {
175
        $this->_checkNotification($notification);
176
        if (!isset($notification['icon'])) {
177
            $notification['icon'] = 'myicon';
178
        }
179
        $this->notification = $notification;
180
181
        return $this;
182
    }
183
184
    /**
185
     * Getter for datas
186
     *
187
     * @return array
188
     */
189
    public function getDatas()
190
    {
191
        return $this->datas;
192
    }
193
194
    /**
195
     * Setter for datas
196
     *
197
     * @param array $datas Array of datas for the push
198
     * @return $this
199
     */
200
    public function setDatas(array $datas)
201
    {
202
        $this->_checkDatas($datas);
203
        foreach ($datas as $key => $value) {
204
            if (is_bool($value)) {
205
                $value = ($value) ? 'true' : 'false';
206
            }
207
            $datas[$key] = (string)$value;
208
        }
209
        $this->datas = $datas;
210
211
        return $this;
212
    }
213
214
    /**
215
     * Getter for parameters
216
     *
217
     * @return array
218
     */
219
    public function getParameters()
220
    {
221
        return $this->parameters;
222
    }
223
224
    /**
225
     * Setter for parameters
226
     *
227
     * @param array $parameters Array of parameters for the push
228
     * @return $this
229
     */
230
    public function setParameters(array $parameters)
231
    {
232
        $this->_checkParameters($parameters);
233
        $this->parameters = Hash::merge($this->config('parameters'), $parameters);
234
235
        return $this;
236
    }
237
238
    /**
239
     * Getter for payload
240
     *
241
     * @return array
242
     */
243
    public function getPayload()
244
    {
245
        $notification = $this->getNotification();
246
        if (!empty($notification)) {
247
            $this->payload['notification'] = $notification;
248
        }
249
250
        $datas = $this->getDatas();
251
        if (!empty($datas)) {
252
            $this->payload['datas'] = $datas;
253
        }
254
255
        return $this->payload;
256
    }
257
258
    /**
259
     * Check tokens's array
260
     *
261
     * @param array $tokens Token's array
262
     * @return void
263
     * @throws \ker0x\Push\Exception\InvalidTokenException
264
     */
265
    private function _checkTokens($tokens)
266
    {
267
        if (empty($tokens) || count($tokens) > 1000) {
268
            throw new InvalidTokenException("Array must contain at least 1 and at most 1000 tokens.");
269
        }
270
    }
271
272
    /**
273
     * Check notification's array
274
     *
275
     * @param array $notification Notification's array
276
     * @return void
277
     * @throws \ker0x\Push\Exception\InvalidNotificationException
278
     */
279
    private function _checkNotification($notification)
280
    {
281
        if (empty($notification) || !isset($notification['title'])) {
282
            throw new InvalidNotificationException("Array must contain at least a key title.");
283
        }
284
285
        $notAllowedKeys = [];
286
        foreach ($notification as $key => $value) {
287
            if (!in_array($key, $this->_allowedNotificationKeys)) {
288
                $notAllowedKeys[] = $key;
289
            }
290
        }
291
292
        if (!empty($notAllowedKeys)) {
293
            $notAllowedKeys = implode(', ', $notAllowedKeys);
294
            throw new InvalidNotificationException("The following keys are not allowed: {$notAllowedKeys}");
295
        }
296
    }
297
298
    /**
299
     * Check datas's array
300
     *
301
     * @param array $datas Datas's array
302
     * @return void
303
     * @throws \ker0x\Push\Exception\InvalidDataException
304
     */
305
    private function _checkDatas($datas)
306
    {
307
        if (empty($datas)) {
308
            throw new InvalidDataException("Array can not be empty.");
309
        }
310
    }
311
312
    /**
313
     * Check parameters's array
314
     *
315
     * @param array $parameters Parameters's array
316
     * @return void
317
     * @throws \ker0x\Push\Exception\InvalidParametersException
318
     */
319
    private function _checkParameters($parameters)
320
    {
321
        if (empty($parameters)) {
322
            throw new InvalidParametersException("Array can not be empty.");
323
        }
324
    }
325
326
    /**
327
     * Execute the push
328
     *
329
     * @return bool
330
     */
331
    private function _executePush()
332
    {
333
        $message = $this->_buildMessage();
334
        $options = $this->_getHttpOptions();
335
336
        $http = new Client();
337
        $this->response = $http->post($this->config('api.url'), $message, $options);
338
339
        return ($this->response->code === '200') ? true : false;
340
    }
341
342
    /**
343
     * Build the message
344
     *
345
     * @return string
346
     */
347
    private function _buildMessage()
348
    {
349
        $tokens = $this->getTokens();
350
        $message = (count($tokens) > 1) ? ['registration_ids' => $tokens] : ['to' => current($tokens)];
351
352
        $payload = $this->getPayload();
353
        if (!empty($payload)) {
354
            $message += $payload;
355
        }
356
357
        $parameters = $this->getParameters();
358
        if (!empty($parameters)) {
359
            $message += $parameters;
360
        }
361
362
        return json_encode($message);
363
    }
364
365
    /**
366
     * Return options for the HTTP request
367
     *
368
     * @return array $options
369
     */
370
    private function _getHttpOptions()
371
    {
372
        $options = Hash::merge($this->config('http'), [
373
            'type' => 'json',
374
            'headers' => [
375
                'Authorization' => 'key=' . $this->config('api.key'),
376
                'Content-Type' => 'application/json'
377
            ]
378
        ]);
379
380
        return $options;
381
    }
382
}
383