Passed
Push — develop ( 43506b...705360 )
by Pieter van der
03:49 queued 12s
created

Tiqr_Message_APNS2::send()   B

Complexity

Conditions 10
Paths 22

Size

Total Lines 93
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 60
c 2
b 0
f 0
dl 0
loc 93
ccs 0
cts 75
cp 0
rs 7.006
cc 10
nc 22
nop 0
crap 110

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use Pushok\AuthProvider;
4
use Pushok\Client;
5
use Pushok\Notification;
6
use Pushok\Payload;
7
use Pushok\Payload\Alert;
8
9
/** @internal includes */
10
require_once('Tiqr/Message/Abstract.php');
11
12
/**
13
 * Apple Push Notification Service message class for the HTTP/2 api.push.apple.com APNs API.
14
 */
15
class Tiqr_Message_APNS2 extends Tiqr_Message_Abstract
16
{
17
    /**
18
     * Send message.
19
     */
20
    public function send()
21
    {
22
        $version_info = curl_version();
23
        if ($version_info['features'] & CURL_VERSION_HTTP2 == 0) {
24
            throw new RuntimeException('APNS2 requires HTTP/2 support in curl');
25
        }
26
27
        // Get the UID from the client certificate we use for authentication, this
28
        // is set to the bundle ID.
29
        $options=$this->getOptions();
30
        $cert_filename = $options['apns.certificate'];
31
        $cert_file_contents = file_get_contents($cert_filename);
32
        if (false === $cert_file_contents) {
33
            throw new RuntimeException(
34
                sprintf('Error reading APNS client certificate file: "%s"', $cert_filename)
35
            );
36
        }
37
38
        $cert=openssl_x509_parse( $cert_file_contents );
39
        if (false === $cert) {
40
            throw new RuntimeException('Error parsing APNS client certificate');
41
        }
42
        $bundle_id = $cert['subject']['UID'] ?? NULL;
43
        if (NULL === $bundle_id) {
44
            throw new RuntimeException('No uid found in the certificate subject');
45
        }
46
        $this->logger->info(sprintf('Setting bundle_id to "%s" based on UID from certificate', $bundle_id));
47
        $this->logger->info(
48
            sprintf('Authenticating using certificate with subject "%s" valid until "%s"',
49
                $cert['name'],
50
                date(DATE_RFC2822, $cert['validTo_time_t'])
51
            )
52
        );
53
54
        $authProviderOptions = [
55
            'app_bundle_id' => $bundle_id, // The bundle ID for app obtained from Apple developer account
56
            'certificate_path' => $cert_filename,
57
            'certificate_secret' => null // Private key secret
58
        ];
59
60
        $authProvider = AuthProvider\Certificate::create($authProviderOptions);
61
62
        // Create the push message
63
        $alert=Alert::create();
64
        $alert->setBody($this->getText());
65
        // Note: It is possible to specify a title and a subtitle: $alert->setTitle() && $alert->setSubtitle()
66
        //       The tiqr service currently does not implement this.
67
        $payload=Payload::create()->setAlert($alert);
68
        $payload->setSound('default');
69
        foreach ($this->getCustomProperties() as $name => $value) {
70
            $payload->setCustomValue($name, $value);
71
        }
72
        $this->logger->debug(sprintf('JSON Payload: %s', $payload->toJson()));
73
        $notification=new Notification($payload, $this->getAddress());
74
        // Set expiration to 30 seconds from now, same as Message_APNS
75
        $now = new DateTimeImmutable();
76
        $expirationInstant=$now->add(new DateInterval('PT30S'));
77
        $notification->setExpirationAt($expirationInstant);
0 ignored issues
show
Bug introduced by
$expirationInstant of type DateTimeImmutable is incompatible with the type DateTime expected by parameter $expirationAt of Pushok\Notification::setExpirationAt(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

77
        $notification->setExpirationAt(/** @scrutinizer ignore-type */ $expirationInstant);
Loading history...
78
79
        // Send the push message
80
        $client = new Client($authProvider, $options['apns.environment'] == 'production');
81
        $client->addNotification($notification);
82
        $responses=$client->push();
83
        if ( sizeof($responses) != 1) {
84
            $this->logger->warning(sprintf('Unexpected number responses. Expected 1, got %d', sizeof($responses)) );
85
            if (sizeof($responses) == 0) {
86
                $this->logger->warning('Could not determine whether the notification was sent');
87
                return;
88
            }
89
        }
90
        /** @var \Pushok\Response $response */
91
        $response = reset($responses);  // Get first response from the array
92
        $deviceToken=$response->getDeviceToken() ?? '';
93
        // A canonical UUID that is the unique ID for the notification. E.g. 123e4567-e89b-12d3-a456-4266554400a0
94
        $apnsId=$response->getApnsId() ?? '';
95
        // Status code. E.g. 200 (Success), 410 (The device token is no longer active for the topic.)
96
        $statusCode=$response->getStatusCode();
97
        $this->logger->info(sprintf('Got response with ApnsId "%s", status %s for deviceToken "%s"', $apnsId, $statusCode, $deviceToken));
98
        if ( strcasecmp($deviceToken, $this->getAddress()) ) {
99
        $this->logger->warning(sprintf('Unexpected deviceToken in response. Expected: "%s"; got: "%s"', $this->getAddress(), $deviceToken));
100
        }
101
        if ($statusCode == 200) {
102
            $this->logger->notice(sprintf('Successfully sent APNS2 push notification. APNS ID: "%s"; deviceToken: "%s"', $apnsId, $deviceToken));
103
            return;
104
        }
105
106
        $reasonPhrase=$response->getReasonPhrase(); // E.g. The device token is no longer active for the topic.
107
        $errorReason=$response->getErrorReason(); // E.g. Unregistered
108
        $errorDescription=$response->getErrorDescription(); // E.g. The device token is inactive for the specified topic.
109
110
        $this->logger->error(sprintf('Error sending APNS2 push notification. APNS ID: "%s"; deviceToken: "%s"; Error: "%s" "%s" "%s"', $apnsId, $deviceToken, $reasonPhrase, $errorReason, $errorDescription));
111
        throw new RuntimeException(
112
            sprintf('Error sending APNS2 push notification. Status: %s. Error: "%s" "%s" "%s"', $statusCode, $reasonPhrase, $errorReason, $errorDescription)
113
        );
114
    }
115
}
116