Passed
Pull Request — master (#4)
by Pieter
01:45
created

PolymorphicRelationObjectAccess::isSupported()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 9
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 16
rs 9.2222
1
<?php
2
3
namespace W2w\Lib\ApieObjectAccessNormalizer\ObjectAccess;
4
5
use ReflectionClass;
6
use Symfony\Component\PropertyInfo\Type;
7
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
8
use W2w\Lib\Apie\Exceptions\BadConfigurationException;
9
use W2w\Lib\ApieObjectAccessNormalizer\Errors\ErrorBag;
10
use W2w\Lib\ApieObjectAccessNormalizer\Exceptions\CouldNotConvertException;
11
use W2w\Lib\ApieObjectAccessNormalizer\Exceptions\ValidationException;
12
use W2w\Lib\ApieObjectAccessNormalizer\Interfaces\ObjectAccessWithNotFilterablePropertiesInterface;
13
use W2w\Lib\ApieObjectAccessNormalizer\ObjectAccess\Getters\DiscriminatorColumn;
14
15
class PolymorphicRelationObjectAccess extends ObjectAccess implements
16
    ObjectAccessSupportedInterface,
17
    ObjectAccessWithNotFilterablePropertiesInterface {
18
    /**
19
     * @var string
20
     */
21
    private $baseClass;
22
    /**
23
     * @var array
24
     */
25
    private $subClasses;
26
    /**
27
     * @var string
28
     */
29
    private $discriminatorColumn;
30
31
    public function __construct(
32
        string $baseClass,
33
        array $subClasses,
34
        string $discriminatorColumn = 'type',
35
        bool $publicOnly = true,
36
        bool $disabledConstructor = false
37
    ) {
38
        if (array_search($baseClass, $subClasses) !== false) {
39
            throw new BadConfigurationException('You can not map the base class as a subclass!');
40
        }
41
        $this->baseClass = $baseClass;
42
        $this->subClasses = $subClasses;
43
        $this->discriminatorColumn = $discriminatorColumn;
44
        parent::__construct($publicOnly, $disabledConstructor);
45
    }
46
47
    private function findClass(string $search): ?ReflectionClass
48
    {
49
        foreach ($this->subClasses as $className => $discriminator) {
50
            if ($discriminator === $search) {
51
                return new ReflectionClass($search);
52
            }
53
        }
54
        return null;
55
    }
56
57
    public function getConstructorArguments(ReflectionClass $reflectionClass, array $data = []): array
58
    {
59
        $discriminator = $data[$this->discriminatorColumn] ?? null;
60
        if (isset($discriminator)) {
61
            $reflectionClass = $this->findClass($discriminator) ?? $reflectionClass;
62
        }
63
        $arguments = parent::getConstructorArguments($reflectionClass);
64
        if (isset($arguments[$this->discriminatorColumn])) {
65
            throw new BadConfigurationException("You can not map the discriminator column in the constructor!");
66
        }
67
        return [$this->discriminatorColumn => new Type(Type::BUILTIN_TYPE_STRING)] + $arguments;
68
    }
69
70
    protected function getGetterMapping(ReflectionClass $reflectionClass): array
71
    {
72
        $mapping = parent::getGetterMapping($reflectionClass);
73
        $mapping[$this->discriminatorColumn] = [new DiscriminatorColumn($this->discriminatorColumn, $this->subClasses)];
74
        return $mapping;
75
    }
76
77
    public function instantiate(ReflectionClass $reflectionClass, array $constructorArgs): object
78
    {
79
        if (!isset($constructorArgs[$this->discriminatorColumn])) {
80
            $errorBag = new ErrorBag('');
81
            $errorBag->addThrowable(
82
                $this->discriminatorColumn,
83
                new MissingConstructorArgumentsException($this->discriminatorColumn . ' is required')
84
            );
85
            throw new ValidationException($errorBag);
86
        }
87
        $discriminator = $constructorArgs[$this->discriminatorColumn];
88
        if (!isset($this->subClasses[$discriminator])) {
89
            $errorBag = new ErrorBag('');
90
            $errorBag->addThrowable(
91
                $this->discriminatorColumn,
92
                new CouldNotConvertException(
93
                    json_encode(array_keys($this->subClasses)),
94
                    json_encode($discriminator)
95
                )
96
            );
97
            throw new ValidationException($errorBag);
98
        }
99
        $discriminatorClass = $this->subClasses[$discriminator];
100
        if (!is_a($discriminatorClass, $reflectionClass->name, true)) {
101
            $errorBag = new ErrorBag('');
102
            $errorBag->addThrowable(
103
                $this->discriminatorColumn,
104
                new CouldNotConvertException(
105
                    'ReflectionClass<' . $reflectionClass->name . '>',
106
                    'ReflectionClass<' . $discriminatorClass . '|subtype of ' . $discriminatorClass . '>',
107
                )
108
            );
109
            throw new ValidationException($errorBag);
110
        }
111
        unset($constructorArgs[$this->discriminatorColumn]);
112
        return parent::instantiate(new ReflectionClass($discriminatorClass), $constructorArgs);
113
    }
114
115
    public function isSupported(ReflectionClass $reflectionClass): bool
116
    {
117
        if ($reflectionClass->name === $this->baseClass) {
118
            return true;
119
        }
120
        foreach ($this->subClasses as $subClass) {
121
            if ($reflectionClass->name === $subClass) {
122
                return true;
123
            }
124
        }
125
        foreach ($this->subClasses as $subClass) {
126
            if (is_a($reflectionClass->name, $subClass, true)) {
127
                return true;
128
            }
129
        }
130
        return false;
131
    }
132
133
    /**
134
     * THe discriminator column can not be filtered out.
135
     *
136
     * @return string[]
137
     */
138
    public function getNotFilterableProperties(): array
139
    {
140
        return [$this->discriminatorColumn];
141
    }
142
}
143