Completed
Push — master ( 72cf95...b4fc03 )
by Artem
08:38
created

ResponseProcessor   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 20
lcom 1
cbo 14
dl 0
loc 147
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
B processResponse() 0 18 5
A processHttpOkResponse() 0 12 2
C processHttpOkResponseWithoutError() 0 43 7
A processHttpOkResponseWithError() 0 4 1
A getBodyAsArray() 0 15 3
A responseContentTypeIsJson() 0 5 2
1
<?php
2
/*
3
 * This file is part of the FirebaseCloudMessagingBundle
4
 *
5
 * (c) Artem Henvald <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Fresh\FirebaseCloudMessagingBundle\Response;
14
15
use Fresh\FirebaseCloudMessagingBundle\Exception\AuthenticationException;
16
use Fresh\FirebaseCloudMessagingBundle\Exception\ExceptionInterface;
17
use Fresh\FirebaseCloudMessagingBundle\Exception\InternalServerErrorException;
18
use Fresh\FirebaseCloudMessagingBundle\Exception\InvalidJsonException;
19
use Fresh\FirebaseCloudMessagingBundle\Exception\UnsupportedResponseException;
20
use Fresh\FirebaseCloudMessagingBundle\Message\Part\Target\TokenTargetInterface;
21
use Fresh\FirebaseCloudMessagingBundle\Message\Type\AbstractMessage;
22
use Fresh\FirebaseCloudMessagingBundle\Response\MessageResult\CanonicalTokenMessageResult;
23
use Fresh\FirebaseCloudMessagingBundle\Response\MessageResult\Collection\CanonicalTokenMessageResultCollection;
24
use Fresh\FirebaseCloudMessagingBundle\Response\MessageResult\Collection\FailedMessageResultCollection;
25
use Fresh\FirebaseCloudMessagingBundle\Response\MessageResult\Collection\SuccessfulMessageResultCollection;
26
use Fresh\FirebaseCloudMessagingBundle\Response\MessageResult\FailedMessageResult;
27
use Fresh\FirebaseCloudMessagingBundle\Response\MessageResult\SuccessfulMessageResult;
28
use Psr\Http\Message\ResponseInterface;
29
use Symfony\Component\HttpFoundation\Response;
30
31
/**
32
 * Class ResponseProcessor.
33
 *
34
 * @author Artem Henvald <[email protected]>
35
 */
36
class ResponseProcessor
37
{
38
    /** @var array */
39
    private $jsonContentTypes = [
40
        'application/json',
41
        'application/json; charset=UTF-8',
42
    ];
43
44
    /** @var AbstractMessage */
45
    private $message;
46
47
    /**
48
     * @param AbstractMessage   $message
49
     * @param ResponseInterface $response
50
     *
51
     * @throws ExceptionInterface
52
     *
53
     * @return FirebaseResponseInterface
54
     */
55
    public function processResponse(AbstractMessage $message, ResponseInterface $response): FirebaseResponseInterface
56
    {
57
        $this->message = $message;
58
59
        if (Response::HTTP_OK === $response->getStatusCode()) {
60
            $result = $this->processHttpOkResponse($response);
61
        } elseif (Response::HTTP_BAD_REQUEST === $response->getStatusCode()) {
62
            throw new InvalidJsonException();
63
        } elseif (Response::HTTP_UNAUTHORIZED === $response->getStatusCode()) {
64
            throw new AuthenticationException();
65
        } elseif (Response::HTTP_INTERNAL_SERVER_ERROR === $response->getStatusCode()) {
66
            throw new InternalServerErrorException();
67
        } else {
68
            throw new UnsupportedResponseException();
69
        }
70
71
        return $result;
72
    }
73
74
    /**
75
     * @param ResponseInterface $response
76
     *
77
     * @return FirebaseResponseInterface
78
     */
79
    private function processHttpOkResponse(ResponseInterface $response)
80
    {
81
        $body = $this->getBodyAsArray($response);
82
83
        if (isset($body['error'])) {
84
            $response = $this->processHttpOkResponseWithError($body);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $response is correct as $this->processHttpOkResponseWithError($body) (which targets Fresh\FirebaseCloudMessa...tpOkResponseWithError()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
85
        } else {
86
            $response = $this->processHttpOkResponseWithoutError($body);
87
        }
88
89
        return $response;
90
    }
91
92
    /**
93
     * @param array $body
94
     *
95
     * @return MulticastMessageResponse
96
     */
97
    private function processHttpOkResponseWithoutError(array $body): MulticastMessageResponse
98
    {
99
        $successfulMessageResults = new SuccessfulMessageResultCollection();
100
        $failedMessageResults = new FailedMessageResultCollection();
101
        $canonicalTokenMessageResults = new CanonicalTokenMessageResultCollection();
102
103
        if ($this->message->getTarget() instanceof TokenTargetInterface) {
104
            $numberOfSequentialSentTokens = $this->message->getTarget()->getNumberOfSequentialSentTokens();
105
106
            if (isset($body['results']) && \count($body['results']) !== $numberOfSequentialSentTokens) {
107
                throw new \Exception('Mismatch number of sent tokens and results');
108
            }
109
110
            for ($i = 0; $i < $numberOfSequentialSentTokens; ++$i) {
111
                $currentToken = $this->message->getTarget()->getSequentialSentTokens()[$i];
112
                $currentResult = $body['results'][$i];
113
114
                if (isset($currentResult['error'])) {
115
                    $messageResult = (new FailedMessageResult())
116
                        ->setError($currentResult['error']);
117
118
                    $failedMessageResults[] = $messageResult;
119
                } elseif (isset($currentResult['registration_id'])) {
120
                    $messageResult = (new CanonicalTokenMessageResult())
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fresh\FirebaseCloudMessa...SuccessfulMessageResult as the method setCanonicalToken() does only exist in the following sub-classes of Fresh\FirebaseCloudMessa...SuccessfulMessageResult: Fresh\FirebaseCloudMessa...nicalTokenMessageResult. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
121
                        ->setMessageId($currentResult['message_id'])
122
                        ->setCanonicalToken($currentResult['registration_id']);
123
124
                    $canonicalTokenMessageResults[] = $messageResult;
125
                } else {
126
                    $messageResult = (new SuccessfulMessageResult())->setMessageId($currentResult['message_id']);
127
                    $successfulMessageResults[] = $messageResult;
128
                }
129
130
                $messageResult->setToken($currentToken);
131
            }
132
        }
133
134
        return (new MulticastMessageResponse())
135
            ->setMulticastId($body['multicast_id'])
136
            ->setSuccessfulMessageResults($successfulMessageResults)
137
            ->setFailedMessageResults($failedMessageResults)
138
            ->setCanonicalTokenMessageResults($canonicalTokenMessageResults);
139
    }
140
141
    /**
142
     * @param array $body
143
     */
144
    private function processHttpOkResponseWithError(array $body)
0 ignored issues
show
Unused Code introduced by
The parameter $body is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    {
146
        // @todo finish it
147
    }
148
149
    /**
150
     * @param ResponseInterface $response
151
     *
152
     * @throws \InvalidArgumentException
153
     *
154
     * @return array
155
     */
156
    private function getBodyAsArray(ResponseInterface $response): array
157
    {
158
        if ($this->responseContentTypeIsJson($response)) {
159
            $response->getBody()->rewind();
160
            $body = null;
161
162
            if ($response->getBody()->getSize() > 0) {
163
                $body = $response->getBody()->getContents();
164
            }
165
166
            return \json_decode($body, true);
167
        }
168
169
        throw new \InvalidArgumentException('Response from Firebase Cloud Messaging is not a JSON');
170
    }
171
172
    /**
173
     * @param ResponseInterface $response
174
     *
175
     * @return bool
176
     */
177
    private function responseContentTypeIsJson(ResponseInterface $response): bool
178
    {
179
        return $response->hasHeader('Content-Type')
180
               && \in_array($response->getHeader('Content-Type')[0], $this->jsonContentTypes);
181
    }
182
}
183