AbstractRequest   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Importance

Changes 10
Bugs 0 Features 0
Metric Value
wmc 28
eloc 65
c 10
b 0
f 0
dl 0
loc 275
rs 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A invoke() 0 8 2
A __get() 0 5 1
A __construct() 0 14 2
A __toString() 0 12 2
A __invoke() 0 13 4
A serialize() 0 4 1
A offsetExists() 0 8 2
A __unserialize() 0 3 1
A offsetSet() 0 5 1
A unserialize() 0 4 1
A hash() 0 4 1
A jsonSerialize() 0 4 1
A getIterator() 0 7 2
A offsetUnset() 0 7 1
A request() 0 10 2
A __serialize() 0 3 1
A offsetGet() 0 11 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use ArrayAccess;
8
use ArrayIterator;
9
use BEAR\Resource\Exception\MethodException;
10
use BEAR\Resource\Exception\OutOfBoundsException;
11
use IteratorAggregate;
12
use JsonSerializable;
13
use LogicException;
14
use Override;
15
use ReturnTypeWillChange;
0 ignored issues
show
Bug introduced by
The type ReturnTypeWillChange was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use Serializable;
17
use Stringable;
18
use Throwable;
19
20
use function array_key_exists;
21
use function array_merge;
22
use function assert;
23
use function in_array;
24
use function is_array;
25
use function md5;
26
use function serialize;
27
use function strtolower;
28
use function trigger_error;
29
30
use const E_USER_ERROR;
31
use const PHP_EOL;
32
33
/**
34
 * @property int     $code
35
 * @property Headers $headers
36
 * @property mixed   $body
37
 * @property string  $view
38
 * @phpstan-implements IteratorAggregate<string, mixed>
39
 * @phpstan-implements ArrayAccess<string, mixed>
40
 * @psalm-suppress PropertyNotSetInConstructor
41
 * @psalm-import-type Query from Types
42
 * @psalm-import-type Headers from Types
43
 * @psalm-import-type Body from Types
44
 */
45
abstract class AbstractRequest implements RequestInterface, ArrayAccess, IteratorAggregate, Serializable, JsonSerializable, Stringable
46
{
47
    /**
48
     * URI
49
     *
50
     * @var string
51
     */
52
    public $uri;
53
54
    /**
55
     * Method
56
     *
57
     * @var string
58
     */
59
    public $method = '';
60
61
    /**
62
     * Options
63
     *
64
     * @var Body
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\Body was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
65
     */
66
    public $options = [];
67
68
    /**
69
     * Request option (eager or lazy)
70
     *
71
     * @var 'eager'|'lazy'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'eager'|'lazy' at position 0 could not be parsed: Unknown type name ''eager'' at position 0 in 'eager'|'lazy'.
Loading history...
72
     */
73
    public $in = 'lazy';
74
75
    /**
76
     * Request Result
77
     *
78
     * @var ?ResourceObject
79
     */
80
    protected $result;
81
82
    /**
83
     * @param Query          $query
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\Query was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
84
     * @param list<LinkType> $links
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
85
     *
86
     * @throws MethodException
87
     */
88
    public function __construct(
89
        protected InvokerInterface $invoker,
90
        public ResourceObject $resourceObject,
91
        string $method = Request::GET,
92
        public array $query = [],
93
        // phpcs:ignore SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint
94
        public array $links = [],
95
        private readonly LinkerInterface|null $linker = null,
96
    ) {
97
        if (! in_array(strtolower($method), ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'], true)) {
98
            throw new MethodException($method, 400);
99
        }
100
101
        $this->method = $method;
102
    }
103
104
    /** @psalm-suppress UnevaluatedCode */
105
    #[Override]
106
    public function __toString(): string
107
    {
108
        try {
109
            $this->invoke();
110
111
            return (string) $this->result;
112
        } catch (Throwable $e) {
113
            trigger_error($e->getMessage() . PHP_EOL . $e->getTraceAsString(), E_USER_ERROR);
114
115
            /** @noinspection PhpUnreachableStatementInspection */
116
            return ''; // @phpstan-ignore-line
117
        }
118
    }
119
120
    /**
121
     * {@inheritDoc}
122
     *
123
     * @param Query $query Query parameters that may contain user input
124
     *
125
     * @psalm-taint-source input $query
126
     */
127
    #[Override]
128
    public function __invoke(array|null $query = null): ResourceObject
129
    {
130
        if (is_array($query)) {
131
            $this->query = array_merge($this->query, $query);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->query, $query) of type array is incompatible with the declared type BEAR\Resource\Query of property $query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
132
        }
133
134
        $this->resourceObject->uri->query = $this->query;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->query of type array is incompatible with the declared type BEAR\Resource\Query of property $query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
135
        if ($this->links && $this->linker instanceof LinkerInterface) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->links of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
136
            return $this->linker->invoke($this);
137
        }
138
139
        return clone $this->invoker->invoke($this);
140
    }
141
142
    /**
143
     * {@inheritDoc}
144
     *
145
     * @return mixed
146
     *
147
     * @noinspection MagicMethodsValidityInspection
148
     */
149
    public function __get(string $name)
150
    {
151
        $this->result = $this->invoke();
152
153
        return $this->result->{$name};
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     *
159
     * @throws OutOfBoundsException
160
     */
161
    #[Override]
162
    #[ReturnTypeWillChange]
163
    public function offsetSet($offset, $value)
164
    {
165
        throw new OutOfBoundsException(__METHOD__ . ' is unavailable.', 400);
166
    }
167
168
    /**
169
     * {@inheritDoc}
170
     *
171
     * @param string $offset
172
     *
173
     * @return never
174
     *
175
     * @throws OutOfBoundsException
176
     */
177
    #[Override]
178
    #[ReturnTypeWillChange]
179
    public function offsetUnset($offset)
180
    {
181
        unset($offset);
182
183
        throw new OutOfBoundsException(__METHOD__ . ' is unavailable.', 400);
184
    }
185
186
    /**
187
     * {@inheritDoc}
188
     */
189
    #[Override]
190
    public function request()
191
    {
192
        if ($this->in === 'eager') {
193
            $this->result = $this->invoke();
194
195
            return $this->result;
196
        }
197
198
        return $this;
199
    }
200
201
    /**
202
     * {@inheritDoc}
203
     *
204
     * @param string $offset
205
     *
206
     * @return mixed
207
     *
208
     * @throws OutOfBoundsException
209
     */
210
    #[Override]
211
    #[ReturnTypeWillChange]
212
    public function offsetGet($offset)
213
    {
214
        $this->invoke();
215
        assert($this->result instanceof ResourceObject);
216
        if (! is_array($this->result->body) || ! array_key_exists($offset, $this->result->body)) {
217
            throw new OutOfBoundsException("[{$offset}] for object[" . $this->result::class . ']', 400);
218
        }
219
220
        return $this->result->body[$offset];
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226
    #[Override]
227
    #[ReturnTypeWillChange]
228
    public function offsetExists($offset): bool
229
    {
230
        $this->invoke();
231
        assert($this->result instanceof ResourceObject);
232
233
        return is_array($this->result->body) && array_key_exists($offset, $this->result->body);
234
    }
235
236
    /**
237
     * Invoke resource request then return resource body iterator
238
     *
239
     * @psalm-return ArrayIterator
240
     * @phpstan-return ArrayIterator<string, mixed>
241
     */
242
    #[Override]
243
    public function getIterator(): ArrayIterator
244
    {
245
        $this->invoke();
246
        assert($this->result instanceof ResourceObject);
247
248
        return is_array($this->result->body) ? new ArrayIterator($this->result->body) : new ArrayIterator([]);
249
    }
250
251
    /**
252
     * {@inheritDoc}
253
     */
254
    #[Override]
255
    public function hash(): string
256
    {
257
        return md5($this->resourceObject::class . $this->method . serialize($this->query) . serialize($this->links));
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     *
263
     * @return never
264
     *
265
     * @noinspection MagicMethodsValidityInspection
266
     */
267
    public function __serialize()
268
    {
269
        throw new LogicException(__METHOD__ . ' not supported');
270
    }
271
272
    /**
273
     * @param Body $data
274
     *
275
     * @codeCoverageIgnore
276
     */
277
    public function __unserialize(array $data): void
278
    {
279
        unset($data);
280
    }
281
282
    private function invoke(): ResourceObject
283
    {
284
        if ($this->result === null) {
285
            /* @noinspection ImplicitMagicMethodCallInspection */
286
            $this->result = ($this)();
287
        }
288
289
        return $this->result;
290
    }
291
292
    #[Override]
293
    public function jsonSerialize(): ResourceObject
294
    {
295
        return $this->invoke();
296
    }
297
298
    /**
299
     * @return never
300
     *
301
     * @codeCoverageIgnore
302
     * @psalm-suppress MethodSignatureMustProvideReturnType - method not supported
303
     */
304
    #[Override]
305
    public function serialize()
306
    {
307
        $this->__serialize();
308
    }
309
310
    /**
311
     * @param string $data
312
     *
313
     * @codeCoverageIgnore
314
     * @psalm-suppress MethodSignatureMustProvideReturnType - method not supported
315
     */
316
    #[Override]
317
    public function unserialize($data)
318
    {
319
        unset($data);
320
    }
321
}
322