Completed
Push — master ( 1a89ae...52ec8c )
by Timur
01:15
created

ApiResource   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 5
dl 0
loc 273
rs 10
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
resourceType() 0 1 ?
resourcePath() 0 1 ?
A __construct() 0 6 1
A createFromResponse() 0 11 2
A throwNotFoundException() 0 4 1
A hasResourceOwner() 0 4 1
A resourceOwner() 0 4 1
A getApi() 0 4 1
A getHttpClient() 0 4 1
A getData() 0 4 1
A getFullData() 0 4 1
A apiUrl() 0 11 4
A id() 0 4 1
A name() 0 4 1
A status() 0 4 1
A createdAt() 0 4 1
A update() 0 22 3
A delete() 0 10 2
A throwResourceException() 0 12 2
1
<?php
2
3
namespace Laravel\Forge;
4
5
use ArrayAccess;
6
use InvalidArgumentException;
7
use GuzzleHttp\ClientInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use GuzzleHttp\Exception\RequestException;
10
use Laravel\Forge\Traits\ArrayAccessTrait;
11
use Laravel\Forge\Contracts\ResourceContract;
12
use Laravel\Forge\Exceptions\Resources\DeleteResourceException;
13
use Laravel\Forge\Exceptions\Resources\UpdateResourceException;
14
15
abstract class ApiResource implements ArrayAccess, ResourceContract
16
{
17
    use ArrayAccessTrait;
18
19
    /**
20
     * @var \Laravel\Forge\ApiProvider
21
     */
22
    protected $api;
23
24
    /**
25
     * @var array
26
     */
27
    protected $data = [];
28
29
    /**
30
     * @var \Laravel\Forge\Contracts\ResourceContract
31
     */
32
    protected $owner;
33
34
    /**
35
     * Create new resource instance.
36
     *
37
     * @param \Laravel\Forge\ApiProvider                $api   = null
38
     * @param array                                     $data  = []
39
     * @param \Laravel\Forge\Contracts\ResourceContract $owner
40
     */
41
    public function __construct(ApiProvider $api = null, array $data = [], ResourceContract $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 \Psr\Http\Message\ResponseInterface       $response
66
     * @param \Laravel\Forge\ApiProvider                $api
67
     * @param \Laravel\Forge\Contracts\ResourceContract $owner    = null
68
     *
69
     * @return static
70
     */
71
    public static function createFromResponse(ResponseInterface $response, ApiProvider $api, ResourceContract $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 \Laravel\Forge\Contracts\ResourceContract|null
107
     */
108
    public function resourceOwner()
109
    {
110
        return $this->owner;
111
    }
112
113
    /**
114
     * Get API provider.
115
     *
116
     * @return \Laravel\Forge\ApiProvider
117
     */
118
    public function getApi(): ApiProvider
119
    {
120
        return $this->api;
121
    }
122
123
    /**
124
     * Get underlying API provider's HTTP client.
125
     *
126
     * @return \GuzzleHttp\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
     * Get full resource data.
148
     *
149
     * @return array
150
     */
151
    public function getFullData()
152
    {
153
        return $this->data;
154
    }
155
156
    /**
157
     * Resource API URL.
158
     *
159
     * @param string $path            = ''
160
     * @param bool   $withPropagation = true
161
     *
162
     * @return string
163
     */
164
    public function apiUrl(string $path = '', bool $withPropagation = true): string
165
    {
166
        $path = ($path ? '/'.ltrim($path, '/') : '');
167
        $resourcePath = rtrim($this->resourcePath(), '/').'/'.$this->id().$path;
168
169
        if (!$this->hasResourceOwner() || !$withPropagation) {
170
            return $resourcePath;
171
        }
172
173
        return $this->resourceOwner()->apiUrl($resourcePath);
174
    }
175
176
    /**
177
     * Resource ID.
178
     *
179
     * @return int
180
     */
181
    public function id(): int
182
    {
183
        return intval($this->getData('id', 0));
184
    }
185
186
    /**
187
     * Resource name.
188
     *
189
     * @return string|null
190
     */
191
    public function name()
192
    {
193
        return $this->getData('name');
194
    }
195
196
    /**
197
     * Resource status.
198
     *
199
     * @return string|null
200
     */
201
    public function status()
202
    {
203
        return $this->getData('status');
204
    }
205
206
    /**
207
     * Get resource creation date.
208
     *
209
     * @return string|null
210
     */
211
    public function createdAt()
212
    {
213
        return $this->getData('created_at');
214
    }
215
216
    /**
217
     * Update resource data.
218
     *
219
     * @param array $payload
220
     *
221
     * @throws UpdateResourceException
222
     *
223
     * @return bool
224
     */
225
    public function update(array $payload): bool
226
    {
227
        $resourceType = static::resourceType();
228
229
        try {
230
            $response = $this->getHttpClient()->request('PUT', $this->apiUrl(), [
231
                'json' => $payload,
232
            ]);
233
        } catch (RequestException $e) {
234
            $this->throwResourceException($e->getResponse(), 'update', UpdateResourceException::class);
0 ignored issues
show
Bug introduced by
It seems like $e->getResponse() can be null; however, throwResourceException() 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...
235
        }
236
237
        $json = json_decode((string) $response->getBody(), true);
238
239
        if (empty($json[$resourceType])) {
240
            return false;
241
        }
242
243
        $this->data = $json[$resourceType];
244
245
        return true;
246
    }
247
248
    /**
249
     * Delete current resource.
250
     *
251
     * @throws \Laravel\Forge\Exceptions\Resources\DeleteResourceException
252
     *
253
     * @return bool
254
     */
255
    public function delete()
256
    {
257
        try {
258
            $this->getHttpClient()->request('DELETE', $this->apiUrl());
259
        } catch (RequestException $e) {
260
            $this->throwResourceException($e->getResponse(), 'delete', DeleteResourceException::class);
0 ignored issues
show
Bug introduced by
It seems like $e->getResponse() can be null; however, throwResourceException() 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...
261
        }
262
263
        return true;
264
    }
265
266
    /**
267
     * Throw resource exception.
268
     *
269
     * @param \Psr\Http\Message\ResponseInterface $response
270
     * @param string                              $action
271
     * @param string                              $exceptionClass
272
     *
273
     * @throws \Exception
274
     */
275
    protected function throwResourceException(ResponseInterface $response, string $action, string $exceptionClass)
276
    {
277
        $message = 'Unable to '.$action.' resource (type: '.static::resourceType().', ID: '.$this->id().').';
278
279
        if (is_null($response)) {
280
            throw new InvalidArgumentException($message);
281
        }
282
283
        $message .= ' Server response: "'.((string) $response->getBody()).'".';
284
285
        throw new $exceptionClass($message, $response->getStatusCode());
286
    }
287
}
288