Passed
Push — refactor ( 63f2db...834b7c )
by Florian
13:12
created

Router::getRequestInformation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2.5

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 8
c 1
b 1
f 0
dl 0
loc 12
ccs 1
cts 2
cp 0.5
rs 10
cc 2
nc 2
nop 1
crap 2.5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Lead\Router;
6
7
use ArrayAccess;
8
use Closure;
9
use Countable;
10
use Iterator;
11
use Lead\Router\Exception\ParserException;
12
use Lead\Router\Exception\RouteNotFoundException;
13
use Lead\Router\Exception\RouterException;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ServerRequestInterface;
16
use RuntimeException;
17
18
/**
19
 * The Router class.
20
 */
21
class Router implements ArrayAccess, Iterator, Countable, RouterInterface
22
{
23
    /**
24
     * @var bool
25
     */
26
    protected $skipNext;
27
28
    /**
29
     * @var array
30
     */
31
    protected $data = [];
32
33
    /**
34
     * @var array
35
     */
36
    protected $pattern = [];
37
38
    /**
39
     * Class dependencies.
40
     *
41
     * @var array
42
     */
43
    protected $classes = [];
44
45
    /**
46
     * Hosts.
47
     *
48
     * @var array
49
     */
50
    protected $hosts = [];
51
52
    /**
53
     * Routes.
54
     *
55
     * @var array
56
     */
57
    protected $routes = [];
58
59
    /**
60
     * Scopes stack.
61
     *
62
     * @var array
63
     */
64
    protected $scopes = [];
65
66
    /**
67
     * Base path.
68
     *
69
     * @param string
70
     */
71
    protected $basePath = '';
72
73
    /**
74
     * Dispatching strategies.
75
     *
76
     * @param array
77
     */
78
    protected $strategies = [];
79
80
    /**
81
     * Defaults parameters to use when generating URLs in a dispatching context.
82
     *
83
     * @var array
84
     */
85
    protected $defaults = [];
86
87
    /**
88
     * Default handler
89
     *
90
     * @var callable|null
91
     */
92
    protected $defaultHandler = null;
93
94
    /**
95
     * Constructor
96
     *
97
     * @param array $config
98
     */
99
    public function __construct($config = [])
100
    {
101
        $defaults = [
102
            'basePath'       => '',
103
            'scope'          => [],
104
            'strategies'     => [],
105
            'defaultHandler' => null,
106
            'classes'        => [
107
                'parser'     => 'Lead\Router\Parser',
108
                'host'       => 'Lead\Router\Host',
109
                'route'      => 'Lead\Router\Route',
110
                'scope'      => 'Lead\Router\Scope'
111
            ]
112 55
        ];
113 55
        $config += $defaults;
114 55
        $this->classes = $config['classes'];
115 55
        $this->strategies = $config['strategies'];
116 55
        $this->setDefaultHandler($config['defaultHandler']);
117 55
        $this->setBasePath($config['basePath']);
118 55
        $scope = $this->classes['scope'];
119 55
        $this->scopes[] = new $scope(['router' => $this]);
120
    }
121
122
    /**
123
     * Sets the default handler for routes
124
     *
125
     * @param mixed $handler
126
     * @return $this
127
     */
128
    public function setDefaultHandler($handler): RouterInterface
129
    {
130 55
        $this->defaultHandler = $handler;
131 55
        return $this;
132
    }
133
134
    /**
135
     * Returns the current router scope.
136
     *
137
     * @return \Lead\Router\ScopeInterface The current scope instance.
138
     */
139
    public function scope(): ScopeInterface
140
    {
141 16
        return end($this->scopes);
142
    }
143
144
    /**
145
     * Pushes a new router scope context.
146
     *
147
     * @param  object $scope A scope instance.
148
     * @return self
149
     */
150
    public function pushScope($scope): RouterInterface
151
    {
152 16
        $this->scopes[] = $scope;
153 16
        return $this;
154
    }
155
156
    /**
157
     * Pops the current router scope context.
158
     *
159
     * @return \Lead\Router\ScopeInterface The popped scope instance.
160
     */
161
    public function popScope(): ScopeInterface
162
    {
163 16
        return array_pop($this->scopes);
164
    }
165
166
    /**
167
     * Gets the base path
168
     *
169
     * @param  string $basePath The base path to set or none to get the setted one.
170
     * @return string
171
     */
172
    public function getBasePath(): string
173
    {
174 8
        return $this->basePath;
175
    }
176
177
    /**
178
     * Sets the base path
179
     *
180
     * @param  string $basePath Base Path
181
     * @return $this
182
     */
183
    public function setBasePath(string $basePath): self
184
    {
185 55
        $basePath = trim($basePath, '/');
186 55
        $this->basePath = $basePath ? '/' . $basePath : '';
187 55
        return $this;
188
    }
189
190
    /**
191
     * Gets/sets the base path of the router.
192
     *
193
     * @deprecated Use setBasePath() and getBasePath() instead
194
     * @param      string|null $basePath The base path to set or none to get the setted one.
195
     * @return     string|self
196
     */
197
    public function basePath(?string $basePath = null)
198
    {
199
        if ($basePath === null) {
200
            return $this->basePath;
201
        }
202
203 1
        return $this->setBasePath($basePath);
204
    }
205
206
    /**
207
     * Adds a route to the router
208
     *
209
     * @param \Lead\Router\RouteInterface $route Route object
210
     * @return \Lead\Router\RouterInterface
211
     */
212
    public function addRoute(RouteInterface $route): RouterInterface
213
    {
214
        $options['pattern'] = $pattern = $route->pattern();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.
Loading history...
Unused Code introduced by
The assignment to $pattern is dead and can be removed.
Loading history...
215
        $options['handler'] = $route->handler();
216
        $options['scope'] = $route->scope();
217
        $scheme = $options['scheme'];
218
        $host = $options['host'];
219
        if (isset($this->hosts[$scheme][$host])) {
220
            $options['host'] = $this->hosts[$scheme][$host];
221
        }
222
223
        $patternKey = md5($options['pattern'] . '-' . $options['name']);
224
        if (isset($this->pattern[$scheme][$host][$patternKey])) {
225
            $route = $this->pattern[$scheme][$host][$patternKey];
226
        } else {
227
            $this->hosts[$scheme][$host] = $route->host();
228
        }
229
230
        if (!isset($this->pattern[$scheme][$host][$patternKey])) {
231
            $this->pattern[$scheme][$host][$patternKey] = $route;
232
        }
233
234
        $methods = $route->methods();
235
        foreach ($methods as $method) {
236
            $this->routes[$scheme][$host][strtoupper($method)][] = $route;
237
        }
238
239
        $this->data[$route->name()] = $route;
240
        return $this;
241
    }
242
243
    /**
244
     * Adds a route.
245
     *
246
     * @param  string|array  $pattern The route's pattern.
247
     * @param  Closure|array $options An array of options or the callback handler.
248
     * @param  Closure|null  $handler The callback handler.
249
     * @return self
250
     */
251
    public function bind($pattern, $options = [], $handler = null): RouteInterface
252
    {
253
        if (!is_array($options)) {
254 18
            $handler = $options;
255 18
            $options = [];
256
        }
257
258
        if (empty($handler) && !empty($this->_defaultHandler)) {
0 ignored issues
show
Bug introduced by
The property _defaultHandler does not exist on Lead\Router\Router. Did you mean defaultHandler?
Loading history...
259
            $handler = $this->_defaultHandler;
260
        }
261
262
        if ($handler !== null) {
263
            if (!$handler instanceof Closure && !method_exists($handler, '__invoke')) {
264 1
                throw new RouterException("The handler needs to be an instance of `Closure` or implements the `__invoke()` magic method.");
265
            }
266
        }
267
268
        if (isset($options['method'])) {
269 1
            throw new RouterException("Use the `'methods'` option to limit HTTP verbs on a route binding definition.");
270
        }
271
272 45
        $scope = end($this->scopes);
273 45
        $options = $scope->scopify($options);
274 45
        $options['pattern'] = $pattern;
275 45
        $options['handler'] = $handler;
276 45
        $options['scope'] = $scope;
277 45
        $scheme = $options['scheme'];
278 45
        $host = $options['host'];
279
        if (isset($this->hosts[$scheme][$host])) {
280
            $options['host'] = $this->hosts[$scheme][$host];
281
        }
282
283 45
        $patternKey = md5($options['pattern'] . '-' . $options['name']);
284
        if (isset($this->pattern[$scheme][$host][$patternKey])) {
285 2
            $instance = $this->pattern[$scheme][$host][$patternKey];
286
        } else {
287 45
            $route = $this->classes['route'];
288 45
            $instance = new $route($options);
289 45
            $this->hosts[$scheme][$host] = $instance->host();
290
        }
291
292
        if (!isset($this->pattern[$scheme][$host][$patternKey])) {
293 45
            $this->pattern[$scheme][$host][$patternKey] = $instance;
294
        }
295
296 45
        $methods = $options['methods'] ? (array)$options['methods'] : [];
297 45
        $instance->allow($methods);
298
        foreach ($methods as $method) {
299 45
            $this->routes[$scheme][$host][strtoupper($method)][] = $instance;
300
        }
301
302
        if (isset($options['name'])) {
303 45
            $this->data[$options['name']] = $instance;
304
        }
305
306 45
        return $instance;
307
    }
308
309
    /**
310
     * Groups some routes inside a new scope.
311
     *
312
     * @param  string|array $prefix  The group's prefix pattern or the options array.
313
     * @param  Closure|array $options An array of options or the callback handler.
314
     * @param  Closure|null  $handler The callback handler.
315
     * @return \Lead\Router\ScopeInterface The newly created scope instance.
316
     */
317
    public function group($prefix, $options, $handler = null)
318
    {
319
        if (!is_array($options)) {
320 11
            $handler = $options;
321
            if (is_string($prefix)) {
322 10
                $options = [];
323
            } else {
324 1
                $options = $prefix;
325 1
                $prefix = '';
326
            }
327
        }
328
        if (!$handler instanceof Closure && !method_exists($handler, '__invoke')) {
329 1
            throw new RouterException("The handler needs to be an instance of `Closure` or implements the `__invoke()` magic method.");
330
        }
331
332 16
        $options['prefix'] = isset($options['prefix']) ? $options['prefix'] : $prefix;
333 16
        $scope = $this->scope();
334 16
        $this->pushScope($scope->seed($options));
335 16
        $handler($this);
336
337 16
        return $this->popScope();
338
    }
339
340
    /**
341
     * Gets information required for routing from a server request
342
     *
343
     * @param \Psr\Http\Message\ServerRequestInterface $request Server Request
344
     * @return array
345
     */
346
    protected function getRequestInformation(ServerRequestInterface $request): array
347
    {
348 1
        $uri = $request->getUri();
349
        if (method_exists($request, 'basePath')) {
350
            $this->setBasePath($request->basePath());
351
        }
352
353
        return [
354
            'scheme' => $uri->getScheme(),
355
            'host' => $uri->getHost(),
356
            'method' => $request->getMethod(),
357
            'path' => $uri->getPath()
358 1
        ];
359
    }
360
361
    /**
362
     * Routes a Request.
363
     *
364
     * @param mixed $request The request to route.
365
     * @return \Lead\Router\RouteInterface A route matching the request or a "route not found" route.
366
     */
367
    public function route($request): RouteInterface
368
    {
369
        $defaults = [
370
            'path' => '',
371
            'method' => 'GET',
372
            'host' => '*',
373
            'scheme' => '*'
374 40
        ];
375 40
        $this->defaults = [];
376
        if ($request instanceof ServerRequestInterface) {
377 1
            $r = $this->getRequestInformation($request);
378
        } elseif (!is_array($request)) {
379 38
            $r = array_combine(array_keys($defaults), func_get_args() + array_values($defaults));
380
        } else {
381 1
            $r = $request + $defaults;
382
        }
383
384 40
        $r = $this->_normalizeRequest($r);
385 40
        $route = $this->_route($r);
386
        if ($route instanceof RouteInterface) {
387 35
            $route->request = is_object($request) ? $request : $r;
0 ignored issues
show
Bug introduced by
Accessing request on the interface Lead\Router\RouteInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
388
            foreach ($route->persistentParams() as $key) {
0 ignored issues
show
Bug introduced by
The method persistentParams() does not exist on Lead\Router\RouteInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Lead\Router\RouteInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

388
            foreach ($route->/** @scrutinizer ignore-call */ persistentParams() as $key) {
Loading history...
389
                if (isset($route->params[$key])) {
0 ignored issues
show
Bug introduced by
Accessing params on the interface Lead\Router\RouteInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
390 2
                    $this->defaults[$key] = $route->params[$key];
391
                }
392
            }
393
394 35
            return $route;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $route could return the type null which is incompatible with the type-hinted return Lead\Router\RouteInterface. Consider adding an additional type-check to rule them out.
Loading history...
395
        }
396
397 13
        $message = "No route found for `{$r['scheme']}:{$r['host']}:{$r['method']}:/{$r['path']}`.";
398 13
        throw new RouteNotFoundException($message);
399
    }
400
401
    /**
402
     * Normalizes a request.
403
     *
404
     * @param  array $request The request to normalize.
405
     * @return array          The normalized request.
406
     */
407
    protected function _normalizeRequest(array $request): array
408
    {
409
        if (preg_match('~^(?:[a-z]+:)?//~i', $request['path'])) {
410 5
            $parsed = array_intersect_key(parse_url($request['path']), $request);
411 5
            $request = $parsed + $request;
412
        }
413
414 40
        $request['path'] = (ltrim((string)strtok($request['path'], '?'), '/'));
415 40
        $request['method'] = strtoupper($request['method']);
416 40
        return $request;
417
    }
418
419
    /**
420
     * Routes a request.
421
     *
422
     * @param array $request The request to route.
423
     * @return null|\Lead\Router\RouteInterface
424
     */
425
    protected function _route($request): ?RouteInterface
426
    {
427 40
        $path = $request['path'];
0 ignored issues
show
Unused Code introduced by
The assignment to $path is dead and can be removed.
Loading history...
428 40
        $httpMethod = $request['method'];
429 40
        $host = $request['host'];
0 ignored issues
show
Unused Code introduced by
The assignment to $host is dead and can be removed.
Loading history...
430 40
        $scheme = $request['scheme'];
431 40
        $allowedSchemes = array_unique([$scheme => $scheme, '*' => '*']);
432 40
        $allowedMethods = array_unique([$httpMethod => $httpMethod, '*' => '*']);
433
        if ($httpMethod === 'HEAD') {
434 3
            $allowedMethods += ['GET' => 'GET'];
435
        }
436
437
        foreach ($this->routes as $scheme => $hostBasedRoutes) {
438
            if (!isset($allowedSchemes[$scheme])) {
439 1
                continue;
440
            }
441
442
            foreach ($hostBasedRoutes as $routeHost => $methodBasedRoutes) {
443
                foreach ($methodBasedRoutes as $method => $routes) {
444
                    if (!isset($allowedMethods[$method]) && $httpMethod !== '*') {
445 4
                        continue;
446
                    }
447
                    foreach ($routes as $route) {
448
                /* @var $route \Lead\Router\RouteInterface */
449
                        if (!$route->match($request, $variables, $hostVariables)) {
450
                            if ($hostVariables === null) {
451 3
                                continue 3;
452
                            }
453 10
                            continue;
454
                        }
455
456 39
                        return $route;
457
                    }
458
                }
459
            }
460
        }
461
462 13
        return null;
463
    }
464
465
    /**
466
     * Middleware generator.
467
     *
468
     * @return callable
469
     */
470
    public function middleware()
471
    {
472
        foreach ($this->scopes[0]->middleware() as $middleware) {
473 1
            yield $middleware;
0 ignored issues
show
Bug Best Practice introduced by
The expression yield $middleware returns the type Generator which is incompatible with the documented return type callable.
Loading history...
474
        }
475
    }
476
477
    /**
478
     * Adds a middleware to the list of middleware.
479
     *
480
     * @param  object|Closure A callable middleware.
0 ignored issues
show
Bug introduced by
The type Lead\Router\A was not found. Did you mean A? If so, make sure to prefix the type with \.
Loading history...
481
     * @return $this
482
     */
483
    public function apply($middleware)
484
    {
485
        foreach (func_get_args() as $mw) {
486 3
            $this->scopes[0]->apply($mw);
487
        }
488
489 3
        return $this;
490
    }
491
492
    /**
493
     * Sets a dispatcher strategy
494
     *
495
     * @param  string   $name    Name
496
     * @param  callable $handler Handler
497
     * @return $this
498
     */
499
    public function setStrategy(string $name, callable $handler)
500
    {
501 2
        $this->strategies[$name] = $handler;
502 2
        return $this;
503
    }
504
505
    /**
506
     * Get a strategy
507
     *
508
     * @return callable
509
     */
510
    public function getStrategy(string $name): callable
511
    {
512
        if (isset($this->strategies[$name])) {
513 2
            return $this->strategies[$name];
514
        }
515
516 16
        throw new RuntimeException(sprintf('Strategy `%s` not found.', $name));
517
    }
518
519
    /**
520
     * Unsets a strategy
521
     *
522
     * @param  string $name
523
     * @return $this
524
     */
525
    public function unsetStrategy(string $name)
526
    {
527
        if (isset($this->strategies[$name])) {
528 1
            unset($this->strategies[$name]);
529 1
            return $this;
530
        }
531
532
        throw new RuntimeException(sprintf('Strategy `%s` not found.', $name));
533
    }
534
535
    /**
536
     * Gets/sets router's strategies.
537
     *
538
     * @deprecated Use setStrategy(), unsetStrategy() and getStrategy()
539
     * @param      string $name    A routing strategy name.
540
     * @param      mixed  $handler The strategy handler or none to get the setted one.
541
     * @return     mixed           The strategy handler (or `null` if not found) on get or `$this` on set.
542
     */
543
    public function strategy($name, $handler = null)
544
    {
545
        if (func_num_args() === 1) {
546
            try {
547 17
                return $this->getStrategy($name);
548
            } catch (RuntimeException $e) {
549 16
                return null;
550
            }
551
        }
552
553
        if ($handler === false) {
554
            try {
555 1
                return $this->unsetStrategy($name);
556
            } catch (RuntimeException $e) {
557
                return null;
558
            }
559
        }
560
561
        if (!$handler instanceof Closure && !method_exists($handler, '__invoke')) {
562 1
            throw new RouterException("The handler needs to be an instance of `Closure` or implements the `__invoke()` magic method.");
563
        }
564
565 2
        return $this->setStrategy($name, $handler);
0 ignored issues
show
Bug introduced by
It seems like $handler can also be of type object; however, parameter $handler of Lead\Router\Router::setStrategy() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

565
        return $this->setStrategy($name, /** @scrutinizer ignore-type */ $handler);
Loading history...
566
    }
567
568
    /**
569
     * Adds a route based on a custom HTTP verb.
570
     *
571
     * @param string $name   The HTTP verb to define a route on.
572
     * @param array  $params The route's parameters.
573
     * @return mixed
574
     */
575
    public function __call($name, $params)
576
    {
577
        if ($strategy = $this->strategy($name)) {
0 ignored issues
show
Deprecated Code introduced by
The function Lead\Router\Router::strategy() has been deprecated: Use setStrategy(), unsetStrategy() and getStrategy() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

577
        if ($strategy = /** @scrutinizer ignore-deprecated */ $this->strategy($name)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
578 1
            array_unshift($params, $this);
579 1
            return call_user_func_array($strategy, $params);
580
        }
581
582
        if (is_callable($params[1])) {
583 11
            $params[2] = $params[1];
584 11
            $params[1] = [];
585
        }
586
587 15
        $params[1]['methods'] = [$name];
588 15
        return call_user_func_array([$this, 'bind'], $params);
589
    }
590
591
    /**
592
     * Returns a route's link.
593
     *
594
     * @param string $name    A route name.
595
     * @param array  $params  The route parameters.
596
     * @param array  $options Options for generating the proper prefix. Accepted values are:
597
     *                        - `'absolute'` _boolean_: `true` or `false`. - `'scheme'`
598
     *                        _string_ : The scheme. - `'host'`     _string_ : The host
599
     *                        name. - `'basePath'` _string_ : The base path. - `'query'`
600
     *                        _string_ : The query string. - `'fragment'` _string_ : The
601
     *                        fragment string.
602
     *
603
     * @return string          The link.
604
     */
605
    public function link(string $name, array $params = [], array $options = []): string
606
    {
607
        $defaults = [
608
            'basePath' => $this->getBasePath()
609 5
        ];
610 5
        $options += $defaults;
611 5
        $params += $this->defaults;
612
        if (!isset($this[$name])) {
613 1
            throw new RouterException("No binded route defined for `'{$name}'`, bind it first with `bind()`.");
614
        }
615 4
        $route = $this[$name];
616 4
        return $route->link($params, $options);
617
    }
618
619
    /**
620
     * Clears the router.
621
     */
622
    public function clear()
623
    {
624 1
        $this->basePath = '';
625 1
        $this->strategies = [];
626 1
        $this->defaults = [];
627 1
        $this->routes = [];
628 1
        $scope = $this->classes['scope'];
629 1
        $this->scopes = [new $scope(['router' => $this])];
630
    }
631
632
    /**
633
     * Return the current element
634
     *
635
     * @link   https://php.net/manual/en/iterator.current.php
636
     * @return mixed Can return any type.
637
     * @since  5.0.0
638
     */
639
    public function current()
640
    {
641
        return current($this->data);
642
    }
643
644
    /**
645
     * Move forward to next element
646
     *
647
     * @link   https://php.net/manual/en/iterator.next.php
648
     * @return void Any returned value is ignored.
649
     * @since  5.0.0
650
     */
651
    public function next()
652
    {
653
        $value = $this->skipNext ? current($this->data) : next($this->data);
654
        $this->skipNext = false;
655
        key($this->data) !== null ? $value : null;
656
    }
657
658
    /**
659
     * Return the key of the current element
660
     *
661
     * @link   https://php.net/manual/en/iterator.key.php
662
     * @return mixed scalar on success, or null on failure.
663
     * @since  5.0.0
664
     */
665
    public function key()
666
    {
667
        return array_keys($this->data);
668
    }
669
670
    /**
671
     * Checks if current position is valid
672
     *
673
     * @link   https://php.net/manual/en/iterator.valid.php
674
     * @return bool The return value will be casted to boolean and then evaluated.
675
     * Returns true on success or false on failure.
676
     * @since  5.0.0
677
     */
678
    public function valid()
679
    {
680
        return key($this->data) !== null;
681
    }
682
683
    /**
684
     * Rewind the Iterator to the first element
685
     *
686
     * @link   https://php.net/manual/en/iterator.rewind.php
687
     * @return void Any returned value is ignored.
688
     * @since  5.0.0
689
     */
690
    public function rewind()
691
    {
692
        $this->skipNext = false;
693
        reset($this->data);
694
    }
695
696
    /**
697
     * Whether a offset exists
698
     *
699
     * @link   https://php.net/manual/en/arrayaccess.offsetexists.php
700
     * @param  mixed $offset <p>
701
     *                       An offset to check for.
702
     *                       </p>
703
     * @return bool true on success or false on failure.
704
     * </p>
705
     * <p>
706
     * The return value will be casted to boolean if non-boolean was returned.
707
     * @since  5.0.0
708
     */
709
    public function offsetExists($offset)
710
    {
711 8
        return array_key_exists($offset, $this->data);
712
    }
713
714
    /**
715
     * Offset to retrieve
716
     *
717
     * @link   https://php.net/manual/en/arrayaccess.offsetget.php
718
     * @param  mixed $offset <p>
719
     *                       The offset to retrieve.
720
     *                       </p>
721
     * @return mixed Can return all value types.
722
     * @since  5.0.0
723
     */
724
    public function offsetGet($offset)
725
    {
726 7
        return $this->data[$offset];
727
    }
728
729
    /**
730
     * Offset to set
731
     *
732
     * @link  https://php.net/manual/en/arrayaccess.offsetset.php
733
     * @param mixed $offset <p>
734
     *                      The offset to assign the value to.
735
     *                      </p>
736
     * @param mixed $value  <p>
737
     *                      The
738
     *                      value
739
     *                      to
740
     *                      set.
741
     *                      </p>
742
     *
743
     * @return void
744
     * @since  5.0.0
745
     */
746
    public function offsetSet($offset, $value)
747
    {
748
        if (is_null($offset)) {
749
            $this->data[] = $value;
750
            return;
751
        }
752
753
        $this->data[$offset] = $value;
754
    }
755
756
    /**
757
     * Offset to unset
758
     *
759
     * @link   https://php.net/manual/en/arrayaccess.offsetunset.php
760
     * @param  mixed $offset <p>
761
     *                       The offset to unset.
762
     *                       </p>
763
     * @return void
764
     * @since  5.0.0
765
     */
766
    public function offsetUnset($offset)
767
    {
768
        $this->skipNext = $offset === key($this->data);
769
        unset($this->data[$offset]);
770
    }
771
772
    /**
773
     * Count elements of an object
774
     *
775
     * @link   https://php.net/manual/en/countable.count.php
776
     * @return int The custom count as an integer.
777
     * </p>
778
     * <p>
779
     * The return value is cast to an integer.
780
     * @since  5.1.0
781
     */
782
    /**
783
     * Counts the items of the object.
784
     *
785
     * @return integer Returns the number of items in the collection.
786
     */
787
    public function count()
788
    {
789
        return count($this->data);
790
    }
791
}
792