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; |
|
|
|
|
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
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.