RequestObjectBinder   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 20
lcom 1
cbo 8
dl 0
loc 148
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A providerErrorResponse() 0 12 3
B bind() 0 30 5
C matchActionArguments() 0 26 7
A resolvePayload() 0 8 2
A isArgumentIsSubtypeOf() 0 8 2
1
<?php
2
3
namespace Fesor\RequestObject;
4
5
use Symfony\Component\HttpFoundation\Request;
6
use Symfony\Component\HttpFoundation\Response;
7
use Symfony\Component\Validator\ConstraintViolationListInterface;
8
use Symfony\Component\Validator\Validator\ValidatorInterface;
9
10
class RequestObjectBinder
11
{
12
    /**
13
     * @var ValidatorInterface
14
     */
15
    private $validator;
16
17
    /**
18
     * @var PayloadResolver
19
     */
20
    private $payloadResolver;
21
22
    /**
23
     * @var ErrorResponseProvider|null
24
     */
25
    private $errorResponseProvider;
26
27
    /**
28
     * RequestObjectBinder constructor.
29
     *
30
     * @param PayloadResolver            $payloadResolver
31
     * @param ValidatorInterface         $validator
32
     * @param ErrorResponseProvider|null $errorResponseProvider
33
     */
34
    public function __construct(
35
        PayloadResolver $payloadResolver,
36
        ValidatorInterface $validator,
37
        ErrorResponseProvider $errorResponseProvider = null
38
    ) {
39
        $this->validator = $validator;
40
        $this->payloadResolver = $payloadResolver;
41
        $this->errorResponseProvider = $errorResponseProvider;
42
    }
43
44
    /**
45
     * @param Request  $request
46
     * @param callable $action
47
     *
48
     * @return Response|void
49
     */
50
    public function bind(Request $request, callable $action)
51
    {
52
        $matchedArguments = $this->matchActionArguments($action);
53
        if (!array_key_exists('requestObject', $matchedArguments)) {
54
            return;
55
        }
56
57
        $requestObjectClass = $matchedArguments['requestObject']->getClass()->name;
58
        /** @var RequestObject $requestObject */
59
        $requestObject = new $requestObjectClass();
60
        $request->attributes->set(
61
            $matchedArguments['requestObject']->name,
62
            $requestObject
63
        );
64
65
        $payload = $this->resolvePayload($requestObject, $request);
66
67
        $errors = $this->validator->validate(
68
            $payload,
69
            $requestObject->rules(),
70
            $requestObject->validationGroup($payload)
71
        );
72
73
        $requestObject->setPayload($payload);
74
        if (array_key_exists('errors', $matchedArguments)) {
75
            $request->attributes->set($matchedArguments['errors']->name, $errors);
76
        } elseif ($errors && 0 !== $errors->count()) {
77
            return $this->providerErrorResponse($requestObject, $errors);
78
        }
79
    }
80
81
    /**
82
     * @param RequestObject                    $requestObject
83
     * @param ConstraintViolationListInterface $errors
84
     *
85
     * @return Response
86
     *
87
     * @throws InvalidRequestPayloadException
88
     */
89
    private function providerErrorResponse(RequestObject $requestObject, ConstraintViolationListInterface $errors)
90
    {
91
        if ($requestObject instanceof ErrorResponseProvider) {
92
            return $requestObject->getErrorResponse($errors);
93
        }
94
95
        if ($this->errorResponseProvider) {
96
            return $this->errorResponseProvider->getErrorResponse($errors);
97
        }
98
99
        throw new InvalidRequestPayloadException($requestObject, $errors);
100
    }
101
102
    /**
103
     * @param callable $action
104
     *
105
     * @return array
106
     */
107
    private function matchActionArguments(callable $action)
108
    {
109
        if (is_array($action)) {
110
            $classReflection = new \ReflectionClass($action[0]);
111
            $actionReflection = $classReflection->getMethod($action[1]);
112
        } elseif ($action instanceof \Closure || is_string($action)) {
113
            $actionReflection = new \ReflectionFunction($action);
114
        }
115
        else {
116
            $classReflection = new \ReflectionClass($action);
117
            $actionReflection = $classReflection->getMethod('__invoke');
118
        }
119
120
        $matchedArguments = [];
121
        $arguments = $actionReflection->getParameters();
122
        foreach ($arguments as $argument) {
123
            if ($this->isArgumentIsSubtypeOf($argument, RequestObject::class)) {
124
                $matchedArguments['requestObject'] = $argument;
125
            }
126
            if ($this->isArgumentIsSubtypeOf($argument, ConstraintViolationListInterface::class)) {
127
                $matchedArguments['errors'] = $argument;
128
            }
129
        }
130
131
        return $matchedArguments;
132
    }
133
134
    private function resolvePayload(RequestObject $requestObject, Request $request)
135
    {
136
        if ($requestObject instanceof PayloadResolver) {
137
            return $requestObject->resolvePayload($request);
138
        }
139
140
        return $this->payloadResolver->resolvePayload($request);
141
    }
142
143
    /**
144
     * @param \ReflectionParameter $argument
145
     * @param string               $subtype
146
     *
147
     * @return bool
148
     */
149
    private function isArgumentIsSubtypeOf(\ReflectionParameter $argument, $subtype)
150
    {
151
        if (!($className = $argument->getClass())) {
152
            return false;
153
        }
154
155
        return is_a($className->name, $subtype, true);
156
    }
157
}
158