Passed
Push — master ( 064bed...d815ea )
by Gerrit
04:18
created

CallDefinition::resolveCallee()   B

Complexity

Conditions 9
Paths 15

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9.2733

Importance

Changes 0
Metric Value
cc 9
eloc 19
c 0
b 0
f 0
nc 15
nop 2
dl 0
loc 39
ccs 17
cts 20
cp 0.85
crap 9.2733
rs 8.0555
1
<?php
2
/**
3
 * Copyright (C) 2018 Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 *
8
 * @license GPL-3.0
9
 *
10
 * @author Gerrit Addiks <[email protected]>
11
 */
12
13
namespace Addiks\RDMBundle\Mapping;
14
15
use Addiks\RDMBundle\Mapping\CallDefinitionInterface;
16
use Addiks\RDMBundle\Mapping\MappingInterface;
17
use Webmozart\Assert\Assert;
18
use Addiks\RDMBundle\Hydration\HydrationContextInterface;
19
use Symfony\Component\DependencyInjection\ContainerInterface;
20
use Doctrine\ORM\Exception\ORMException;
21
use Doctrine\Common\Util\ClassUtils;
22
use ArgumentCountError;
23
24
final class CallDefinition implements CallDefinitionInterface
25
{
26
27
    /**
28
     * @var ContainerInterface
29
     */
30
    private $container;
31
32
    /**
33
     * @var string|null
34
     */
35
    private $objectReference;
36
37
    /**
38
     * @var string
39
     */
40
    private $routineName;
41
42
    /**
43
     * @var array<MappingInterface>
44
     */
45
    private $argumentMappings = array();
46
47
    /**
48
     * @var bool
49
     */
50
    private $isStaticCall;
51
52
    /**
53
     * @var string
54
     */
55
    private $origin;
56
57 13
    public function __construct(
58
        ContainerInterface $container,
59
        string $routineName,
60
        string $objectReference = null,
61
        array $argumentMappings = array(),
62
        bool $isStaticCall = false,
63
        string $origin = "unknown"
64
    ) {
65 13
        $this->routineName = $routineName;
66 13
        $this->objectReference = $objectReference;
67 13
        $this->isStaticCall = $isStaticCall;
68 13
        $this->container = $container;
69 13
        $this->origin = $origin;
70
71 13
        foreach ($argumentMappings as $argumentMapping) {
72
            /** @var MappingInterface $argumentMapping */
73
74 13
            Assert::isInstanceOf($argumentMapping, MappingInterface::class);
75
76 13
            $this->argumentMappings[] = $argumentMapping;
77
        }
78
    }
79
80 1
    public function __sleep(): array
81
    {
82
        return [
83 1
            'objectReference',
84
            'routineName',
85
            'argumentMappings',
86
            'isStaticCall',
87
            'origin',
88
        ];
89
    }
90
91 7
    public function execute(
92
        HydrationContextInterface $context,
93
        array $dataFromAdditionalColumns
94
    ) {
95
        /** @var mixed $result */
96 7
        $result = null;
97
98
        /** @var null|object|string $callee */
99 7
        $callee = $this->resolveCallee((string)$this->objectReference, $context);
100
101 7
        if ($this->isStaticCall && is_object($callee)) {
102
            $callee = get_class($callee);
103
        }
104
105
        /** @var array<mixed> $arguments */
106 7
        $arguments = $this->resolveArguments(
107
            $context,
108
            $dataFromAdditionalColumns
109
        );
110
111
        try {
112 7
            if (is_null($callee) && !empty($this->objectReference)) {
113
                $result = null;
114
115 7
            } elseif (is_null($callee)) {
116 1
                $result = call_user_func_array($this->routineName, $arguments);
117
118 6
            } elseif (is_string($callee)) {
119 1
                $result = call_user_func_array("{$callee}::{$this->routineName}", $arguments);
120
121
            } else {
122 7
                $result = call_user_func_array([$callee, $this->routineName], $arguments);
123
            }
124
125
        } catch (ArgumentCountError $exception) {
126
            /** @var string $calleeDescription */
127
            $calleeDescription = "";
128
129
            if (is_object($callee)) {
130
                $calleeDescription = get_class($callee);
131
132
                if (class_exists(ClassUtils::class)) {
133
                    $calleeDescription = ClassUtils::getRealClass($calleeDescription);
134
                }
135
136
            } elseif (is_string($callee)) {
137
                $calleeDescription = $callee;
138
            }
139
140
            if (!empty($calleeDescription)) {
141
                $calleeDescription .= $this->isStaticCall ?'::' :'->';
142
            }
143
144
            throw new ORMException(sprintf(
145
                "Wrong number of arguments passed to routine '%s%s' in %s: %s",
146
                $calleeDescription,
147
                $this->routineName,
148
                $this->origin,
149
                $exception->getMessage()
150
            ), 0, $exception);
151
        }
152
153 7
        return $result;
154
    }
155
156 1
    public function getObjectReference(): ?string
157
    {
158 1
        return $this->objectReference;
159
    }
160
161 1
    public function getRoutineName(): string
162
    {
163 1
        return $this->routineName;
164
    }
165
166 1
    public function getArgumentMappings(): array
167
    {
168 1
        return $this->argumentMappings;
169
    }
170
171 1
    public function isStaticCall(): bool
172
    {
173 1
        return $this->isStaticCall;
174
    }
175
176
    /**
177
     * (This return type should be nullable, but there seems to be a bug in current version psalm preventing it.)
178
     *
179
     * @return object|string|null
180
     */
181 7
    private function resolveCallee(
182
        string $objectReference,
183
        HydrationContextInterface $context
184
    ) {
185
        /** @var object|string $callee */
186 7
        $callee = null;
187
188 7
        if (!empty($objectReference)) {
189
            /** @var array<mixed> $hydrationStack */
190 6
            $hydrationStack = $context->getObjectHydrationStack();
191
192 6
            if ($objectReference[0] === '$') {
193 1
                $objectReference = substr($objectReference, 1);
194
            }
195
196 6
            if (in_array($objectReference, ['root', 'entity'])) {
197
                $callee = $context->getEntity();
198
199 6
            } elseif (in_array($objectReference, ['self', 'this'])) {
200 2
                $callee = $hydrationStack[count($hydrationStack) - 1];
201
202 4
            } elseif (in_array($objectReference, ['parent'])) {
203 1
                $callee = $hydrationStack[count($hydrationStack) - 2];
204
205 3
            } elseif ($objectReference[0] === '@') {
206
                /** @var string $serviceId */
207 2
                $serviceId = substr($objectReference, 1);
208
209 2
                $callee = $this->container->get($serviceId);
210
211 1
            } elseif (class_exists($objectReference)) {
212 1
                $callee = $objectReference;
213
214
            } elseif ($context->hasRegisteredValue($objectReference)) {
215
                $callee = $context->getRegisteredValue($objectReference);
216
            }
217
        }
218
219 7
        return $callee;
220
    }
221
222
    /**
223
     * @param array<scalar> $dataFromAdditionalColumns
224
     *
225
     * @return array<mixed>
226
     */
227 7
    private function resolveArguments(
228
        HydrationContextInterface $context,
229
        array $dataFromAdditionalColumns
230
    ): array {
231
        /** @var array<mixed> $arguments */
232 7
        $arguments = array();
233
234 7
        if (array_key_exists('', $dataFromAdditionalColumns)) {
235
            $arguments[] = $dataFromAdditionalColumns[''];
236
            unset($dataFromAdditionalColumns['']);
237
        }
238
239 7
        foreach ($this->argumentMappings as $argumentMapping) {
240
            /** @var MappingInterface $argumentMapping */
241
242 7
            $arguments[] = $argumentMapping->resolveValue(
243
                $context,
244
                $dataFromAdditionalColumns
245
            );
246
        }
247
248 7
        return $arguments;
249
    }
250
251 1
    public function wakeUpCall(ContainerInterface $container): void {
252 1
        $this->container = $container;
253
    }
254
}
255