Completed
Pull Request — master (#11)
by Oliver
02:28
created

ApnsClient::getStreamContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 7
nc 2
nop 0
crap 2
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\Logger\NullLogger;
14
use LinkValue\MobileNotif\Exception\PushException;
15
use LinkValue\MobileNotif\Model\Message;
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 8
    public function __construct()
38
    {
39 8
        $this->logger = new NullLogger();
40 8
    }
41
42
    /**
43
     * Set up parameters.
44
     *
45
     * @param array $params
46
     *
47
     * @throws \RuntimeException
48
     */
49 7
    public function setUp(array $params)
50
    {
51 7
        if (empty($params['endpoint'])) {
52 1
            throw new \RuntimeException('Parameter "endpoint" cannot be empty.');
53
        }
54
55 6
        if (empty($params['ssl_pem_path'])) {
56 1
            throw new \RuntimeException('Parameter "ssl_pem_path" cannot be empty.');
57
        }
58
59 5
        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 4
        $this->params = $params;
64 4
    }
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 4
    public function push(Message $message)
75
    {
76 4
        if (empty($this->params)) {
77 1
            throw new \RuntimeException('Please setUp this client before pushing messages.');
78
        }
79
80 3
        $this->logger->info('Connecting to APNS server');
81 3
        $stream = $this->getStreamSocketClient();
82
83 2
        $payload = $message->getPayloadAsJson();
84
85 2
        foreach ($message->getTokens() as $token) {
86 2
            $this->logger->info('Sending message to APNS server.', array(
87 2
                'deviceToken' => $token,
88 2
                'payload' => $payload,
89
            ));
90 2
            $this->writePayloadOnStreamForGivenToken($payload, $stream, $token);
91
        }
92
93 2
        fclose($stream);
94 2
    }
95
96
    /**
97
     * Get the Stream socket client.
98
     *
99
     * Connect to the APNS server and open a client socket to it.
100
     *
101
     * @return resource Socket to the APNS server.
102
     *
103
     * @throws PushException if connection to APNS server failed.
104
     */
105 3
    private function getStreamSocketClient()
106
    {
107 3
        $stream_socket_client = stream_socket_client(
108 3
            $this->params['endpoint'],
109
            $errno,
110
            $errstr,
111 3
            30,
112 3
            STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
113 3
            $this->getStreamContext()
114
        );
115
116 3
        if ($stream_socket_client === false) {
117 1
            throw new PushException('An error occurred while trying to contact APNS server.');
118
        }
119
120 2
        return $stream_socket_client;
121
    }
122
123
    /**
124
     * Get secured stream context from SSL certificate.
125
     *
126
     * @return resource
127
     */
128 3
    private function getStreamContext()
129
    {
130
        $context = array(
131
            'ssl' => array(
132 3
                'local_cert' => $this->params['ssl_pem_path'],
133
            ),
134
        );
135
136
        // Handle Certificate Bundle passphrase
137 3
        if(!empty($this->params['ssl_passphrase'])){
138 1
            $context['ssl']['passphrase'] = $this->params['ssl_passphrase'];
139
        }
140
141 3
        return stream_context_create($context);
142
    }
143
144
    /**
145
     * Write $payload on $stream, it will be sent to given device $token.
146
     *
147
     * @param $payload
148
     * @param $stream
149
     * @param $token
150
     */
151 2
    private function writePayloadOnStreamForGivenToken($payload, $stream, $token)
152
    {
153 2
        $binaryMessage = sprintf('%s%s%s%s%s',
154 2
            chr(0),
155 2
            pack('n', 32),
156 2
            pack('H*', str_replace(' ', '', $token)),
157 2
            pack('n', strlen($payload)),
158
            $payload
159
        );
160
161 2
        fwrite($stream, $binaryMessage, strlen($binaryMessage));
162 2
    }
163
}
164