PactNormalizer::instantiateObject()   F
last analyzed

Complexity

Conditions 26
Paths 152

Size

Total Lines 78
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 48
c 0
b 0
f 0
nc 152
nop 6
dl 0
loc 78
rs 3.7333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * PHPacto - Contract testing solution
5
 *
6
 * Copyright (c) Damian Długosz
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
namespace PHPacto\Serializer;
23
24
use PHPacto\Matcher\Mismatches;
25
use PHPacto\Pact;
26
use PHPacto\PactInterface;
27
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
28
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
29
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
30
use Symfony\Component\Serializer\Exception\RuntimeException;
31
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
32
33
class PactNormalizer extends AbstractNormalizer
34
{
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function getSupportedTypes(?string $format): array
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed. ( Ignorable by Annotation )

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

38
    public function getSupportedTypes(/** @scrutinizer ignore-unused */ ?string $format): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
39
    {
40
        return [
41
            PactInterface::class => true,
42
            PactInterface::class.'[]' => true,
43
        ];
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function supportsNormalization($data, $format = null): bool
50
    {
51
        return $data instanceof PactInterface && self::isFormatSupported($format);
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function supportsDenormalization($data, $type, $format = null): bool
58
    {
59
        return \is_array($data) && PactInterface::class === $type && self::isFormatSupported($format);
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function normalize($object, $format = null, array $context = [])
66
    {
67
        if (!$object instanceof PactInterface) {
68
            throw new InvalidArgumentException(sprintf('The object "%s" must implement "%s".', \get_class($object), PactInterface::class));
69
        }
70
71
        return $this->normalizePactObject($object, $format, $context);
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function denormalize($data, $class, $format = null, array $context = [])
78
    {
79
        if (!(\is_array($data) && PactInterface::class === $class)) {
80
            throw new InvalidArgumentException(sprintf('Data must be array type and class equal to "%s".', $class));
81
        }
82
83
        return $this->denormalizeArray($data, Pact::class, $format, $context);
84
    }
85
86
    protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null)
87
    {
88
        $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
89
        if ($constructor) {
90
            $constructorParameters = $constructor->getParameters();
91
92
            $mismatches = [];
93
            $params = [];
94
            foreach ($constructorParameters as $constructorParameter) {
95
                $paramName = $constructorParameter->name;
96
                $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
97
98
                $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes, true);
99
                $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
100
                if ($constructorParameter->isVariadic()) {
101
                    if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
102
                        if (!\is_array($data[$paramName])) {
103
                            throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.', $class, $constructorParameter->name));
104
                        }
105
106
                        $params = array_merge($params, $data[$paramName]);
107
                    }
108
                } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
109
                    $parameterData = $data[$key];
110
111
                    if (null === $parameterData && $constructorParameter->allowsNull()) {
112
                        $params[] = null;
113
                        // Don't run set for a parameter passed to the constructor
114
                        unset($data[$key]);
115
                        continue;
116
                    }
117
118
                    try {
119
                       $parameterType = self::getParameterReflectionClass($constructorParameter);
120
121
                        if (null !== $parameterType) {
122
                            $parameterData = $this->recursiveDenormalization($parameterData, $parameterType->getName(), $format, $this->createChildContext($context, $paramName, $format));
123
                        }
124
                    } catch (Mismatches\Mismatch $e) {
125
                        $mismatches[strtoupper($key)] = $e;
126
                    } catch (\ReflectionException $e) {
127
                        throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
128
                    } catch (MissingConstructorArgumentsException $e) {
129
                        if (!$constructorParameter->getType()->allowsNull()) {
130
                            throw $e;
131
                        }
132
                        $parameterData = null;
133
                    }
134
135
                    // Don't run set for a parameter passed to the constructor
136
                    $params[] = $parameterData;
137
                    unset($data[$key]);
138
                } elseif ($constructorParameter->isDefaultValueAvailable()) {
139
                    $params[] = $constructorParameter->getDefaultValue();
140
                } else {
141
                    $message = sprintf('Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name);
142
143
                    // MissingConstructorArgumentsException added on Sf 4.1
144
                    if (class_exists(MissingConstructorArgumentsException::class)) {
145
                        throw new MissingConstructorArgumentsException($message);
146
                    }
147
148
                    throw new RuntimeException($message);
149
                }
150
            }
151
152
            if ($mismatches) {
153
                throw new Mismatches\MismatchCollection($mismatches, 'There are {{ count }} errors');
154
            }
155
156
            if ($constructor->isConstructor()) {
157
                return $reflectionClass->newInstanceArgs($params);
158
            }
159
160
            return $constructor->invokeArgs(null, $params);
161
        }
162
163
        return new $class();
164
    }
165
166
    private function normalizePactObject(PactInterface $object, $format = null, array $context = [])
167
    {
168
        if (!isset($context['cache_key'])) {
169
            $context['cache_key'] = $this->getCacheKey($format, $context);
170
        }
171
172
        $data = [];
173
174
        //$attributes = $this->getAttributes($object, $format, $context);
175
        $attributes = ['version', 'description', 'request', 'response'];
176
177
        foreach ($attributes as $attribute) {
178
            $attributeValue = $this->getAttributeValue($object, $attribute, $format, $context);
179
180
            if ($this->nameConverter) {
181
                $attribute = $this->nameConverter->normalize($attribute);
182
            }
183
184
            if (null !== $attributeValue && !is_scalar($attributeValue)) {
185
                $data[$attribute] = $this->recursiveNormalization($attributeValue, $format, $this->createChildContext($context, $attribute, $format));
186
            } else {
187
                $data[$attribute] = $attributeValue;
188
            }
189
        }
190
191
        return $data;
192
    }
193
194
    private function denormalizeArray($data, $class, $format = null, array $context = []): PactInterface
195
    {
196
        if (!isset($context['cache_key'])) {
197
            $context['cache_key'] = $this->getCacheKey($format, $context);
198
        }
199
200
        $allowedAttributes = $this->getAllowedAttributes($class, $context, true);
201
202
        $reflectionClass = new \ReflectionClass($class);
203
        $object = $this->instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format);
204
205
        foreach ($data as $attribute => $value) {
206
            if ($this->nameConverter) {
207
                $attribute = $this->nameConverter->denormalize($attribute);
208
            }
209
210
            if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes, true)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) {
0 ignored issues
show
Bug introduced by
It seems like $allowedAttributes can also be of type true; however, parameter $haystack of in_array() does only seem to accept array, 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

210
            if ((false !== $allowedAttributes && !\in_array($attribute, /** @scrutinizer ignore-type */ $allowedAttributes, true)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) {
Loading history...
211
                $extraAttributes[] = $attribute;
212
213
                continue;
214
            }
215
216
            try {
217
                $this->setAttributeValue($object, $attribute, $value, $format, $context);
218
            } catch (InvalidArgumentException $e) {
219
                throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
220
            }
221
        }
222
223
        if (!empty($extraAttributes)) {
224
            throw new ExtraAttributesException($extraAttributes);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $extraAttributes seems to be defined by a foreach iteration on line 205. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
225
        }
226
227
        return $object;
228
    }
229
}
230