ApnsClient   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 146
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 5
dl 0
loc 146
ccs 59
cts 59
cp 1
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setUp() 0 16 4
B push() 0 26 4
A getStreamSocketClient() 0 17 2
A getStreamContext() 0 15 2
A writePayloadOnStreamForGivenToken() 0 12 1
1
<?php
2
3
/*
4
 * This file is part of the MobileNotif package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace LinkValue\MobileNotif\Client;
11
12
use LinkValue\MobileNotif\Logger\ClientLoggableTrait;
13
use LinkValue\MobileNotif\Exception\PushException;
14
use LinkValue\MobileNotif\Model\Message;
15
use Psr\Log\NullLogger;
16
17
/**
18
 * Apple Push Notification Service client implementation.
19
 *
20
 * @author  Jamal Youssefi <[email protected]>
21
 * @author  Valentin Coulon <[email protected]>
22
 */
23
class ApnsClient implements ClientInterface
24
{
25
    use ClientLoggableTrait;
26
27
    /**
28
     * Push server params.
29
     *
30
     * @var array
31
     */
32
    private $params;
33
34
    /**
35
     * Constructor.
36
     */
37 9
    public function __construct()
38
    {
39 9
        $this->logger = new NullLogger();
40 9
    }
41
42
    /**
43
     * Set up parameters.
44
     *
45
     * @param array $params
46
     *
47
     * @throws \RuntimeException
48
     */
49 8
    public function setUp(array $params)
50
    {
51 8
        if (empty($params['endpoint'])) {
52 1
            throw new \RuntimeException('Parameter "endpoint" cannot be empty.');
53
        }
54
55 7
        if (empty($params['ssl_pem_path'])) {
56 1
            throw new \RuntimeException('Parameter "ssl_pem_path" cannot be empty.');
57
        }
58
59 6
        if (!is_readable($params['ssl_pem_path'])) {
60 1
            throw new \RuntimeException('"ssl_pem_path" file does not exist or is not readable.');
61
        }
62
63 5
        $this->params = $params;
64 5
    }
65
66
    /**
67
     * Send $message to a mobile client.
68
     *
69
     * @param Message $message
70
     *
71
     * @throws \RuntimeException if setUp method was not called.
72
     * @throws PushException if connection to APNS server failed.
73
     */
74 5
    public function push(Message $message)
75
    {
76 5
        if (empty($this->params)) {
77 1
            throw new \RuntimeException('Please set up this client using setUp() method before pushing messages.');
78
        }
79
80 4
        $tokens = $message->getTokens();
81 4
        if (empty($tokens)) {
82 1
            throw new \RuntimeException('No device token set. Please add at least 1 token using Message::addToken() before trying to push the message.');
83
        }
84
85 3
        $this->logger->info('Connecting to APNS server');
86 3
        $stream = $this->getStreamSocketClient();
87
88 2
        $payload = $message->getPayloadAsJson();
89
90 2
        foreach ($tokens as $token) {
91 2
            $this->logger->info('Sending message to APNS server.', array(
92 2
                'deviceToken' => $token,
93 2
                'payload' => $payload,
94 2
            ));
95 2
            $this->writePayloadOnStreamForGivenToken($payload, $stream, $token);
96 2
        }
97
98 2
        fclose($stream);
99 2
    }
100
101
    /**
102
     * Get the Stream socket client.
103
     *
104
     * Connect to the APNS server and open a client socket to it.
105
     *
106
     * @return resource Socket to the APNS server.
107
     *
108
     * @throws PushException if connection to APNS server failed.
109
     */
110 3
    private function getStreamSocketClient()
111
    {
112 3
        $stream_socket_client = stream_socket_client(
113 3
            $this->params['endpoint'],
114 3
            $errno,
115 3
            $errstr,
116 3
            30,
117 3
            STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
118 3
            $this->getStreamContext()
119 3
        );
120
121 3
        if ($stream_socket_client === false) {
122 1
            throw new PushException('An error occurred while trying to contact APNS server.');
123
        }
124
125 2
        return $stream_socket_client;
126
    }
127
128
    /**
129
     * Get secured stream context from SSL certificate.
130
     *
131
     * @return resource
132
     */
133 3
    private function getStreamContext()
134
    {
135
        $context = array(
136
            'ssl' => array(
137 3
                'local_cert' => $this->params['ssl_pem_path'],
138 3
            ),
139 3
        );
140
141
        // Handle Certificate Bundle passphrase
142 3
        if (!empty($this->params['ssl_passphrase'])) {
143 1
            $context['ssl']['passphrase'] = $this->params['ssl_passphrase'];
144 1
        }
145
146 3
        return stream_context_create($context);
147
    }
148
149
    /**
150
     * Write $payload on $stream, it will be sent to given device $token.
151
     *
152
     * @param $payload
153
     * @param $stream
154
     * @param $token
155
     */
156 2
    private function writePayloadOnStreamForGivenToken($payload, $stream, $token)
157
    {
158 2
        $binaryMessage = sprintf('%s%s%s%s%s',
159 2
            chr(0),
160 2
            pack('n', 32),
161 2
            pack('H*', str_replace(' ', '', $token)),
162 2
            pack('n', strlen($payload)),
163
            $payload
164 2
        );
165
166 2
        fwrite($stream, $binaryMessage, strlen($binaryMessage));
167 2
    }
168
}
169