Passed
Pull Request — master (#4)
by Pieter
02:18
created

PolymorphicRelationObjectAccess::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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