MethodCallDenormalizer   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 81
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 40
c 1
b 0
f 1
dl 0
loc 81
rs 10
wmc 11

3 Methods

Rating   Name   Duplication   Size   Complexity  
A supportsDenormalization() 0 6 2
A __construct() 0 8 1
B denormalize() 0 40 8
1
<?php
2
3
4
namespace Apie\ObjectAccessNormalizer\Normalizers;
5
6
use Apie\ObjectAccessNormalizer\Errors\ErrorBag;
7
use Apie\ObjectAccessNormalizer\Exceptions\ValidationException;
8
use Apie\ObjectAccessNormalizer\NameConverters\NullNameConverter;
9
use Apie\ObjectAccessNormalizer\ObjectAccess\ObjectAccessInterface;
10
use ReflectionClass;
11
use ReflectionMethod;
12
use stdClass;
13
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
14
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
15
use Throwable;
16
17
/**
18
 * Special denormalizer to call a reflection method and return the value back.
19
 *
20
 * Usage:
21
 * - type: ReflectionMethod::ClassWithNameSpace::method
22
 * - context['object-instance'] object to run method on
23
 * - context['initial-arguments'] already hydrated arguments with key = variable name.
24
 * - context['object-access'] used object access instance.
25
 */
26
class MethodCallDenormalizer implements ContextAwareDenormalizerInterface
27
{
28
    /**
29
     * @var ObjectAccessInterface
30
     */
31
    private $objectAccess;
32
33
    /**
34
     * @var ApieObjectAccessNormalizer
35
     */
36
    private $normalizer;
37
38
    /**
39
     * @var NameConverterInterface
40
     */
41
    private $nameConverter;
42
43
    public function __construct(
44
        ObjectAccessInterface $objectAccess,
45
        ApieObjectAccessNormalizer $normalizer,
46
        NameConverterInterface $nameConverter = null
47
    ) {
48
        $this->objectAccess = $objectAccess;
49
        $this->normalizer = $normalizer;
50
        $this->nameConverter = $nameConverter ?? new NullNameConverter();
51
    }
52
53
    /**
54
     * {@inheritDoc}
55
     */
56
    public function supportsDenormalization($data, $type, $format = null, array $context = [])
57
    {
58
        if (strpos($type, 'ReflectionMethod::') !== 0) {
59
            return false;
60
        }
61
        return isset($context['object-instance']);
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     */
67
    public function denormalize($data, $type, $format = null, array $context = [])
68
    {
69
        if ($data instanceof stdClass) {
70
            $data = json_decode(json_encode($data), true);
71
        }
72
        if (!isset($context['key_prefix'])) {
73
            $context['key_prefix'] = '';
74
        }
75
        $method = new ReflectionMethod(substr($type, strlen('ReflectionMethod::')));
76
        $objectAccess = $context['object_access'] ?? $this->objectAccess;
77
        $arguments = $objectAccess->getMethodArguments($method, new ReflectionClass($context['object-instance']));
78
        $initialArguments = $context['initial-arguments'] ?? [];
79
        $returnObject = $initialArguments;
80
        $errorBag = new ErrorBag('');
81
        foreach ($arguments as $denormalizedFieldName => $typeHint) {
82
            $fieldName = $this->nameConverter->normalize($denormalizedFieldName, $type, $format, $context);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Component\Serial...rInterface::normalize() has too many arguments starting with $type. ( Ignorable by Annotation )

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

82
            /** @scrutinizer ignore-call */ 
83
            $fieldName = $this->nameConverter->normalize($denormalizedFieldName, $type, $format, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
83
            if (isset($initialArguments[$fieldName])) {
84
                continue;
85
            }
86
            if (!isset($data[$fieldName])) {
87
                $errorBag->addThrowable($fieldName, new ValidationException([$fieldName => ['required']]));
88
                continue;
89
            }
90
            try {
91
                $returnObject[$fieldName] = $this->normalizer->denormalizeType(
92
                    $data,
93
                    $denormalizedFieldName,
94
                    $fieldName,
95
                    $typeHint,
96
                    $format,
97
                    $context
98
                );
99
            } catch (Throwable $throwable) {
100
                $errorBag->addThrowable($fieldName, $throwable);
101
            }
102
        }
103
        if ($errorBag->hasErrors()) {
104
            throw new ValidationException($errorBag);
105
        }
106
        return $method->invokeArgs($context['object-instance'], $returnObject);
107
    }
108
}
109