Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.57% covered (warning)
88.57%
31 / 35
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PropertyAccess
88.57% covered (warning)
88.57%
31 / 35
25.00% covered (danger)
25.00%
1 / 4
15.34
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPropertyValue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visit
93.10% covered (success)
93.10%
27 / 29
0.00% covered (danger)
0.00%
0 / 1
11.04
 throwErrorOnMissingValue
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2namespace Apie\Core;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\IndexNotFoundException;
6use Apie\Core\Lists\ItemHashmap;
7use Apie\Core\Lists\ItemListInterface;
8use Apie\Core\Lists\ItemSet;
9use Apie\Core\Metadata\GetterInterface;
10use Apie\Core\Metadata\MetadataFactory;
11use Apie\Core\Metadata\MetadataInterface;
12use ReflectionClass;
13
14final class PropertyAccess
15{
16    private function __construct()
17    {
18    }
19
20    /**
21     * @param array<int, string> $property
22     */
23    public static function getPropertyValue(
24        object $object,
25        array $property,
26        ApieContext $context = new ApieContext(),
27        bool $throwErrorOnMissing = true
28    ): mixed {
29        $node = MetadataFactory::getResultMetadata(new ReflectionClass($object), $context);
30
31        return self::visit($object, $node, $context, $property, $throwErrorOnMissing);
32    }
33
34    /**
35     * @param array<int, string> $property
36     */
37    private static function visit(
38        mixed $object,
39        MetadataInterface $node,
40        ApieContext $apieContext,
41        array $property,
42        bool $throwErrorOnMissing
43    ): mixed {
44        $hashmap = $node->getHashmap();
45        $key = array_shift($property);
46        if (($hashmap[$key] ?? null) instanceof GetterInterface) {
47            $newObject = $hashmap[$key]->getValue($object, $apieContext);
48            if (empty($property)) {
49                return $newObject;
50            }
51            if (!is_object($newObject)) {
52                return self::throwErrorOnMissingValue($key, $throwErrorOnMissing);
53            }
54            $class = new ReflectionClass($newObject);
55            return self::visit(
56                $newObject,
57                MetadataFactory::getResultMetadata($class, $apieContext),
58                $apieContext,
59                $property,
60                $throwErrorOnMissing
61            );
62        }
63        $arrayItemType = $node->getArrayItemType();
64        if (null === $arrayItemType) {
65            return self::throwErrorOnMissingValue($key, $throwErrorOnMissing);
66        }
67        if (is_array($object) || $object instanceof ItemListInterface || $object instanceof ItemHashmap) {
68            if ($object instanceof ItemSet) {
69                $object = $object->toArray();
70            }
71            if (!isset($object[$key])) {
72                return self::throwErrorOnMissingValue($key, $throwErrorOnMissing);
73            }
74            $value = $object[$key];
75            if (empty($property)) {
76                return $value;
77            }
78            return self::visit($value, $arrayItemType, $apieContext, $property, $throwErrorOnMissing);
79        }
80
81        return self::throwErrorOnMissingValue($key, $throwErrorOnMissing);
82    }
83
84    /**
85     * @phpstan-return ($throwErrorOnMissing is true ? never : null)
86     */
87    private static function throwErrorOnMissingValue(?string $key, bool $throwErrorOnMissing): mixed
88    {
89        if ($throwErrorOnMissing) {
90            throw new IndexNotFoundException($key);
91        }
92        return null;
93    }
94}