FormRequestRulesParser::withReflection()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace AlexWells\ApiDocsGenerator\Parsers;
4
5
use ReflectionClass;
6
use Illuminate\Foundation\Http\FormRequest;
7
use Illuminate\Contracts\Validation\ValidatesWhenResolved;
8
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
9
10
class FormRequestRulesParser
11
{
12
    /**
13
     * Form request class name string.
14
     *
15
     * @var string
16
     */
17
    protected $className;
18
19
    /**
20
     * Reflection of FormRequest class.
21
     *
22
     * @var ReflectionClass
23
     */
24
    protected $reflection;
25
26
    /**
27
     * FormRequest instance.
28
     *
29
     * @var FormRequest
30
     */
31
    protected $formRequestInstance;
32
33
    /**
34
     * Set FormRequest class name.
35
     *
36
     * @param string $className
37
     *
38
     * @return $this
39
     */
40
    public function setClassName(string $className)
41
    {
42
        $this->className = $className;
43
44
        return $this;
45
    }
46
47
    /**
48
     * Set reflection class.
49
     *
50
     * @param ReflectionClass $reflection
51
     *
52
     * @return $this
53
     */
54
    public function setReflection(ReflectionClass $reflection)
55
    {
56
        $this->reflection = $reflection;
57
58
        return $this;
59
    }
60
61
    /**
62
     * Set FormRequest class instance.
63
     *
64
     * @param FormRequest $instance
65
     *
66
     * @return $this
67
     */
68
    public function setFormRequestInstance(FormRequest $instance)
69
    {
70
        $this->formRequestInstance = $instance;
71
72
        return $this;
73
    }
74
75
    /**
76
     * Create instance from ReflectionClass.
77
     *
78
     * @param ReflectionClass $reflection
79
     *
80
     * @return $this
81
     */
82
    public static function withReflection(ReflectionClass $reflection)
83
    {
84
        return (new static())
85
            ->setClassName($reflection->getName())
86
            ->setReflection($reflection);
87
    }
88
89
    /**
90
     * Create instance from class name.
91
     *
92
     * @param string $className
93
     *
94
     * @return $this
95
     */
96
    public static function withClassName(string $className)
97
    {
98
        return (new static())
99
            ->setClassName($className)
100
            ->setReflection(new ReflectionClass($className));
101
    }
102
103
    /**
104
     * Create instance from FormRequest instance.
105
     *
106
     * @param FormRequest $instance
107
     *
108
     * @return $this
109
     */
110
    public static function withInstance(FormRequest $instance)
111
    {
112
        return static::withClassName(get_class($instance))
113
            ->setFormRequestInstance($instance);
114
    }
115
116
    /**
117
     * Parse the rules.
118
     *
119
     * @return array
120
     */
121
    public function parse()
122
    {
123
        $instance = $this->getFormRequestInstance();
124
125
        if(method_exists($instance, 'validator')) {
126
            $rules = $this->parseRulesFromValidator();
127
        } elseif(method_exists($instance, 'rules')) {
128
            $rules = $this->parseRulesFromMethod();
129
        } else {
130
            return [];
131
        }
132
133
        return $this->formatRules($rules);
134
    }
135
136
    /**
137
     * Format the rules into single format.
138
     *
139
     * @return array
140
     */
141
    protected static function formatRules(array $rules)
142
    {
143
        // This was copied from Laravel
144
        return array_map(function ($rule) {
145
            if (is_string($rule)) {
146
                return explode('|', $rule);
147
            } elseif (is_object($rule)) {
148
                return [strval($rule)];
149
            } else {
150
                return array_map(function ($rule) {
151
                    return is_object($rule) ? strval($rule) : $rule;
152
                }, $rule);
153
            }
154
        }, $rules);
155
    }
156
157
    /**
158
     * Parse rules from FormRequest's `validate` method.
159
     *
160
     * @return array
161
     */
162
    protected function parseRulesFromValidator()
163
    {
164
        // First we call needed method with needed container
165
        $factory = app()->make(ValidationFactory::class);
166
        $validator = app()->call([$this->getFormRequestInstance(), 'validator'], [$factory]);
167
168
        // After that we get initialRules property (since plain `rules` prop doesn't contain array rules)
169
        // and make it public so that later we can get it's value
170
        $property = (new ReflectionClass($validator))->getProperty('initialRules');
171
        $property->setAccessible(true);
172
173
        return $property->getValue($validator);
174
    }
175
176
    /**
177
     * Parse rules from FormRequest's `rules` method.
178
     *
179
     * @return array
180
     */
181
    protected function parseRulesFromMethod()
182
    {
183
        return app()->call([$this->getFormRequestInstance(), 'rules']);
184
    }
185
186
    /**
187
     * Return FormRequest class instance.
188
     *
189
     * @return FormRequest
190
     */
191
    protected function getFormRequestInstance()
192
    {
193
        if(! $this->formRequestInstance) {
194
            return $this->formRequestInstance = $this->getNewFormRequestInstance();
195
        }
196
197
        return $this->formRequestInstance;
198
    }
199
200
    /**
201
     * Get new FormRequest class instance with container but without calling `validate` method.
202
     *
203
     * @return FormRequest
204
     */
205
    protected function getNewFormRequestInstance()
206
    {
207
        // First we get container instance and it's reflection
208
        $container = app();
209
        $containerReflection = new ReflectionClass($container);
210
211
        // Then we get `afterResolvingCallbacks` property of container,
212
        // make it accessible (public) and store it's value into temp var
213
        $property = $containerReflection->getProperty('afterResolvingCallbacks');
214
        $property->setAccessible(true);
215
        $originalValue = $property->getValue($container);
216
217
        // After that we get it's value once again into new variable and do the most important part:
218
        // we set an empty array for `ValidatesWhenResolved` interface, which stops Laravel's
219
        // container from calling `validate` method right after creating FormRequest class,
220
        // which uses `ValidatesWhenResolvedTrait`
221
        $modified = $property->getValue($container);
222
        $modified[ValidatesWhenResolved::class] = [];
223
        $property->setValue($container, $modified);
224
225
        // Then we get a new instance of FormRequest using our reflection,
226
        // so that it doesn't throw a ValidationException right away
227
        /** @var FormRequest $formRequest */
228
        $formRequest = $containerReflection->getMethod('make')->invoke($container, $this->className);
229
230
        // Just in case we set original value back
231
        $property->setValue($container, $originalValue);
232
233
        // Finally, we return created instance
234
        return $formRequest;
235
    }
236
}
237