Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.63% covered (warning)
68.63%
35 / 51
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Response
68.63% covered (warning)
68.63%
35 / 51
25.00% covered (danger)
25.00%
1 / 4
25.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
84.00% covered (warning)
84.00%
21 / 25
0.00% covered (danger)
0.00%
0 / 1
8.26
 __get
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 tryDecodeJson
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 checkVersion
31.25% covered (danger)
31.25%
5 / 16
0.00% covered (danger)
0.00%
0 / 1
5.92
1<?php
2
3namespace LDC\LDCCRM\Transport;
4
5use LDC\LDCCRM\Enums\ApiError;
6use LDC\LDCCRM\Enums\ApiStatus;
7use LDC\LDCCRM\Enums\LogLevel;
8use LDC\LDCCRM\Exceptions\CrmException;
9use LDC\LDCCRM\Utils;
10use Psr\Http\Message\ResponseInterface;
11use Psr\Http\Message\StreamInterface;
12use RecursiveArrayIterator;
13use RecursiveIteratorIterator;
14
15/**
16 * @property \LDC\LDCCRM\Enums\ApiStatus $status The response api status
17 * @property \LDC\LDCCRM\Enums\ApiError $apiError The api error status it there is any
18 * @property int $httpCode The http response code
19 * @property string $message The api message
20 * @property array $headers The http reponse headers
21 * @property array $content The http response content, this is parsed json
22 * @property ?array $data The reponse data
23 *
24 * @package LDCCrmClient
25 * @subpackage transport
26 */
27class Response
28{
29    /**
30     * The response status
31     *
32     * @var \LDC\LDCCRM\Enums\ApiStatus
33     */
34    private ApiStatus $status;
35
36    /**
37     * The api error
38     *
39     * @var \LDC\LDCCRM\Enums\ApiError
40     */
41    private ApiError $apiError;
42
43    /**
44     * The response http code
45     *
46     * @var integer
47     */
48    private int $httpCode = 0;
49
50    /**
51     * The response headers
52     *
53     * @var array<string,string[]>
54     */
55    private array $headers;
56
57    /**
58     * The response body
59     *
60     * @var \Psr\Http\Message\StreamInterface
61     */
62    private StreamInterface $body;
63
64    /**
65     * The response decoded content
66     *
67     * @var array
68     */
69    private array $content;
70
71    /**
72     * The interface message
73     *
74     * @var string
75     */
76    private string $message;
77
78    /**
79     * PSR response
80     *
81     * @var \Psr\Http\Message\ResponseInterface
82     */
83    private ResponseInterface $response;
84
85    /**
86     * Response
87     *
88     * @param \LDC\LDCCRM\Enums\ApiStatus         $status
89     * @param \Psr\Http\Message\ResponseInterface $response
90     * @throws \LDC\LDCCRM\Exceptions\CrmException If HTTP response body in not JSON.
91     */
92    public function __construct(ApiStatus $status, ResponseInterface $response)
93    {
94        $this->response = $response;
95        $this->status   = $status;
96        $this->httpCode = $response->getStatusCode();
97        $this->headers  = $response->getHeaders();
98        $this->body     = $response->getBody();
99
100        if (!in_array($response->getStatusCode(), [200, 201])) {
101            $this->message = $response->getReasonPhrase();
102        }
103
104
105        // * Body should be json data.
106        if (($decodedJson = $this->tryDecodeJson($this->body->getContents())) === false) {
107            if (Utils::getConfig('app.env') === 'testing') {
108                dump((string)$this->body);
109            }
110            throw new CrmException('The Api should have returned JSON data');
111        }
112        $this->content = $decodedJson;
113
114        // * If Api Error.
115        if ($this->status->equals(ApiStatus::KO)) {
116            // * If CRM Exception or Any Exception.
117            if ($this->httpCode === 500) {
118                $this->apiError = $this->content['data']['code'];
119            } else {
120                // * From here it should be an ApiErrorException.
121                $this->apiError = ApiError::make($this->content['data']['code']);
122                $this->message  = $this->content['data']['message'];
123                if ($this->apiError->equals(ApiError::validationFailed)) {
124                    $errors = new RecursiveIteratorIterator(
125                        new RecursiveArrayIterator($this->content['data']['errors'])
126                    );
127                    foreach ($errors as $error) {
128                        $this->message = "$this->message $error";
129                    }
130                }
131            }
132        }
133
134        $this->checkVersion($this->content['version']);
135
136        return $this;
137    }
138
139    /**
140     * Generic accessor
141     *
142     * @param string $field
143     * @return mixed|null
144     */
145    public function __get(string $field)
146    {
147        if (
148            \in_array($field, [
149                'status', 'httpCode', 'headers',
150                'content', 'message', 'response', 'apiError'
151            ])
152        ) {
153            return $this->{$field};
154        }
155        if ($field === 'data' and isset($this->content[$field])) {
156            return $this->content[$field];
157        }
158        return null;
159    }
160
161    /**
162     * Try to decode json and return false if it is
163     * not a valid json
164     *
165     * @param string $string
166     * @return array|false Returns an associative data array or false if failed to parse.
167     */
168    private function tryDecodeJson(string $string)
169    {
170        /** @var array|null */
171        $data = json_decode($string, true);
172        return (json_last_error() == JSON_ERROR_NONE) ? $data : false;
173    }
174
175    /**
176     * This checks if the Flucollector version is way far beyond the client version.
177     * If a minor version mismatch is detected we will log this as a warning.
178     * If a major version mismatch is detected we will throw a AppilcationException as
179     * there shall be an incompatibility with the response.
180     *
181     * The goal here is to force clients to be up to date.
182     * A major version change means breaking change.
183     *
184     * @param string $version
185     * @return void
186     * @throws \LDC\LDCCRM\Exceptions\CrmException If a major version mismatch is detected.
187     */
188    private function checkVersion(string $version)
189    {
190        $packageInfo  = Utils::getPackageInfo();
191        $ApackageInfo = explode('.', $packageInfo['version']);
192        $Aversion     = \explode('.', $version);
193        if ($ApackageInfo[0] != $Aversion[0]) {
194            throw new CrmException(sprintf(
195                'Incompatible major versions between CRM server %s and client %s',
196                $version,
197                $packageInfo['version']
198            ));
199        }
200        if ($ApackageInfo[1] != $Aversion[1]) {
201            $message = sprintf(
202                'Minor version mismatch between CRM server %s and client %s, this could lead to errors',
203                $version,
204                $packageInfo['version']
205            );
206            Utils::log($message, LogLevel::WARNING);
207        }
208    }
209}