RouteInfo::getUri()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 8
ccs 0
cts 4
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php
2
3
namespace Loadsman\LaravelPlugin\Entities;
4
5
use Illuminate\Contracts\Support\Arrayable;
6
use Illuminate\Foundation\Http\FormRequest;
7
use JsonSerializable;
8
9
/**
10
 * Frontend ready route
11
 */
12
class RouteInfo implements Arrayable, JsonSerializable
13
{
14
    /**
15
     * @var \ReflectionClass|null
16
     */
17
    protected $routeReflection = null;
18
19
    /**
20
     * @var \ReflectionFunctionAbstract|null
21
     */
22
    protected $actionReflection = null;
23
24
    /**
25
     * @var array
26
     */
27
    protected $options = [];
28
29
    /**
30
     * @var array
31
     */
32
    protected $errors = [];
33
34
    /**
35
     * @var bool
36
     */
37
    protected $addMeta;
38
39
    /**
40
     * @var \Illuminate\Routing\Route
41
     */
42
    private $route;
43
44
    public function __construct($route, $options = [])
45
    {
46
        $this->route = $route;
47
        $this->options = $options;
48
        $this->addMeta = config('api-tester.route_meta');
49
    }
50
51
    /**
52
     * @return array
53
     */
54
    public function toArray()
55
    {
56
        return array_merge([
57
            'name'    => $this->route->getName(),
58
            'methods' => $this->getMethods(),
59
            'domain'  => $this->route->domain(),
60
            'path'    => $this->preparePath(),
61
            'action'  => $this->route->getAction(),
62
            'wheres'  => $this->extractWheres(),
63
            'errors'  => $this->errors,
64
        ], $this->getMeta(), $this->options);
65
    }
66
67
    /**
68
     * Cross version get methods.
69
     *
70
     * @return array
71
     */
72
    private function getMethods()
73
    {
74
        $isOldLaravel = method_exists($this->route, 'getMethods');
75
        // < 5.4 has `getMethods', 5.4+ has methods().
76
        $methodName = $isOldLaravel ? 'getMethods' : 'methods';
77
78
        return $this->route->$methodName();
79
    }
80
81
    protected function extractWheres()
82
    {
83
        $prop = $this->getRouteReflection()->getProperty('wheres');
84
        $prop->setAccessible(true);
85
86
        $wheres = $prop->getValue($this->route);
87
88
        // Хак, чтобы в json всегда был объект
89
        if (empty($wheres)) {
90
            return (object) [];
91
        }
92
93
        return $wheres;
94
    }
95
96
    /**
97
     * @return array
98
     */
99
    function jsonSerialize()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
100
    {
101
        return $this->toArray();
102
    }
103
104
    /**
105
     * @return string
106
     */
107
    protected function extractDocBlocks()
108
    {
109
        $reflection = $this->getActionReflection();
110
111
        if (! is_null($reflection)) {
112
            return $reflection->getDocComment();
113
        }
114
115
        return '';
116
    }
117
118
    protected function extractFormRequest()
119
    {
120
        $reflection = $this->getActionReflection();
121
122
        if (is_null($reflection)) {
123
            return null;
124
        }
125
126
        foreach ($reflection->getParameters() as $parameter) {
127
128
            // If first argument is untyped Laravel DI won't check any other
129
            // arguments. So we will take a break as well.
130
            try {
131
                $class = $parameter->getClass();
132
            } catch (\ReflectionException $e) {
133
                break;
134
            }
135
136
            if (is_null($class)) {
137
                break;
138
            }
139
140
            // We don't care if argument is anything but FormRequest
141
            if (! is_subclass_of($class->name, FormRequest::class)) {
142
                continue;
143
            }
144
145
            // To trigger non static method on object we have to instantiate it.
146
            // We will `build` instead of `make` so the validation won't be triggered.
147
            $formRequest = app()->build($class->name);
148
149
            // Next we use `call` to resolve dependencies.
150
            $rules = app()->call([$formRequest, 'rules']);
151
152
            return [
153
                'class' => $class->name,
154
                'rules' => $rules,
155
            ];
156
        }
157
158
        return null;
159
    }
160
161
    /**
162
     * @return \ReflectionClass
163
     */
164
    protected function getRouteReflection()
165
    {
166
        if ($this->routeReflection) {
167
            return $this->routeReflection;
168
        }
169
170
        return $this->routeReflection = new \ReflectionClass($this->route);
171
    }
172
173
    /**
174
     * @return \ReflectionFunctionAbstract|null
175
     */
176
    protected function getActionReflection()
177
    {
178
        if ($this->actionReflection) {
179
            return $this->actionReflection;
180
        }
181
182
        $uses = $this->route->getAction()['uses'];
183
184
        // `uses` is string and contains '@'
185
        // means we're looking at controller@method
186
        if (is_string($uses) && str_contains($uses, '@')) {
187
            list($controller, $action) = explode('@', $uses);
188
189
            // Controller is missing.
190
            if (! class_exists($controller)) {
191
                $this->setError('uses', 'controller does not exists');
192
193
                return null;
194
            }
195
196
            // Action is missing.
197
            if (! method_exists($controller, $action)) {
198
                $this->setError('uses', 'controller@method does not exists');
199
200
                return null;
201
            }
202
203
            return $this->actionReflection = new \ReflectionMethod($controller,
204
                $action);
205
        }
206
207
        if (is_callable($uses)) {
208
            return $this->actionReflection = new \ReflectionFunction($uses);
209
        }
210
211
        $this->setError('uses', 'route uses is not valid');
212
213
        return null;
214
    }
215
216
    protected function preparePath()
217
    {
218
        $path = $this->getUri();
219
        if ($path === '/') {
220
            return $path;
221
        }
222
223
        return trim($path, '/');
224
    }
225
226
    /**
227
     * Backwards compatible uri getter.
228
     *
229
     * @return string
230
     */
231
    protected function getUri()
232
    {
233
        $isOldLaravel = method_exists($this->route, 'getPath');
234
        // <5.4 got `getPath`, 5.4+ got `uri`.
235
        $methodName = $isOldLaravel ? 'getPath' : 'uri';
236
237
        return $this->route->$methodName();
238
    }
239
240
    protected function setError($type, $text, $params = [])
241
    {
242
        $this->errors[$type] = trans($text, $params);
243
    }
244
245
    /**
246
     * @return array
247
     */
248
    protected function getMeta()
249
    {
250
        if ($this->addMeta) {
251
            return [
252
                'annotation'  => $this->extractDocBlocks(),
253
                'formRequest' => $this->extractFormRequest(),
254
                'errors'      => $this->errors,
255
            ];
256
        }
257
258
        return [];
259
    }
260
}
261