ApiResource::apiUrl()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.2
cc 4
eloc 5
nc 4
nop 2
1
<?php
2
3
namespace Zurbaev\ApiClient;
4
5
use ArrayAccess;
6
use GuzzleHttp\ClientInterface;
7
use Psr\Http\Message\ResponseInterface;
8
use GuzzleHttp\Exception\RequestException;
9
use Zurbaev\ApiClient\Contracts\ApiProviderInterface;
10
use Zurbaev\ApiClient\Traits\ArrayAccessTrait;
11
use Zurbaev\ApiClient\Contracts\ApiResourceInterface;
12
use Zurbaev\ApiClient\Exceptions\Resources\DeleteResourceException;
13
use Zurbaev\ApiClient\Exceptions\Resources\UpdateResourceException;
14
15
abstract class ApiResource implements ArrayAccess, ApiResourceInterface
16
{
17
    use ArrayAccessTrait;
18
19
    /**
20
     * @var ApiProviderInterface
21
     */
22
    protected $api;
23
24
    /**
25
     * @var array
26
     */
27
    protected $data = [];
28
29
    /**
30
     * @var ApiResourceInterface
31
     */
32
    protected $owner;
33
34
    /**
35
     * Create new resource instance.
36
     *
37
     * @param ApiProviderInterface $api   = null
38
     * @param array                $data  = []
39
     * @param ApiResourceInterface $owner
40
     */
41
    public function __construct(ApiProviderInterface $api = null, array $data = [], ApiResourceInterface $owner = null)
42
    {
43
        $this->api = $api;
44
        $this->data = $data;
45
        $this->owner = $owner;
46
    }
47
48
    /**
49
     * Resource type.
50
     *
51
     * @return string
52
     */
53
    abstract public static function resourceType();
54
55
    /**
56
     * Resource path (relative to owner or API root).
57
     *
58
     * @return string
59
     */
60
    abstract public function resourcePath();
61
62
    /**
63
     * Create new Resource instance from HTTP response.
64
     *
65
     * @param ResponseInterface    $response
66
     * @param ApiProviderInterface $api
67
     * @param ApiResourceInterface $owner    = null
68
     *
69
     * @return static
70
     */
71
    public static function createFromResponse(ResponseInterface $response, ApiProviderInterface $api, ApiResourceInterface $owner = null)
72
    {
73
        $json = json_decode((string) $response->getBody(), true);
74
        $resourceType = static::resourceType();
75
76
        if (empty($json[$resourceType])) {
77
            static::throwNotFoundException();
78
        }
79
80
        return new static($api, $json[$resourceType], $owner);
81
    }
82
83
    /**
84
     * Throw HTTP Not Found exception.
85
     *
86
     * @throws \InvalidArgumentException
87
     */
88
    protected static function throwNotFoundException()
89
    {
90
        throw new \InvalidArgumentException('Given response is not a '.static::resourceType().' response.');
91
    }
92
93
    /**
94
     * Determines if current resource has an owner.
95
     *
96
     * @return bool
97
     */
98
    public function hasResourceOwner(): bool
99
    {
100
        return !is_null($this->owner);
101
    }
102
103
    /**
104
     * Get current resource owner.
105
     *
106
     * @return ApiResourceInterface|null
107
     */
108
    public function resourceOwner()
109
    {
110
        return $this->owner;
111
    }
112
113
    /**
114
     * Get API provider.
115
     *
116
     * @return ApiProviderInterface
117
     */
118
    public function getApi(): ApiProviderInterface
119
    {
120
        return $this->api;
121
    }
122
123
    /**
124
     * Get underlying API provider's HTTP client.
125
     *
126
     * @return ClientInterface
127
     */
128
    public function getHttpClient(): ClientInterface
129
    {
130
        return $this->api->getClient();
131
    }
132
133
    /**
134
     * Get resource data.
135
     *
136
     * @param string|int $key
137
     * @param mixed      $default = null
138
     *
139
     * @return mixed|null
140
     */
141
    public function getData($key, $default = null)
142
    {
143
        return $this->data[$key] ?? $default;
144
    }
145
146
    /**
147
     * Resource API URL.
148
     *
149
     * @param string $path            = ''
150
     * @param bool   $withPropagation = true
151
     *
152
     * @return string
153
     */
154
    public function apiUrl(string $path = '', bool $withPropagation = true): string
155
    {
156
        $path = ($path ? '/'.ltrim($path, '/') : '');
157
        $resourcePath = rtrim($this->resourcePath(), '/').'/'.$this->id().$path;
158
159
        if (!$this->hasResourceOwner() || !$withPropagation) {
160
            return $resourcePath;
161
        }
162
163
        return $this->resourceOwner()->apiUrl($resourcePath);
164
    }
165
166
    /**
167
     * Resource ID.
168
     *
169
     * @return mixed
170
     */
171
    public function id()
172
    {
173
        return $this->getData('id', 0);
174
    }
175
176
    /**
177
     * Update resource data.
178
     *
179
     * @param array $payload
180
     *
181
     * @throws UpdateResourceException
182
     *
183
     * @return bool
184
     */
185
    public function update(array $payload): bool
186
    {
187
        $resourceType = static::resourceType();
188
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
189
190
        try {
191
            $response = $this->getHttpClient()->request('PUT', $this->apiUrl(), [
192
                'json' => $payload,
193
            ]);
194
        } catch (RequestException $e) {
195
            $this->throwResourceException($e->getResponse(), 'update', UpdateResourceException::class);
196
        }
197
198
        $json = json_decode((string) $response->getBody(), true);
199
200
        if (empty($json[$resourceType])) {
201
            return false;
202
        }
203
204
        $this->data = $json[$resourceType];
205
206
        return true;
207
    }
208
209
    /**
210
     * Delete current resource.
211
     *
212
     * @throws DeleteResourceException
213
     *
214
     * @return bool
215
     */
216
    public function delete()
217
    {
218
        try {
219
            $this->getHttpClient()->request('DELETE', $this->apiUrl());
220
        } catch (RequestException $e) {
221
            $this->throwResourceException($e->getResponse(), 'delete', DeleteResourceException::class);
222
        }
223
224
        return true;
225
    }
226
227
    /**
228
     * Throw resource exception.
229
     *
230
     * @param ResponseInterface $response
231
     * @param string            $action
232
     * @param string            $exceptionClass
233
     *
234
     * @throws \Exception
235
     */
236
    protected function throwResourceException(ResponseInterface $response, string $action, string $exceptionClass)
237
    {
238
        $message = 'Unable to '.$action.' resource (type: '.static::resourceType().', ID: '.$this->id().').';
239
240
        if (is_null($response)) {
241
            throw new \InvalidArgumentException($message);
242
        }
243
244
        $message .= ' Server response: "'.((string) $response->getBody()).'".';
245
246
        throw new $exceptionClass($message, $response->getStatusCode());
247
    }
248
}
249