OneSignalClient::execute()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.584
c 0
b 0
f 0
cc 3
nc 3
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Explicit Architecture POC,
7
 * which is created on top of the Symfony Demo application.
8
 *
9
 * (c) Herberto Graça <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Acme\App\Infrastructure\Notification\Client\Push\OneSignal;
16
17
use Acme\App\Core\Port\Notification\Client\Push\Exception\PushNotifierException;
18
use Acme\App\Core\Port\Notification\Client\Push\PushNotification;
19
use Acme\App\Core\Port\Notification\Client\Push\PushNotifierInterface;
20
use Acme\App\Core\Port\Notification\Client\Push\PushNotifierResponse;
21
use Acme\App\Core\SharedKernel\Component\User\Domain\User\UserId;
22
use Exception;
23
use GuzzleHttp\Client;
24
use GuzzleHttp\ClientInterface;
25
use GuzzleHttp\Exception\ClientException;
26
use GuzzleHttp\Exception\GuzzleException;
27
28
/**
29
 * @author Herberto Graca <[email protected]>
30
 * @author Alexander Malyk
31
 */
32
final class OneSignalClient implements PushNotifierInterface
33
{
34
    public const API_BASE_URI = 'https://onesignal.com/api/v1/';
35
    private const HTTP_TIMEOUT = 30;
36
37
    public const ENDPOINT_NOTIFICATIONS = 'notifications';
38
39
    private const RELATION_EQUAL = '=';
40
41
    private const FIELD_TAG = 'tag';
42
43
    // This tag name needs to stay in sync with what is used in the mobile app
44
    public const TAG_USER_ID = 'userId';
45
46
    private const DEFAULT_LANGUAGE = 'en';
47
48
    /**
49
     * @var string
50
     */
51
    private $appIDKey;
52
53
    /**
54
     * @var string
55
     */
56
    private $restAPIKey;
57
58
    /**
59
     * @var ClientInterface
60
     */
61
    private $httpClient;
62
63
    public function __construct(
64
        string $appIDKey,
65
        string $restAPIKey,
66
        ClientInterface $httpClient = null
67
    ) {
68
        $this->appIDKey = $appIDKey;
69
        $this->restAPIKey = $restAPIKey;
70
        $this->httpClient = $httpClient ?? new Client(['base_uri' => self::API_BASE_URI, 'timeout' => self::HTTP_TIMEOUT]);
71
    }
72
73
    /**
74
     * @throws GuzzleException
75
     */
76
    public function sendNotification(PushNotification $pushNotification): PushNotifierResponse
77
    {
78
        return $this->execute(
79
            self::ENDPOINT_NOTIFICATIONS,
80
            'POST',
81
            [
82
                'headers' => [
83
                    'Content-Type' => 'application/json; charset=utf-8',
84
                ],
85
                'json' => [
86
                    'app_id' => $this->appIDKey,
87
                    'filters' => [
88
                        [
89
                            'field' => self::FIELD_TAG,
90
                            'key' => self::TAG_USER_ID,
91
                            'relation' => self::RELATION_EQUAL,
92
                            'value' => $this->createUserIdTag($pushNotification->getUserId()),
93
                        ],
94
                    ],
95
                    'contents' => [self::DEFAULT_LANGUAGE => $pushNotification->getMessage()],
96
                    'headings' => [self::DEFAULT_LANGUAGE => $pushNotification->getTitle()],
97
                    'data' => array_merge($pushNotification->getData(), ['short_name' => $pushNotification->getShortName()]),
98
                ],
99
            ]
100
        );
101
    }
102
103
    /**
104
     * @throws PushNotifierException
105
     * @throws GuzzleException
106
     */
107
    private function execute(string $url, string $method, array $httpClientOptions = []): PushNotifierResponse
108
    {
109
        $defaultHttpClientOptions = [
110
            'headers' => [
111
                'Authorization' => sprintf('Basic %s', $this->restAPIKey),
112
            ],
113
            'form_params' => [],
114
        ];
115
        $endpointURL = sprintf('%s%s', self::API_BASE_URI, $url);
116
        $httpClientOptions = array_replace_recursive($defaultHttpClientOptions, $httpClientOptions);
117
118
        try {
119
            $response = $this->httpClient->request(mb_strtoupper($method), $endpointURL, $httpClientOptions);
120
        } catch (ClientException $exception) {
121
            $response = $exception->getResponse();
122
        } catch (Exception $exception) {
123
            throw new PushNotifierException($exception->getMessage(), 0, $exception);
124
        }
125
126
        return new PushNotifierResponse($response);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $exception->getResponse() on line 121 can be null; however, Acme\App\Core\Port\Notif...Response::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
127
    }
128
129
    private function createUserIdTag(UserId $userId): string
130
    {
131
        return (string) $userId;
132
    }
133
}
134