• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MichaelJ2324 / PHP-REST-Client / 13080782723

31 Jan 2025 09:06PM UTC coverage: 94.359% (+2.5%) from 91.889%
13080782723

push

github

MichaelJ2324
Test Coverage + Auth Request Logging/Handling

67 of 73 new or added lines in 11 files covered. (91.78%)

1 existing line in 1 file now uncovered.

1037 of 1099 relevant lines covered (94.36%)

1.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.03
/src/Endpoint/Abstracts/AbstractCollectionEndpoint.php
1
<?php
2

3
namespace MRussell\REST\Endpoint\Abstracts;
4

5
use MRussell\REST\Exception\Endpoint\InvalidRequest;
6
use GuzzleHttp\Psr7\Response;
7
use MRussell\REST\Endpoint\Data\DataInterface;
8
use MRussell\REST\Endpoint\Interfaces\CollectionInterface;
9
use MRussell\REST\Endpoint\Interfaces\ModelInterface;
10
use MRussell\REST\Endpoint\Traits\ParseResponseBodyToArrayTrait;
11
use MRussell\REST\Exception\Endpoint\UnknownEndpoint;
12

13
abstract class AbstractCollectionEndpoint extends AbstractSmartEndpoint implements
14
    CollectionInterface,
15
    \ArrayAccess,
16
    \Iterator
17
{
18
    use ParseResponseBodyToArrayTrait;
19

20
    public const PROPERTY_RESPONSE_PROP = AbstractModelEndpoint::PROPERTY_RESPONSE_PROP;
21

22
    public const PROPERTY_MODEL_ENDPOINT = 'model';
23

24
    public const PROPERTY_MODEL_ID_KEY = AbstractModelEndpoint::PROPERTY_MODEL_KEY;
25

26
    public const EVENT_BEFORE_SYNC = 'before_sync';
27

28
    public const SETOPT_MERGE = 'merge';
29

30
    public const SETOPT_RESET = 'reset';
31

32
    protected string $_modelInterface = '';
33

34
    /**
35
     * The ID Field used by the Model
36
     */
37
    protected static string $_DEFAULT_MODEL_KEY = AbstractModelEndpoint::DEFAULT_MODEL_KEY;
38

39
    /**
40
     * The Collection of Models
41
     */
42
    protected array $models = [];
43

44
    /**
45
     * The Class Name of the ModelEndpoint
46
     */
47
    protected string $model;
48

49
    /**
50
     * Assigns a value to the specified offset
51
     * @param string $offset - The offset to assign the value to
52
     * @param mixed $value - The value to set
53
     * @abstracting ArrayAccess
54
     */
55
    public function offsetSet($offset, mixed $value): void
1✔
56
    {
57
        if (is_null($offset)) {
1✔
58
            $this->models[] = $value;
1✔
59
        } else {
60
            $this->models[$offset] = $value;
1✔
61
        }
62
    }
63

64
    /**
65
     * Whether or not an offset exists
66
     * @param string $offset - An offset to check for
67
     * @abstracting ArrayAccess
68
     */
69
    public function offsetExists($offset): bool
1✔
70
    {
71
        return isset($this->models[$offset]);
1✔
72
    }
73

74
    /**
75
     * Unsets an offset
76
     * @param string $offset - The offset to unset
77
     * @abstracting ArrayAccess
78
     */
79
    public function offsetUnset($offset): void
1✔
80
    {
81
        if ($this->offsetExists($offset)) {
1✔
82
            unset($this->models[$offset]);
1✔
83
        }
84
    }
85

86
    /**
87
     * Returns the value at specified offset
88
     * @param string $offset - The offset to retrieve
89
     * @return mixed
90
     * @abstracting ArrayAccess
91
     */
92
    #[\ReturnTypeWillChange]
1✔
93
    public function offsetGet($offset)
94
    {
95
        return $this->offsetExists($offset) ? $this->models[$offset] : null;
1✔
96
    }
97

98
    /**
99
     * @implements ArrayableInterface
100
     */
101
    public function toArray(): array
1✔
102
    {
103
        return $this->models;
1✔
104
    }
105

106
    /**
107
     * @return $this
108
     * @implements ResettableInterface
109
     */
110
    public function reset(): static
2✔
111
    {
112
        parent::reset();
2✔
113
        return $this->clear();
2✔
114
    }
115

116
    /**
117
     *
118
     * @return $this
119
     * @implements ClearableInterface
120
     */
121
    public function clear(): static
1✔
122
    {
123
        $this->models = [];
1✔
124
        return $this;
1✔
125
    }
126

127
    //Iterator
128
    /**
129
     * @return mixed|void
130
     * @implements \Iterator
131
     */
132

133
    #[\ReturnTypeWillChange]
1✔
134
    public function current()
135
    {
136
        return current($this->models);
1✔
137
    }
138

139
    /**
140
     * @return mixed|void
141
     * @implements \Iterator
142
     */
143
    #[\ReturnTypeWillChange]
1✔
144
    public function key()
145
    {
146
        return key($this->models);
1✔
147
    }
148

149
    /**
150
     * @implements \Iterator
151
     */
152
    public function next(): void
1✔
153
    {
154
        next($this->models);
1✔
155
    }
156

157
    /**
158
     * @implements \Iterator
159
     */
160
    public function rewind(): void
1✔
161
    {
162
        reset($this->models);
1✔
163
    }
164

165
    /**
166
     * @return mixed|void
167
     * @implements \Iterator
168
     */
169
    public function valid(): bool
1✔
170
    {
171
        return key($this->models) !== null;
1✔
172
    }
173

174
    //Collection Interface
175
    /**
176
     * @inheritdoc
177
     * @throws InvalidRequest
178
     */
179
    public function fetch(): static
1✔
180
    {
181
        $this->setProperty(self::PROPERTY_HTTP_METHOD, "GET");
1✔
182
        return $this->execute();
1✔
183
    }
184

185
    /**
186
     * @inheritdoc
187
     */
188
    public function get(string|int $key): ModelInterface|array|\ArrayAccess|null
1✔
189
    {
190
        $data = null;
1✔
191
        if ($this->offsetExists($key)) {
1✔
192
            $data = $this->models[$key];
1✔
193
            $Model = $this->buildModel($data);
1✔
194
            if ($Model instanceof ModelInterface) {
1✔
195
                $data = $Model;
1✔
196
            }
197
        }
198

199
        return $data;
1✔
200
    }
201

202
    /**
203
     * Get a model based on numerical index
204
     */
205
    public function at(int $index): ModelInterface|array|\ArrayAccess|null
1✔
206
    {
207
        $this->rewind();
1✔
208
        if ($index < 0) {
1✔
209
            $index += $this->length();
1✔
210
        }
211

212
        $c = 1;
1✔
213
        while ($c <= $index) {
1✔
214
            $this->next();
1✔
215
            $c++;
1✔
216
        }
217

218
        $return = $this->current();
1✔
219
        $Model = $this->buildModel($return);
1✔
220
        if ($Model instanceof ModelInterface) {
1✔
221
            $return = $Model;
1✔
222
        }
223

224
        return $return;
1✔
225
    }
226

227
    protected function getModelIdKey(): string
1✔
228
    {
229
        $model = $this->buildModel();
1✔
230
        if ($model instanceof ModelInterface) {
1✔
231
            return $model->getKeyProperty();
1✔
232
        }
233

234
        return $this->getProperty(self::PROPERTY_MODEL_ID_KEY) ?? static::$_DEFAULT_MODEL_KEY;
1✔
235
    }
236

237
    /**
238
     * Append models to the collection
239
     */
240
    public function set(array $models, array $options = []): static
2✔
241
    {
242
        $modelIdKey = $this->getModelIdKey();
2✔
243
        $reset = $options[self::SETOPT_RESET] ?? false;
2✔
244
        $merge = $options[self::SETOPT_MERGE] ?? false;
2✔
245
        if ($reset) {
2✔
246
            $this->models = [];
1✔
247
        }
248

249
        foreach ($models as $m) {
2✔
250
            if ($m instanceof DataInterface) {
2✔
251
                $m = $m->toArray();
1✔
252
            } elseif ($m instanceof \stdClass) {
2✔
253
                $m = (array) $m;
1✔
254
            }
255

256
            if (!empty($m[$modelIdKey])) {
2✔
257
                $id = $m[$modelIdKey];
2✔
258
                $this->models[$id] = $merge && isset($this->models[$id]) ? array_merge($this->models[$id], $m) : $m;
2✔
259
            } else {
260
                $this->models[] = $m;
1✔
261
            }
262
        }
263

264
        return $this;
2✔
265
    }
266

267
    /**
268
     * Return the current collection count
269
     */
270
    public function length(): int
1✔
271
    {
272
        return count($this->models);
1✔
273
    }
274

275
    /**
276
     * @inheritdoc
277
     * @throws UnknownEndpoint
278
     */
279
    public function setModelEndpoint(string|ModelInterface $model): static
2✔
280
    {
281
        try {
282
            $interface = $model;
2✔
283
            if (is_string($model)) {
2✔
284
                if (!class_exists($model) && (!empty($this->_client) && $this->_client->hasEndpoint($model))) {
2✔
NEW
285
                    $model = $this->_client->getEndpoint($model);
×
UNCOV
286
                    $interface = $model::class;
×
287
                }
288
            } else {
289
                $interface = $model::class;
1✔
290
            }
291

292
            $implements = class_implements($model);
2✔
293
            if (is_array($implements) && isset($implements[ModelInterface::class])) {
1✔
294
                $this->setProperty(self::PROPERTY_MODEL_ENDPOINT, $interface);
1✔
295
                return $this;
1✔
296
            }
297
        } catch (\Exception) {
1✔
298
            //If class_implements cannot load class
299
        }
300

301
        throw new UnknownEndpoint($model);
1✔
302
    }
303

304
    public function getEndPointUrl(bool $full = false): string
1✔
305
    {
306
        $epURL = parent::getEndPointUrl();
1✔
307
        if ($epURL === '') {
1✔
308
            $model = $this->buildModel();
1✔
309
            if ($model instanceof ModelInterface) {
1✔
310
                $epURL = $model->getEndPointUrl();
1✔
311
            }
312
        }
313

314
        if ($full) {
1✔
315
            $epURL = rtrim($this->getBaseUrl(), "/") . ('/' . $epURL);
1✔
316
        }
317

318
        return $epURL;
1✔
319
    }
320

321
    protected function setResponse(Response $response): static
1✔
322
    {
323
        parent::setResponse($response);
1✔
324
        $this->parseResponse($response);
1✔
325
        return $this;
1✔
326
    }
327

328
    public function getCollectionResponseProp(): string
2✔
329
    {
330
        return $this->getProperty(self::PROPERTY_RESPONSE_PROP) ?? '';
2✔
331
    }
332

333
    /**
334
     * @inheritdoc
335
     */
336
    protected function parseResponse(Response $response): void
1✔
337
    {
338
        if ($response->getStatusCode() == 200) {
1✔
339
            $body = $this->getResponseContent($response);
1✔
340
            $this->syncFromApi($this->parseResponseBodyToArray($body, $this->getCollectionResponseProp()));
1✔
341
        }
342
    }
343

344
    /**
345
     * Configures the collection based on the Response Body
346
     */
347
    protected function syncFromApi(array $data): void
1✔
348
    {
349
        $this->triggerEvent(self::EVENT_BEFORE_SYNC, $data);
1✔
350
        $this->set($data);
1✔
351
    }
352

353
    /**
354
     * Build the ModelEndpoint
355
     */
356
    protected function buildModel(array $data = []): ModelInterface|null
1✔
357
    {
358
        $Model = null;
1✔
359
        $endpoint = $this->getProperty(self::PROPERTY_MODEL_ENDPOINT) ?? $this->_modelInterface;
1✔
360
        if (!empty($endpoint)) {
1✔
361
            if (class_exists($endpoint)) {
1✔
362
                $Model = new $endpoint();
1✔
NEW
363
            } elseif (!empty($this->_client)) {
×
NEW
364
                if ($this->_client->hasEndpoint($endpoint)) {
×
NEW
365
                    $Model = $this->_client->getEndpoint($endpoint);
×
366
                }
367
            }
368

369
            if (!empty($this->_client)) {
1✔
370
                $Model->setClient($this->getClient());
1✔
371
            } else {
372
                $Model->setBaseUrl($this->getBaseUrl());
1✔
373
            }
374

375
            if (!empty($data)) {
1✔
376
                $Model->set($data);
1✔
377
            }
378
        }
379

380
        return $Model;
1✔
381
    }
382
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc