Controller::behaviors()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 65
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 41
nc 8
nop 0
dl 0
loc 65
rs 9.264
c 0
b 0
f 0

How to fix   Long Method   

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
namespace yrc\rest;
4
5
use yii\rest\Controller as RestController;
6
use yii\filters\Cors;
7
use yii\filters\AccessControl;
8
use yii\filters\RateLimiter;
9
use yii\filters\VerbFilter;
10
use yii\filters\ContentNegotiator;
11
use yii\web\HttpException;
12
use yii\web\ForbiddenHttpException;
13
use yrc\web\Response;
14
15
use Yii;
16
17
use ReflectionClass;
18
use ReflectionMethod;
19
20
/**
21
 * Implements Restful API controller interfaces
22
 * @class Controller
23
 */
24
class Controller extends RestController
25
{
26
    /**
27
     * Allowed HTTP verbs
28
     * @var array $httpVerbs
29
     */
30
    private $httpVerbs = ['post', 'get', 'delete', 'put', 'patch', 'options', 'head'];
31
    
32
    /**
33
     * Global access filter
34
     */
35
    public function beforeAction($action)
36
    {
37
        $parent = parent::beforeAction($action);
38
39
        // Check the global access control header
40
        if (!Yii::$app->yrc->checkAccessHeader(Yii::$app->request)) {
0 ignored issues
show
Bug introduced by
The method checkAccessHeader() does not exist on null. ( Ignorable by Annotation )

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

40
        if (!Yii::$app->yrc->/** @scrutinizer ignore-call */ checkAccessHeader(Yii::$app->request)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
41
            throw new HttpException(401);
42
        }
43
44
        return $parent;
45
    }
46
47
    /**
48
     * RestController automatically applies HTTP verb filtering and CORS headers
49
     * @return array
50
     */
51
    public function behaviors()
52
    {
53
        $behaviors = parent::behaviors();
54
55
        $authenticator = false;
56
57
        if (isset($behaviors['authenticator'])) {
58
            $authenticator = $behaviors['authenticator'];
59
            unset($behaviors['authenticator']);
60
        }
61
62
        $behaviors['contentNegotiator'] = [
63
            'class' => ContentNegotiator::class,
64
            'formats' => [
65
                'application/json' => Response::FORMAT_JSON,
66
                'application/vnd.25519+json' => Response::FORMAT_JSON25519,
67
                'application/vnd.ncryptf+json' => Response::FORMAT_NCRYPTF_JSON,
68
                'application/xml' => Response::FORMAT_XML,
69
            ]
70
        ];
71
72
        $behaviors['corsFilter'] = [
73
            'class' => Cors::class,
74
            'cors' => [
75
                'Origin' => ['*'],
76
                'Access-Control-Request-Method' => $this->getHttpVerbMethodsFromClass($this->actions()[$this->action->id]),
77
                'Access-Control-Request-Headers' => ['*'],
78
                'Access-Control-Expose-Headers' => [
79
                    'Access-Control-Allow-Origin',
80
                    'X-Pagination-Per-Page',
81
                    'X-Pagination-Total-Count',
82
                    'X-Pagination-Current-Page',
83
                    'X-Pagination-Page-Count',
84
                    'Allow',
85
                    'X-Rate-Limit-Limit',
86
                    'X-Rate-Limit-Remaining',
87
                    'X-Rate-Limit-Reset'
88
                ],
89
            ]
90
        ];
91
92
        $behaviors['verbs'] = [
93
            'class' => VerbFilter::class,
94
            'actions' => $this->getVerbFilterActionMap()
95
        ];
96
97
        // Move authenticator after verbs and cors
98
        if ($authenticator != false) {
99
            $behaviors['authenticator'] = $authenticator;
100
        }
101
102
        $behaviors['rateLimiter'] = [
103
            'class' => RateLimiter::class,
104
            'enableRateLimitHeaders' => true
105
        ];
106
107
        $access = $this->getAccessControl();
108
109
        if ($access !== null) {
0 ignored issues
show
introduced by
The condition $access !== null is always true.
Loading history...
110
            $behaviors['access'] = $access;
111
        }
112
113
        // Manually add the ACAO header because Yii2 is terrible at doing it
114
        header("Access-Control-Allow-Origin: " . \implode(',', $behaviors['corsFilter']['cors']['Origin']));
115
        return $behaviors;
116
    }
117
118
    /**
119
     * Pulls the ACL list from the action
120
     * @return array
121
     */
122
    private function getAccessControl()
123
    {
124
        $access = [
125
            'class' => AccessControl::class,
126
            'denyCallback' => function ($rule, $action) {
0 ignored issues
show
Unused Code introduced by
The parameter $rule is not used and could be removed. ( Ignorable by Annotation )

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

126
            'denyCallback' => function (/** @scrutinizer ignore-unused */ $rule, $action) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $action is not used and could be removed. ( Ignorable by Annotation )

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

126
            'denyCallback' => function ($rule, /** @scrutinizer ignore-unused */ $action) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
127
                throw new ForbiddenHttpException(Yii::t('yrc', 'You do not have permission to access this resource'));
128
            }
129
        ];
130
131
        $acl = $this->action->acl;
132
        if ($acl === null) {
133
            return null;
134
        }
135
        
136
        foreach ($acl as $verb => $perms) {
137
            $access['rules'][] = [
138
                'allow' => true,
139
                'verbs' => [$verb],
140
                'roles' => $perms
141
            ];
142
        }
143
144
        // Allow HTTP Options
145
        $access['rules'][] = [
146
            'allow' => true,
147
            'verbs' => ['OPTIONS']
148
        ];
149
150
        return $access;
151
    }
152
153
    /**
154
     * Retrieves the HTTP verb list
155
     * @param string $class
156
     * @return array
157
     */
158
    private function getHttpVerbMethodsFromClass($class)
159
    {
160
        $result = [];
161
162
        if (is_array($class)) {
0 ignored issues
show
introduced by
The condition is_array($class) is always false.
Loading history...
163
            $class = $class['class'];
164
        }
165
166
        // Fetch the public methods for the class then filter them out by the http verbs
167
        $reflection = new ReflectionClass($class);
168
        $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
169
        foreach ($methods as $method) {
170
            if (\in_array($method->name, $this->httpVerbs)) {
171
                $result[] = $method->name;
172
            }
173
        }
174
175
        return $result;
176
    }
177
178
    /**
179
     * Convers self::actions() for automatic verb filtering
180
     * @return array
181
     */
182
    private function getVerbFilterActionMap()
183
    {
184
        $actions = $this->actions();
185
186
        // Only apply this filtering for ActionMapped Controllers
187
        if (empty($actions)) {
188
            return [];
189
        }
190
191
        $actionMap = [];
192
        
193
        // Iterate over all the actions, and automatically determine the methods implemented
194
        foreach ($actions as $actionName => $params) {
195
            static $class = null;
196
            if (is_array($params)) {
197
                $class = $params['class'];
198
            } else {
199
                $class = $params;
200
            }
201
202
            $actionMap[$actionName] = $this->getHttpVerbMethodsFromClass($class);
203
        }
204
205
        return $actionMap;
206
    }
207
}
208