Completed
Push — master ( 45b9bd...dd95a4 )
by Sergey
02:29
created

RequestObjectBinder::resolvePayload()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 9
rs 9.6666
cc 2
eloc 4
nc 2
nop 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
     * @param PayloadResolver $payloadResolver
30
     * @param ValidatorInterface $validator
31
     * @param ErrorResponseProvider|null $errorResponseProvider
32
     */
33
    public function __construct(
34
        PayloadResolver $payloadResolver,
35
        ValidatorInterface $validator,
36
        ErrorResponseProvider $errorResponseProvider = null
37
    )
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
     * @return Response|null
48
     */
49
    public function bind(Request $request, callable $action)
50
    {
51
        $matchedArguments = $this->matchActionArguments($action);
52
        if (!isset($matchedArguments['requestObject'])) {
53
            return null;
54
        }
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 (isset($matchedArguments['errors'])) {
75
            $request->attributes->set($matchedArguments['errors']->name, $errors);
76
        } elseif (0 !== count($errors)) {
77
            return $this->providerErrorResponse($requestObject, $errors);
78
        }
79
80
        return null;
81
    }
82
83
    /**
84
     * @param RequestObject $requestObject
85
     * @param ConstraintViolationListInterface $errors
86
     * @return Response
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
     * @return \ReflectionParameter
105
     */
106
    private function matchActionArguments(callable $action)
107
    {
108
        if (is_array($action)) {
109
            $classReflection = new \ReflectionClass($action[0]);
110
            $actionReflection = $classReflection->getMethod($action[1]);
111
        } else {
112
            $actionReflection = new \ReflectionFunction($action);
113
        }
114
115
        $matchedArguments = [];
116
        $arguments = $actionReflection->getParameters();
117
        foreach ($arguments as $argument) {
118
            if ($this->isArgumentIsSubtypeOf($argument, RequestObject::class)) {
119
                $matchedArguments['requestObject'] = $argument;
120
            }
121
            if ($this->isArgumentIsSubtypeOf($argument, ConstraintViolationListInterface::class)) {
122
                $matchedArguments['errors'] = $argument;
123
            }
124
        }
125
126
        return $matchedArguments;
127
    }
128
129
    private function resolvePayload(RequestObject $requestObject, Request $request)
130
    {
131
        if ($requestObject instanceof PayloadResolver) {
132
133
            return $requestObject->resolvePayload($request);
134
        }
135
136
        return $this->payloadResolver->resolvePayload($request);
137
    }
138
139
    /**
140
     * @param \ReflectionParameter $argument
141
     * @param string $subtype
142
     * @return bool
143
     */
144
    private function isArgumentIsSubtypeOf(\ReflectionParameter $argument, $subtype)
145
    {
146
        if (!($className = $argument->getClass())) {
147
            return false;
148
        }
149
150
        return is_a($className->name, $subtype, true);
151
    }
152
}
153