Test Failed
Push — 22.0 ( 52343a...94f44f )
by Alexander
18:24 queued 12:51
created

Controller::bindActionParams()   C

Complexity

Conditions 16
Paths 52

Size

Total Lines 66
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 16.1479

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 46
nc 52
nop 2
dl 0
loc 66
ccs 44
cts 48
cp 0.9167
crap 16.1479
rs 5.5666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\Exception;
12
use yii\base\InlineAction;
13
use yii\helpers\Url;
14
15
/**
16
 * Controller is the base class of web controllers.
17
 *
18
 * For more details and usage information on Controller, see the [guide article on controllers](guide:structure-controllers).
19
 *
20
 * @property Request $request The request object.
21
 * @property Response $response The response object.
22
 * @property View $view The view object that can be used to render views or view files.
23
 *
24
 * @author Qiang Xue <[email protected]>
25
 * @since 2.0
26
 */
27
class Controller extends \yii\base\Controller
28
{
29
    /**
30
     * @var bool whether to enable CSRF validation for the actions in this controller.
31
     * CSRF validation is enabled only when both this property and [[\yii\web\Request::enableCsrfValidation]] are true.
32
     */
33
    public $enableCsrfValidation = true;
34
    /**
35
     * @var array the parameters bound to the current action.
36
     */
37
    public $actionParams = [];
38
39
40
    /**
41
     * Renders a view in response to an AJAX request.
42
     *
43
     * This method is similar to [[renderPartial()]] except that it will inject into
44
     * the rendering result with JS/CSS scripts and files which are registered with the view.
45
     * For this reason, you should use this method instead of [[renderPartial()]] to render
46
     * a view to respond to an AJAX request.
47
     *
48
     * @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
49
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
50
     * @return string the rendering result.
51
     */
52
    public function renderAjax($view, $params = [])
53
    {
54
        return $this->getView()->renderAjax($view, $params, $this);
0 ignored issues
show
Bug introduced by
The method renderAjax() does not exist on yii\base\View. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
        return $this->getView()->/** @scrutinizer ignore-call */ renderAjax($view, $params, $this);
Loading history...
55
    }
56
57
    /**
58
     * Send data formatted as JSON.
59
     *
60
     * This method is a shortcut for sending data formatted as JSON. It will return
61
     * the [[Application::getResponse()|response]] application component after configuring
62
     * the [[Response::$format|format]] and setting the [[Response::$data|data]] that should
63
     * be formatted. A common usage will be:
64
     *
65
     * ```php
66
     * return $this->asJson($data);
67
     * ```
68
     *
69
     * @param mixed $data the data that should be formatted.
70
     * @return Response a response that is configured to send `$data` formatted as JSON.
71
     * @since 2.0.11
72
     * @see Response::$format
73
     * @see Response::FORMAT_JSON
74
     * @see JsonResponseFormatter
75
     */
76 1
    public function asJson($data)
77
    {
78 1
        $this->response->format = Response::FORMAT_JSON;
79 1
        $this->response->data = $data;
80 1
        return $this->response;
81
    }
82
83
    /**
84
     * Send data formatted as XML.
85
     *
86
     * This method is a shortcut for sending data formatted as XML. It will return
87
     * the [[Application::getResponse()|response]] application component after configuring
88
     * the [[Response::$format|format]] and setting the [[Response::$data|data]] that should
89
     * be formatted. A common usage will be:
90
     *
91
     * ```php
92
     * return $this->asXml($data);
93
     * ```
94
     *
95
     * @param mixed $data the data that should be formatted.
96
     * @return Response a response that is configured to send `$data` formatted as XML.
97
     * @since 2.0.11
98
     * @see Response::$format
99
     * @see Response::FORMAT_XML
100
     * @see XmlResponseFormatter
101
     */
102 1
    public function asXml($data)
103
    {
104 1
        $this->response->format = Response::FORMAT_XML;
105 1
        $this->response->data = $data;
106 1
        return $this->response;
107
    }
108
109
    /**
110
     * Binds the parameters to the action.
111
     * This method is invoked by [[\yii\base\Action]] when it begins to run with the given parameters.
112
     * This method will check the parameter names that the action requires and return
113
     * the provided parameters according to the requirement. If there is any missing parameter,
114
     * an exception will be thrown.
115
     * @param \yii\base\Action $action the action to be bound with parameters
116
     * @param array $params the parameters to be bound to the action
117
     * @return array the valid parameters that the action can run with.
118
     * @throws BadRequestHttpException if there are missing or invalid parameters.
119
     */
120 91
    public function bindActionParams($action, $params)
121
    {
122 91
        if ($action instanceof InlineAction) {
123 77
            $method = new \ReflectionMethod($this, $action->actionMethod);
124
        } else {
125 14
            $method = new \ReflectionMethod($action, 'run');
126
        }
127
128 91
        $args = [];
129 91
        $missing = [];
130 91
        $actionParams = [];
131 91
        $requestedParams = [];
132 91
        foreach ($method->getParameters() as $param) {
133 9
            $name = $param->getName();
134 9
            if (array_key_exists($name, $params)) {
135 6
                $isValid = true;
136 6
                $type = $param->getType();
137 6
                if ($type instanceof \ReflectionNamedType) {
138
                    [$result, $isValid] = $this->filterSingleTypeActionParam($params[$name], $type);
139 6
                    $params[$name] = $result;
140
                } elseif ($type instanceof \ReflectionUnionType) {
141
                    [$result, $isValid] = $this->filterUnionTypeActionParam($params[$name], $type);
142 6
                    $params[$name] = $result;
143 6
                }
144 6
145 6
                if (!$isValid) {
146 6
                    throw new BadRequestHttpException(
147
                        Yii::t('yii', 'Invalid data received for parameter "{param}".', ['param' => $name])
148 1
                    );
149
                }
150 1
                $args[] = $actionParams[$name] = $params[$name];
151 1
                unset($params[$name]);
152 1
            } elseif (
153
                PHP_VERSION_ID >= 70100
154
                && ($type = $param->getType()) !== null
155
                && $type instanceof \ReflectionNamedType
156 1
                && !$type->isBuiltin()
157 1
            ) {
158 1
                try {
159 1
                    $this->bindInjectedParams($type, $name, $args, $requestedParams);
160
                } catch (HttpException $e) {
161
                    throw $e;
162 1
                } catch (Exception $e) {
163 1
                    throw new ServerErrorHttpException($e->getMessage(), 0, $e);
164 1
                }
165
            } elseif ($param->isDefaultValueAvailable()) {
166 1
                $args[] = $actionParams[$name] = $param->getDefaultValue();
167 1
            } else {
168
                $missing[] = $name;
169
            }
170
        }
171 6
172 1
        if (!empty($missing)) {
173 1
            throw new BadRequestHttpException(
174 1
                Yii::t('yii', 'Missing required parameters: {params}', ['params' => implode(', ', $missing)])
175
            );
176 6
        }
177 6
178
        $this->actionParams = $actionParams;
179 7
180 7
        // We use a different array here, specifically one that doesn't contain service instances but descriptions instead.
181 7
        if (Yii::$app->requestedParams === null) {
182 7
            Yii::$app->requestedParams = array_merge($actionParams, $requestedParams);
183
        }
184
185 6
        return $args;
186 3
    }
187 1
188 2
    /**
189 5
     * The logic for [[bindActionParam]] to validate whether a given parameter matches the action's typing
190
     * if the function parameter has a single named type.
191 1
     * @param mixed $param The parameter value.
192 1
     * @param \ReflectionNamedType $type
193
     * @return array{0: mixed, 1: bool} The resulting parameter value and a boolean indicating whether the value is valid.
194
     */
195
    private function filterSingleTypeActionParam($param, $type)
196
    {
197
        $isArray = $type->getName() === 'array';
198 88
        if ($isArray) {
199
            return [(array)$param, true];
200
        }
201
202
        if (is_array($param)) {
203
            return [$param, false];
204 88
        }
205
206
        if (
207 88
            PHP_VERSION_ID >= 70000
208 88
            && method_exists($type, 'isBuiltin')
209
            && $type->isBuiltin()
210
            && ($param !== null || !$type->allowsNull())
211 88
        ) {
212
            $typeName = PHP_VERSION_ID >= 70100 ? $type->getName() : (string)$type;
213
            if ($param === '' && $type->allowsNull()) {
214
                if ($typeName !== 'string') { // for old string behavior compatibility
215
                    return [null, true];
216
                }
217 83
                return ['', true];
218
            }
219 83
220 77
            if ($typeName === 'string') {
221
                return [$param, true];
222
            }
223
            $filterResult = $this->filterParamByType($param, $typeName);
224 77
            return [$filterResult, $filterResult !== null];
225
        }
226
        return [$param, true];
227
    }
228
229
    /**
230
     * The logic for [[bindActionParam]] to validate whether a given parameter matches the action's typing
231
     * if the function parameter has a union type.
232
     * @param mixed $param The parameter value.
233
     * @param \ReflectionUnionType $type
234
     * @return array{0: mixed, 1: bool} The resulting parameter value and a boolean indicating whether the value is valid.
235
     */
236
    private function filterUnionTypeActionParam($param, $type)
237
    {
238
        $types = $type->getTypes();
239
        if ($param === '' && $type->allowsNull()) {
240
            // check if type can be string for old string behavior compatibility
241
            foreach ($types as $partialType) {
242
                if (
243
                    $partialType === null
244
                    || !method_exists($partialType, 'isBuiltin')
245
                    || !$partialType->isBuiltin()
246
                ) {
247
                    continue;
248
                }
249
                $typeName = PHP_VERSION_ID >= 70100 ? $partialType->getName() : (string)$partialType;
250
                if ($typeName === 'string') {
251
                    return ['', true];
252
                }
253
            }
254
            return [null, true];
255
        }
256 1
        // if we found a built-in type but didn't return out, its validation failed
257
        $foundBuiltinType = false;
258
        // we save returning out an array or string for later because other types should take precedence
259 1
        $canBeArray = false;
260
        $canBeString = false;
261
        foreach ($types as $partialType) {
262
            if (
263
                $partialType === null
264
                || !method_exists($partialType, 'isBuiltin')
265
                || !$partialType->isBuiltin()
266
            ) {
267
                continue;
268
            }
269
            $foundBuiltinType = true;
270
            $typeName = PHP_VERSION_ID >= 70100 ? $partialType->getName() : (string)$partialType;
271
            $canBeArray |= $typeName === 'array';
272
            $canBeString |= $typeName === 'string';
273
            if (is_array($param)) {
274
                if ($canBeArray) {
275
                    break;
276
                }
277
                continue;
278
            }
279
280
            $filterResult = $this->filterParamByType($param, $typeName);
281
            if ($filterResult !== null) {
282
                return [$filterResult, true];
283
            }
284
        }
285
        if (!is_array($param) && $canBeString) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $canBeString of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
286
            return [$param, true];
287
        }
288
        if ($canBeArray) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $canBeArray of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
289
            return [(array)$param, true];
290
        }
291
        return [$param, $canBeString || !$foundBuiltinType];
0 ignored issues
show
Bug Best Practice introduced by
The expression $canBeString of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
292
    }
293
294
    /**
295
     * Run the according filter_var logic for teh given type.
296
     * @param string $param The value to filter.
297
     * @param string $typeName The type name.
298
     * @return mixed|null The resulting value, or null if validation failed or the type can't be validated.
299
     */
300
    private function filterParamByType(string $param, string $typeName)
301
    {
302
        switch ($typeName) {
303
            case 'int':
304
                return filter_var($param, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
305
            case 'float':
306
                return filter_var($param, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
307
            case 'bool':
308
                return filter_var($param, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
309
        }
310
        return null;
311
    }
312
313
    /**
314
     * {@inheritdoc}
315
     */
316
    public function beforeAction($action)
317
    {
318
        if (parent::beforeAction($action)) {
319
            if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !$this->request->validateCsrfToken()) {
320
                throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
321
            }
322
323
            return true;
324
        }
325
326
        return false;
327
    }
328
329
    /**
330
     * Redirects the browser to the specified URL.
331
     * This method is a shortcut to [[Response::redirect()]].
332
     *
333
     * You can use it in an action by returning the [[Response]] directly:
334
     *
335
     * ```php
336
     * // stop executing this action and redirect to login page
337
     * return $this->redirect(['login']);
338
     * ```
339
     *
340
     * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
341
     *
342
     * - a string representing a URL (e.g. "https://example.com")
343
     * - a string representing a URL alias (e.g. "@example.com")
344
     * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`)
345
     *   [[Url::to()]] will be used to convert the array into a URL.
346
     *
347
     * Any relative URL that starts with a single forward slash "/" will be converted
348
     * into an absolute one by prepending it with the host info of the current request.
349
     *
350
     * @param int $statusCode the HTTP status code. Defaults to 302.
351
     * See <https://tools.ietf.org/html/rfc2616#section-10>
352
     * for details about HTTP status code
353
     * @return Response the current response object
354
     */
355
    public function redirect($url, $statusCode = 302)
356
    {
357
        // calling Url::to() here because Response::redirect() modifies route before calling Url::to()
358
        return $this->response->redirect(Url::to($url), $statusCode);
359
    }
360
361
    /**
362
     * Redirects the browser to the home page.
363
     *
364
     * You can use this method in an action by returning the [[Response]] directly:
365
     *
366
     * ```php
367
     * // stop executing this action and redirect to home page
368
     * return $this->goHome();
369
     * ```
370
     *
371
     * @return Response the current response object
372
     */
373
    public function goHome()
374
    {
375
        return $this->response->redirect(Yii::$app->getHomeUrl());
0 ignored issues
show
Bug introduced by
The method getHomeUrl() does not exist on yii\console\Application. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

375
        return $this->response->redirect(Yii::$app->/** @scrutinizer ignore-call */ getHomeUrl());
Loading history...
376
    }
377
378
    /**
379
     * Redirects the browser to the last visited page.
380
     *
381
     * You can use this method in an action by returning the [[Response]] directly:
382
     *
383
     * ```php
384
     * // stop executing this action and redirect to last visited page
385
     * return $this->goBack();
386
     * ```
387
     *
388
     * For this function to work you have to [[User::setReturnUrl()|set the return URL]] in appropriate places before.
389
     *
390
     * @param string|array|null $defaultUrl the default return URL in case it was not set previously.
391
     * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
392
     * Please refer to [[User::setReturnUrl()]] on accepted format of the URL.
393
     * @return Response the current response object
394
     * @see User::getReturnUrl()
395
     */
396
    public function goBack($defaultUrl = null)
397
    {
398
        return $this->response->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl));
0 ignored issues
show
Bug introduced by
The method getUser() does not exist on yii\console\Application. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

398
        return $this->response->redirect(Yii::$app->/** @scrutinizer ignore-call */ getUser()->getReturnUrl($defaultUrl));
Loading history...
399
    }
400
401
    /**
402
     * Refreshes the current page.
403
     * This method is a shortcut to [[Response::refresh()]].
404
     *
405
     * You can use it in an action by returning the [[Response]] directly:
406
     *
407
     * ```php
408
     * // stop executing this action and refresh the current page
409
     * return $this->refresh();
410
     * ```
411
     *
412
     * @param string $anchor the anchor that should be appended to the redirection URL.
413
     * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
414
     * @return Response the response object itself
415
     */
416
    public function refresh($anchor = '')
417
    {
418
        return $this->response->redirect($this->request->getUrl() . $anchor);
419
    }
420
}
421