Passed
Push — master ( 76b039...f6fc8d )
by Alex
04:36
created

getNewFormRequestInstance()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 11
nc 1
nop 0
1
<?php
2
3
namespace AlexWells\ApiDocsGenerator\Parsers;
4
5
use Illuminate\Contracts\Validation\ValidatesWhenResolved;
6
use Illuminate\Foundation\Http\FormRequest;
7
use ReflectionClass;
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
     * @return $this
38
     */
39
    public function setClassName(string $className)
40
    {
41
        $this->className = $className;
42
43
        return $this;
44
    }
45
46
    /**
47
     * Set reflection class.
48
     *
49
     * @param ReflectionClass $reflection
50
     * @return $this
51
     */
52
    public function setReflection(ReflectionClass $reflection)
53
    {
54
        $this->reflection = $reflection;
55
56
        return $this;
57
    }
58
59
    /**
60
     * Set FormRequest class instance.
61
     *
62
     * @param FormRequest $instance
63
     * @return $this
64
     */
65
    public function setFormRequestInstance(FormRequest $instance)
66
    {
67
        $this->formRequestInstance = $instance;
68
69
        return $this;
70
    }
71
72
    /**
73
     * Create instance from ReflectionClass.
74
     *
75
     * @param ReflectionClass $reflection
76
     * @return $this
77
     */
78
    public static function withReflection(ReflectionClass $reflection)
79
    {
80
        return (new static())
81
            ->setClassName($reflection->getName())
82
            ->setReflection($reflection);
83
    }
84
85
    /**
86
     * Create instance from class name.
87
     *
88
     * @param string $className
89
     * @return $this
90
     */
91
    public static function withClassName(string $className)
92
    {
93
        return (new static())
94
            ->setClassName($className)
95
            ->setReflection(new ReflectionClass($className));
96
    }
97
98
    /**
99
     * Create instance from FormRequest instance.
100
     *
101
     * @param FormRequest $instance
102
     * @return $this
103
     */
104
    public static function withInstance(FormRequest $instance)
105
    {
106
        return static::withClassName(get_class($instance))
107
            ->setFormRequestInstance($instance);
108
    }
109
110
    /**
111
     * Parse the rules.
112
     *
113
     * @return array
114
     */
115
    public function parse()
116
    {
117
        $instance = $this->getFormRequestInstance();
118
119
        if(method_exists($instance, 'validator')) {
120
            $rules = $this->parseRulesFromValidator();
121
        } elseif(method_exists($instance, 'rules')) {
122
            $rules = $this->parseRulesFromMethod();
123
        } else {
124
            return [];
125
        }
126
127
        return $this->formatRules($rules);
128
    }
129
130
    /**
131
     * Format the rules into single format.
132
     *
133
     * @return array
134
     */
135
    protected static function formatRules(array $rules)
136
    {
137
        // This was copied from Laravel
138
        return array_map(function ($rule) {
139
            if (is_string($rule)) {
140
                return explode('|', $rule);
141
            } elseif (is_object($rule)) {
142
                return [strval($rule)];
143
            } else {
144
                return array_map(function ($rule) {
145
                    return is_object($rule) ? strval($rule) : $rule;
146
                }, $rule);
147
            }
148
        }, $rules);
149
    }
150
151
    /**
152
     * Parse rules from FormRequest's `validate` method.
153
     *
154
     * @return array
155
     */
156
    protected function parseRulesFromValidator()
157
    {
158
        // First we call needed method with needed container
159
        $factory = app()->make(ValidationFactory::class);
160
        $validator = app()->call([$this->getFormRequestInstance(), 'validator'], [$factory]);
161
162
        // After that we get initialRules property (since plain `rules` prop doesn't contain array rules)
163
        // and make it public so that later we can get it's value
164
        $property = (new ReflectionClass($validator))->getProperty('initialRules');
165
        $property->setAccessible(true);
166
167
        return $property->getValue($validator);
1 ignored issue
show
Bug introduced by
It seems like $validator can also be of type callable; however, parameter $object of ReflectionProperty::getValue() does only seem to accept object, 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

167
        return $property->getValue(/** @scrutinizer ignore-type */ $validator);
Loading history...
168
    }
169
170
    /**
171
     * Parse rules from FormRequest's `rules` method.
172
     *
173
     * @return array
174
     */
175
    protected function parseRulesFromMethod()
176
    {
177
        return app()->call([$this->getFormRequestInstance(), 'rules']);
1 ignored issue
show
Bug Best Practice introduced by
The expression return app()->call(array...stInstance(), 'rules')) also could return the type callable which is incompatible with the documented return type array.
Loading history...
178
    }
179
180
    /**
181
     * Return FormRequest class instance.
182
     *
183
     * @return FormRequest
184
     */
185
    protected function getFormRequestInstance()
186
    {
187
        if(! $this->formRequestInstance) {
188
            return $this->formRequestInstance = $this->getNewFormRequestInstance();
189
        }
190
191
        return $this->formRequestInstance;
192
    }
193
194
    /**
195
     * Get new FormRequest class instance with container but without calling `validate` method.
196
     *
197
     * @return FormRequest
198
     */
199
    protected function getNewFormRequestInstance()
200
    {
201
        // First we get container instance and it's reflection
202
        $container = app();
203
        $containerReflection = new ReflectionClass($container);
204
205
        // Then we get `afterResolvingCallbacks` property of container,
206
        // make it accessible (public) and store it's value into temp var
207
        $property = $containerReflection->getProperty('afterResolvingCallbacks');
208
        $property->setAccessible(true);
209
        $originalValue = $property->getValue($container);
210
211
        // After that we get it's value once again into new variable and do the most important part:
212
        // we set an empty array for `ValidatesWhenResolved` interface, which stops Laravel's
213
        // container from calling `validate` method right after creating FormRequest class,
214
        // which uses `ValidatesWhenResolvedTrait`
215
        $modified = $property->getValue($container);
216
        $modified[ValidatesWhenResolved::class] = [];
217
        $property->setValue($container, $modified);
218
219
        // Then we get a new instance of FormRequest using our reflection,
220
        // so that it doesn't throw a ValidationException right away
221
        /** @var FormRequest $formRequest */
222
        $formRequest = $containerReflection->getMethod('make')->invoke($container, $this->className);
223
224
        // Just in case we set original value back
225
        $property->setValue($container, $originalValue);
226
227
        // Finally, we return created instance
228
        return $formRequest;
229
    }
230
}