Completed
Branch 2.x (c99d86)
by Julián
08:01
created

ApnsAdapter::getRequiredParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * Unified push notification services abstraction (http://github.com/juliangut/tify).
5
 *
6
 * @license BSD-3-Clause
7
 * @link https://github.com/juliangut/tify
8
 * @author Julián Gutiérrez <[email protected]>
9
 */
10
11
namespace Jgut\Tify\Adapter;
12
13
use Jgut\Tify\Adapter\Apns\DefaultFactory;
14
use Jgut\Tify\Adapter\Apns\Factory;
15
use Jgut\Tify\Exception\AdapterException;
16
use Jgut\Tify\Exception\NotificationException;
17
use Jgut\Tify\Notification;
18
use Jgut\Tify\Receiver\ApnsReceiver;
19
use Jgut\Tify\Result;
20
use ZendService\Apple\Exception\RuntimeException as ApnsRuntimeException;
21
22
/**
23
 * APNS service adapter.
24
 */
25
class ApnsAdapter extends AbstractAdapter implements PushAdapter, FeedbackAdapter
26
{
27
    const PARAMETER_CERTIFICATE = 'certificate';
28
    const PARAMETER_PASS_PHRASE = 'pass_phrase';
29
30
    const RESPONSE_OK = 0;
31
    const RESPONSE_PROCESSING_ERROR = 1;
32
    const RESPONSE_MISSING_DEVICE_TOKEN = 2;
33
    const RESPONSE_MISSING_TOPIC = 3;
34
    const RESPONSE_MISSING_PAYLOAD = 4;
35
    const RESPONSE_INVALID_TOKEN_SIZE = 5;
36
    const RESPONSE_INVALID_TOPIC_SIZE = 6;
37
    const RESPONSE_INVALID_PAYLOAD_SIZE = 7;
38
    const RESPONSE_INVALID_TOKEN = 8;
39
    const RESPONSE_UNKNOWN_ERROR = 255;
40
    const RESPONSE_UNAVAILABLE = 2048;
41
42
    /**
43
     * Status codes mapping.
44
     *
45
     * @var array
46
     */
47
    protected static $statusCodeMap = [
48
        self::RESPONSE_OK => Result::STATUS_SUCCESS,
49
50
        self::RESPONSE_MISSING_DEVICE_TOKEN => Result::STATUS_INVALID_MESSAGE,
51
        self::RESPONSE_MISSING_TOPIC => Result::STATUS_INVALID_MESSAGE,
52
        self::RESPONSE_MISSING_PAYLOAD => Result::STATUS_INVALID_MESSAGE,
53
        self::RESPONSE_INVALID_TOKEN_SIZE => Result::STATUS_INVALID_MESSAGE,
54
        self::RESPONSE_INVALID_TOPIC_SIZE => Result::STATUS_INVALID_MESSAGE,
55
        self::RESPONSE_INVALID_PAYLOAD_SIZE => Result::STATUS_INVALID_MESSAGE,
56
57
        self::RESPONSE_INVALID_TOKEN => Result::STATUS_INVALID_DEVICE,
58
59
        self::RESPONSE_PROCESSING_ERROR => Result::STATUS_SERVER_ERROR,
60
        self::RESPONSE_UNAVAILABLE => Result::STATUS_SERVER_ERROR,
61
62
        self::RESPONSE_UNKNOWN_ERROR => Result::STATUS_UNKNOWN_ERROR,
63
    ];
64
65
    /**
66
     * Status messages mapping.
67
     *
68
     * @var array
69
     */
70
    protected static $statusMessageMap = [
71
        self::RESPONSE_OK => 'OK',
72
73
        self::RESPONSE_MISSING_DEVICE_TOKEN => 'Missing Device Token',
74
        self::RESPONSE_MISSING_TOPIC => 'Missing Topic',
75
        self::RESPONSE_MISSING_PAYLOAD => 'Missing Payload',
76
        self::RESPONSE_INVALID_TOKEN_SIZE => 'Invalid Token Size',
77
        self::RESPONSE_INVALID_TOPIC_SIZE => 'Invalid Topic Size',
78
        self::RESPONSE_INVALID_PAYLOAD_SIZE => 'Invalid Payload Size',
79
80
        self::RESPONSE_INVALID_TOKEN => 'Invalid Token',
81
82
        self::RESPONSE_PROCESSING_ERROR => 'Processing Error',
83
        self::RESPONSE_UNAVAILABLE => 'Server Unavailable',
84
85
        self::RESPONSE_UNKNOWN_ERROR => 'Unknown Error',
86
    ];
87
88
    /**
89
     * APNS service factory.
90
     *
91
     * @var Factory
92
     */
93
    protected $factory;
94
95
    /**
96
     * Push service client.
97
     *
98
     * @var \ZendService\Apple\Apns\Client\Message
99
     */
100
    protected $pushClient;
101
102
    /**
103
     * Feedback service client.
104
     *
105
     * @var \ZendService\Apple\Apns\Client\Feedback
106
     */
107
    protected $feedbackClient;
108
109
    /**
110
     * APNS service adapter constructor.
111
     *
112
     * @param array   $parameters
113
     * @param Factory $factory
0 ignored issues
show
Documentation introduced by
Should the type for parameter $factory not be null|Factory?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
114
     * @param bool    $sandbox
115
     *
116
     * @throws AdapterException
117
     */
118
    public function __construct(array $parameters = [], $sandbox = false, Factory $factory = null)
119
    {
120
        parent::__construct($parameters, $sandbox);
121
122
        $certificatePath = $this->getParameter(static::PARAMETER_CERTIFICATE);
123
        if (!file_exists($certificatePath) || !is_readable($certificatePath)) {
124
            throw new AdapterException(
125
                sprintf('Certificate file "%s" does not exist or is not readable', $certificatePath)
126
            );
127
        }
128
129
        // @codeCoverageIgnoreStart
130
        if ($factory === null) {
131
            $factory = new DefaultFactory;
132
        }
133
        // @codeCoverageIgnoreEnd
134
135
        $this->factory = $factory;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     *
141
     * @throws \InvalidArgumentException
142
     * @throws \Jgut\Tify\Exception\AdapterException
143
     * @throws \ZendService\Apple\Exception\RuntimeException
144
     */
145
    public function push(Notification $notification)
146
    {
147
        $client = $this->getPushClient();
148
149
        $pushResults = [];
150
151
        $date = new \DateTime('now', new \DateTimeZone('UTC'));
152
153
        foreach ($this->getPushMessage($notification) as $pushMessage) {
154
            try {
155
                $statusCode = $client->send($pushMessage)->getCode();
156
            // @codeCoverageIgnoreStart
157
            } catch (ApnsRuntimeException $exception) {
158
                $statusCode = $this->getErrorCodeFromException($exception);
159
            }
160
            // @codeCoverageIgnoreEnd
161
162
            $pushResults[] = new Result(
163
                $pushMessage->getToken(),
164
                $date,
165
                self::$statusCodeMap[$statusCode],
166
                self::$statusMessageMap[$statusCode]
167
            );
168
        }
169
170
        $client->close();
171
        $this->pushClient = null;
172
173
        return $pushResults;
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     *
179
     * @throws \InvalidArgumentException
180
     * @throws \Jgut\Tify\Exception\AdapterException
181
     * @throws \Jgut\Tify\Exception\NotificationException
182
     */
183
    public function feedback()
184
    {
185
        $client = $this->getFeedbackClient();
186
187
        $feedbackResults = [];
188
189
        try {
190
            /* @var \ZendService\Apple\Apns\Response\Feedback[] $feedbackResponses */
191
            $feedbackResponses = $client->feedback();
192
        // @codeCoverageIgnoreStart
193
        } catch (ApnsRuntimeException $exception) {
194
            throw new NotificationException($exception->getMessage(), $exception->getCode(), $exception);
195
        }
196
        // @codeCoverageIgnoreEnd
197
198
        foreach ($feedbackResponses as $response) {
199
            $feedbackResults[] = new Result(
200
                $response->getToken(),
201
                \DateTime::createFromFormat('U', $response->getTime())
0 ignored issues
show
Security Bug introduced by
It seems like \DateTime::createFromFor..., $response->getTime()) targeting DateTime::createFromFormat() can also be of type false; however, Jgut\Tify\Result::__construct() does only seem to accept object<DateTime>, did you maybe forget to handle an error condition?
Loading history...
202
            );
203
        }
204
205
        $client->close();
206
        $this->feedbackClient = null;
207
208
        return $feedbackResults;
209
    }
210
211
    /**
212
     * Get opened push service client.
213
     *
214
     * @throws AdapterException
215
     *
216
     * @return \ZendService\Apple\Apns\Client\Message
217
     */
218
    protected function getPushClient()
219
    {
220
        if ($this->pushClient === null) {
221
            $this->pushClient = $this->factory->buildPushClient(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->factory->buildPus...HRASE), $this->sandbox) of type object<Jgut\Tify\Adapter\Apns\AbstractClient> is incompatible with the declared type object<ZendService\Apple\Apns\Client\Message> of property $pushClient.

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...
222
                $this->getParameter(static::PARAMETER_CERTIFICATE),
223
                $this->getParameter(static::PARAMETER_PASS_PHRASE),
224
                $this->sandbox
225
            );
226
        }
227
228
        return $this->pushClient;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->pushClient; of type Jgut\Tify\Adapter\Apns\A...ple\Apns\Client\Message adds the type Jgut\Tify\Adapter\Apns\AbstractClient to the return on line 228 which is incompatible with the return type documented by Jgut\Tify\Adapter\ApnsAdapter::getPushClient of type ZendService\Apple\Apns\Client\Message.
Loading history...
229
    }
230
231
    /**
232
     * Get opened feedback service client.
233
     *
234
     * @throws AdapterException
235
     *
236
     * @return \ZendService\Apple\Apns\Client\Feedback
237
     */
238
    protected function getFeedbackClient()
239
    {
240
        if ($this->feedbackClient === null) {
241
            $this->feedbackClient = $this->factory->buildFeedbackClient(
242
                $this->getParameter('certificate'),
243
                $this->getParameter('pass_phrase'),
244
                $this->sandbox
245
            );
246
        }
247
248
        return $this->feedbackClient;
249
    }
250
251
    /**
252
     * Get service formatted push message.
253
     *
254
     * @param Notification $notification
255
     *
256
     * @throws \ZendService\Apple\Exception\RuntimeException
257
     *
258
     * @return \Generator<\ZendService\Apple\Apns\Message>
0 ignored issues
show
Documentation introduced by
The doc-type \Generator<\ZendService\Apple\Apns\Message> could not be parsed: Expected "|" or "end of type", but got "<" at position 10. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
259
     */
260
    protected function getPushMessage(Notification $notification)
261
    {
262
        foreach ($notification->getReceivers() as $receiver) {
263
            if ($receiver instanceof ApnsReceiver) {
264
                yield $this->factory->buildPushMessage($receiver, $notification);
265
            }
266
        }
267
    }
268
269
    /**
270
     * Extract error code from exception.
271
     *
272
     * @param ApnsRuntimeException $exception
273
     *
274
     * @return int
275
     */
276
    protected function getErrorCodeFromException(ApnsRuntimeException $exception)
277
    {
278
        $message = $exception->getMessage();
279
280
        if (preg_match('/^Server is unavailable/', $message)) {
281
            return static::RESPONSE_UNAVAILABLE;
282
        }
283
284
        return static::RESPONSE_UNKNOWN_ERROR;
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    protected function getDefinedParameters()
291
    {
292
        return [static::PARAMETER_CERTIFICATE, static::PARAMETER_PASS_PHRASE];
293
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298
    protected function getDefaultParameters()
299
    {
300
        return [
301
            static::PARAMETER_PASS_PHRASE => null,
302
        ];
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    protected function getRequiredParameters()
309
    {
310
        return [static::PARAMETER_CERTIFICATE];
311
    }
312
313
    /**
314
     * Disconnect clients.
315
     *
316
     * @codeCoverageIgnore
317
     */
318
    public function __destruct()
319
    {
320
        if ($this->pushClient !== null && $this->pushClient->isConnected()) {
321
            $this->pushClient->close();
322
        }
323
324
        if ($this->feedbackClient !== null && $this->feedbackClient->isConnected()) {
325
            $this->feedbackClient->close();
326
        }
327
    }
328
}
329