Client::responseReceived()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Ipag\Sdk\Core;
4
5
use JsonSerializable;
6
use Ipag\Sdk\Exception\HttpException;
7
use Ipag\Sdk\Http\Client\BaseHttpClient;
8
use Ipag\Sdk\Http\Response;
9
use Ipag\Sdk\IO\SerializerInterface;
10
use Ipag\Sdk\Path\CompositePathInterface;
11
use Psr\Log\LoggerInterface;
12
use Psr\Log\NullLogger;
13
use Throwable;
14
15
abstract class Client implements CompositePathInterface
16
{
17
    private static $requestCounter = 0;
18
19
    protected Environment $environment;
20
    protected BaseHttpClient $httpClient;
21
    protected ?SerializerInterface $defaultSerializer;
22
    protected ?LoggerInterface $logger;
23
24
    public function __construct(Environment $environment, BaseHttpClient $httpClient, ?SerializerInterface $defaultSerializer = null, ?LoggerInterface $logger = null)
25
    {
26
        $this->environment = $environment;
27
        $this->httpClient = $httpClient;
28
        $this->defaultSerializer = $defaultSerializer;
29
        $this->logger = $logger ?? new NullLogger();
30
    }
31
32
    //
33
34
    public function getEnvironment(): Environment
35
    {
36
        return $this->environment;
37
    }
38
39
    //
40
41
    protected function serialize($body, ?SerializerInterface $serializer = null): ?string
42
    {
43
        $serializer ??= $this->defaultSerializer;
44
45
        if (!$serializer) {
46
            return $body;
47
        }
48
49
        if ($body instanceof JsonSerializable) {
50
            $body = $body->jsonSerialize();
51
        }
52
53
        if (is_array($body) && $serializer) {
54
            $body = $serializer->serialize($body);
55
        }
56
57
        if (is_object($body) && $serializer) {
58
            $body = $serializer->serialize(get_object_vars($body));
59
        }
60
61
        return $body;
62
    }
63
64
    //
65
66
    protected function responseReceived(Response $response): Response
67
    {
68
        return $response->unSerialize();
69
    }
70
71
    protected function exceptionThrown(Throwable $e): void
72
    {
73
        throw $e;
74
    }
75
76
    //
77
78
    protected function get(string $path, array $query = [], array $header = []): Response
79
    {
80
        return $this->request(__FUNCTION__, $this->joinPath($path), null, $query, $header);
81
    }
82
83
    protected function post(string $path, $body, array $query = [], array $header = []): Response
84
    {
85
        return $this->request(__FUNCTION__, $this->joinPath($path), $body, $query, $header);
86
    }
87
88
    protected function put(string $path, $body, array $query = [], array $header = []): Response
89
    {
90
        return $this->request(__FUNCTION__, $this->joinPath($path), $body, $query, $header);
91
    }
92
93
    protected function patch(string $path, $body, array $query = [], array $header = []): Response
94
    {
95
        return $this->request(__FUNCTION__, $this->joinPath($path), $body, $query, $header);
96
    }
97
98
    protected function delete(string $path, $body, array $query = [], array $header = []): Response
99
    {
100
        return $this->request(__FUNCTION__, $this->joinPath($path), $body, $query, $header);
101
    }
102
103
    protected function head(string $path, array $query = [], array $header = []): Response
104
    {
105
        return $this->request(__FUNCTION__, $this->joinPath($path), null, $query, $header);
106
    }
107
108
    public function request(
109
        string $method,
110
        string $url,
111
        $body,
112
        array $query = [],
113
        array $header = [],
114
        ?SerializerInterface $inputSerializer = null,
115
        ?SerializerInterface $outputSerializer = null
116
    ): Response {
117
        $requestId = $this->incrementRequestCounter();
118
119
        $outputSerializer ??= $this->defaultSerializer;
120
        $inputSerializer ??= $this->defaultSerializer;
121
122
        if ($inputSerializer) {
123
            $header['Content-Type'] = $header['Content-Type'] ?? $inputSerializer->getContentType();
124
        }
125
126
        if ($outputSerializer) {
127
            $header['Accept'] = $header['Accept'] ?? $outputSerializer->getContentType();
128
        }
129
130
        $this->logger->debug("({$requestId}) {$method} {$url} : Sending request", ['body' => $body, 'query' => $query, 'header' => $header]);
0 ignored issues
show
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

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

130
        $this->logger->/** @scrutinizer ignore-call */ 
131
                       debug("({$requestId}) {$method} {$url} : Sending request", ['body' => $body, 'query' => $query, 'header' => $header]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
131
132
        try {
133
            $response = $this->httpClient->request(
134
                $method,
135
                $url,
136
                // @NOTE:
137
                // We are not using our custom serializer from args
138
                // because it easier for an external user to send an alternative body encoded
139
                // with another serialization method (Ex: XML) as an string instead of
140
                // assuming that the response from the current endpoint is also expecting
141
                // to receive XML data.
142
                $this->serialize($body, $inputSerializer),
143
                $query,
144
                $header
145
            ) ?? '';
146
147
            $response = Response::from($response, $this->httpClient->lastResponseHeaders(), $this->httpClient->lastResponseStatusCode());
148
            $response->setSerializer($outputSerializer);
149
150
            $this->logger->debug("({$requestId}) {$method} {$url} : Read successful", ['response' => $response->getBody()]);
151
            return $this->responseReceived($response) ?? $response;
152
        } catch (HttpException $e) {
153
            $response = $e->getResponse();
154
            $this->logger->error("({$requestId}) {$method} {$url} : Read failed with status code {$e->getStatusCode()} {$e->getStatusMessage()}", ['exception' => strval($e), 'response' => $response ? $response->getBody() : null]);
155
156
            if ($response) {
157
                $response->setSerializer($outputSerializer);
158
            }
159
160
            $this->exceptionThrown($e);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Ipag\Sdk\Http\Response. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
161
        } catch (Throwable $e) {
162
            $this->logger->error("({$requestId}) {$method} {$url} : Read failed with unhandled exception", ['exception' => strval($e)]);
163
            $this->exceptionThrown($e);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Ipag\Sdk\Http\Response. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
164
        }
165
    }
166
167
    //
168
169
    public function getParent(): ?CompositePathInterface
170
    {
171
        return $this->environment->getParent();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->environment->getParent() targeting Ipag\Sdk\Core\Environment::getParent() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
172
    }
173
174
    public function setParent(?CompositePathInterface $parent): void
175
    {
176
        $this->environment->setParent($parent);
177
    }
178
179
    public function getPath(): string
180
    {
181
        return $this->environment->getPath();
182
    }
183
184
    public function joinPath(string $relative): string
185
    {
186
        if (filter_var($relative, FILTER_VALIDATE_URL)) {
187
            // If it's a valid URL, that means it's not a relative path, so
188
            // don't append it to our base.
189
            return $relative;
190
        }
191
192
        return $this->environment->joinPath($relative);
193
    }
194
195
    private function incrementRequestCounter(): int
196
    {
197
        return ++self::$requestCounter;
198
    }
199
}