Mapper   C
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 385
Duplicated Lines 4.16 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 16
loc 385
rs 6.018
c 0
b 0
f 0
wmc 61
lcom 1
cbo 5

13 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 4 24 7
A getMapper() 0 4 1
C getIdObjectIfUser() 8 32 7
D getIdObjectIfAdmin() 4 35 9
B get() 0 14 7
A put() 0 14 3
A post() 0 14 2
A patch() 0 14 3
B delete() 0 20 6
A listingAdmin() 0 11 2
B listing() 0 16 6
A searchAdmin() 0 11 2
B search() 0 16 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Mapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FFCMS\Controllers\API;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Traits, Models, Mappers};
7
8
9
/**
10
 * REST API Base Controller Class.
11
 *
12
 * @author Vijay Mahrra <[email protected]>
13
 * @copyright Vijay Mahrra
14
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
15
 */
16
abstract class Mapper extends API
17
{
18
    use Traits\Validation,
19
        Traits\SearchController;
20
21
    /**
22
     * For admin listing and search results
23
     */
24
    use Traits\SearchController;
25
26
    /**
27
     * @var object database class
28
     */
29
    protected $db;
30
31
    /**
32
     * @var string table in the db
33
     */
34
    protected $table = null;
35
36
    /**
37
     * @var string class name
38
     */
39
    protected $mapperClass;
40
41
    /**
42
     * @var \FFMVC\Mappers\Mapper mapper for class
43
     */
44
    protected $mapper;
45
46
    /**
47
     * is the user authorised for access?
48
     *
49
     * @var bool
50
     */
51
    protected $isAuthorised = false;
52
53
    /**
54
     * is the user authorised for access to the object type?
55
     *
56
     * @var bool
57
     */
58
    protected $adminOnly = true;
59
60
61
    /**
62
     * initialize
63
     * @param \Base $f3
64
     */
65
    public function __construct(\Base $f3)
66
    {
67
        parent::__construct();
68
69
        // guess the table name from the class name if not specified as a class member
70
        $class = \UTF::instance()->substr(strrchr(get_class($this), '\\'),1);
71
        $this->table = empty($this->table) ? $f3->snakecase($class) : $this->table;
72
        $mapperClass = "\FFCMS\Mappers\\" . $class;
73
74
        if (class_exists($mapperClass) && $this instanceof Mapper) {
75
            $this->mapperClass = $mapperClass;
76
            $this->mapper = new $this->mapperClass;
77
        }
78
79
        $this->isAuthorised = $this->validateAccess();
80
        if (empty($this->isAuthorised)) {
81
            $this->setOAuthError('invalid_grant');
82 View Code Duplication
        } elseif (empty($f3->get('isAdmin')) && !empty($this->adminOnly)) {
83
            $this->failure('authentication_error', "User does not have permission.", 401);
84
            $this->setOAuthError('access_denied');
85
        } else {
86
            $this->isAuthorised = true;
87
        }
88
    }
89
90
91
    /**
92
     * Get the associated mapper for the table
93
     *
94
     * @return
95
     */
96
    public function &getMapper()
97
    {
98
        return $this->mapper;
99
    }
100
101
102
    /**
103
     * Check permissions and load the mapper with the object in the URL param @id
104
     * for the user
105
     *
106
     * @param \Base $f3
107
     * @param array $params
108
     * @param string $idField the field used for the unique id to load by
109
     * @param string|null $defaultId defaule value to use if not found
110
     * @return null|array|boolean|\FFMVC\Mappers\Mapper
111
     */
112
    public function getIdObjectIfUser(\Base $f3, array $params, string $idField = 'uuid', $defaultId = null)
113
    {
114
        // valid user?
115
        if (empty($this->isAuthorised)) {
116
            return;
117
        }
118
119
        // only admin has permission to specify @id param
120
        $isAdmin = $f3->get('isAdmin');
121
        $id = !empty($params['id']) ? $params['id'] : $f3->get('REQUEST.id');
122
123 View Code Duplication
        if ((!$isAdmin && !empty($this->adminOnly))) {
124
            $this->failure('authentication_error', "User does not have permission.", 401);
125
            return $this->setOAuthError('access_denied');
126
        }
127
128
        // use default user id
129
        if (empty($id)) {
130
            $id = $defaultId;
131
        }
132
133
        // load object by correct id
134
        $m = $this->getMapper();
135
        $m->load([$m->quotekey($idField) . ' = ?', $id]);
136 View Code Duplication
        if (null == $m->$idField) {
137
            $this->failure('authentication_error', "Object with @id does not exist.", 404);
138
            return $this->setOAuthError('invalid_request');
139
        }
140
141
        $this->mapper =& $m;
142
        return $m;
143
    }
144
145
146
    /**
147
     * Check permissions and load the mapper with the object in the URL param @id
148
     * if the user is an admin
149
     *
150
     * @param \Base $f3
151
     * @param array $params
152
     * @param string $idField the field used for the unique id to load by
153
     * @param string|null $defaultId defaule value to use if not found
154
     * @return null|array|boolean|\FFMVC\Mappers\Mapper
155
     */
156
    public function getIdObjectIfAdmin(\Base $f3, array $params, string $idField = 'uuid', $defaultId = null)
157
    {
158
        if (empty($this->isAuthorised)) {
159
            return;
160
        }
161
162
        // only admin has permission to delete @id
163
        $isAdmin = $f3->get('isAdmin');
164
        if (!$isAdmin) {
165
            $this->failure('authentication_error', "User does not have permission.", 401);
166
            return $this->setOAuthError('access_denied');
167
        }
168
169
        // invalid id
170
        $id = !empty($params['id']) ? $params['id'] : $f3->get('REQUEST.id');
171
        if (empty($id)) {
172
            $id = $defaultId;
173
        }
174
175
        if (!empty($id) && ('uuid' == $idField && 8 !== strlen($id))) {
176
            $this->failure('authentication_error', "Invalid @id parameter.", 400);
177
            return $this->setOAuthError('invalid_request');
178
        }
179
180
        // check id exists
181
        $m = $this->getMapper();
182
        $m->load([$m->quotekey($idField) . ' = ?', $id]);
183 View Code Duplication
        if (null == $m->$idField) {
184
            $this->failure('authentication_error', "Object with @id does not exist.", 404);
185
            return $this->setOAuthError('invalid_request');
186
        }
187
188
        $this->mapper =& $m;
189
        return $m;
190
    }
191
192
193
    /**
194
     * Display the data item
195
     *
196
     * @param \Base $f3
197
     * @param array $params
198
     * @return null|array|boolean
199
     */
200
    public function get(\Base $f3, array $params)
201
    {
202
        $isAdmin = $f3->get('isAdmin');
203
        $m = $this->getIdObjectIfUser($f3, $params, 'uuid', $params['id']);
204
        if (!is_object($m) || null == $m->uuid) {
205
            return;
206
        } elseif (!$isAdmin && $m->users_uuid !== $f3->get('uuid')) {
207
            $this->failure('authentication_error', "User does not have permission.", 401);
208
            return $this->setOAuthError('access_denied');
209
        }
210
        // return raw data for object?
211
        $adminView = $f3->get('isAdmin') && 'admin' == $f3->get('REQUEST.view');
212
        $this->data = $adminView ? $m->castFields($f3->get('REQUEST.fields')) : $m->exportArray($f3->get('REQUEST.fields'));
213
    }
214
215
216
    /**
217
     * Replace data
218
     *
219
     * @param \Base $f3
220
     * @param array $params
221
     * @return null|array|boolean
222
     */
223
    public function put(\Base $f3, array $params)
224
    {
225
        $m = $this->getIdObjectIfAdmin($f3, $params, 'uuid', $params['id']);
226
        if (!is_object($m) || null == $m->uuid) {
227
            return;
228
        }
229
230
        $f3->set('REQUEST.uuid', $m->uuid);
231
232
        // these fields can't be modified
233
        return $this->save($f3, [
234
            'id'
235
        ]);
236
    }
237
238
239
    /**
240
     * Create new data
241
     *
242
     * @param \Base $f3
243
     * @return null|array|boolean
244
     */
245
    public function post(\Base $f3)
246
    {
247
        $isAdmin = $f3->get('isAdmin');
248
        if (!$isAdmin) {
249
            return;
250
        }
251
252
        // this fields can't be modified
253
        $prohibitedFields = [
254
            'id', 'uuid'
255
        ];
256
257
        return $this->save($f3, $prohibitedFields);
258
    }
259
260
261
    /**
262
     * Update data
263
     *
264
     * @param \Base $f3
265
     * @param array $params
266
     * @return null|array|boolean
267
     */
268
    public function patch(\Base $f3, array $params)
269
    {
270
        $m = $this->getIdObjectIfAdmin($f3, $params, 'uuid', $params['id']);
271
        if (!is_object($m) || null == $m->uuid) {
272
            return;
273
        }
274
275
        $f3->set('REQUEST.uuid', $m->uuid);
276
277
        // these fields can't be modified
278
        return $this->save($f3, [
279
            'id', 'uuid'
280
        ]);
281
    }
282
283
284
    /**
285
     * Delete the data object indicated by @id in the request
286
     *
287
     * @param \Base $f3
288
     * @param array $params
289
     * @return null|array|boolean
290
     */
291
    public function delete(\Base $f3, array $params)
292
    {
293
        $m = $this->getIdObjectIfUser($f3, $params);
294
        if (!is_object($m) || null == $m->uuid) {
295
            return;
296
        }
297
298
        // a user can only delete if they own the object
299
        $isAdmin = $f3->get('isAdmin');
300
        if (!$isAdmin) {
301
            if (!empty($m->users_uuid) && $m->users_uuid !== $f3->get('uuid')) {
302
                $this->failure('authentication_error', "User does not have permission.", 401);
303
                return $this->setOAuthError('access_denied');
304
            }
305
        }
306
307
        $this->data = [
308
            'deleted' => $m->erase()
309
        ];
310
    }
311
312
313
    /**
314
     * list objects (list is a reserved keyword)
315
     *
316
     * @param \Base $f3
317
     * @return null|array|boolean
318
     */
319
    public function listingAdmin(\Base $f3)
320
    {
321
        // must be an admin
322
        $isAdmin = $f3->get('isAdmin');
323
        if (!$isAdmin) {
324
            $this->failure('authentication_error', "User does not have permission.", 401);
325
            return $this->setOAuthError('access_denied');
326
        }
327
328
        $this->data = $this->getListingResults($f3, $this->getMapper());
329
    }
330
331
332
    /**
333
     * list objects (list is a reserved keyword)
334
     *
335
     * @param \Base $f3
336
     * @param array $params
337
     * @return array|boolean|null
338
     */
339
    public function listing(\Base $f3, array $params)
340
    {
341
        $isAdmin = $f3->get('isAdmin');
342
        if (!$isAdmin && array_key_exists('id', $params)) {
343
            $this->failure('authentication_error', "User does not have permission.", 401);
344
            return $this->setOAuthError('access_denied');
345
        } elseif ($isAdmin && array_key_exists('id', $params)) {
346
            $users_uuid = $params['id'];
347
        } elseif (!$isAdmin) {
348
            $users_uuid = $f3->get('uuid');
349
        } else {
350
            $users_uuid = null;
351
        }
352
353
        $this->data = $this->getListingResults($f3, $this->getMapper(), $users_uuid);
354
    }
355
356
357
    /**
358
     * search objects
359
     *
360
     * @param \Base $f3
361
     * @return null|array|boolean
362
     */
363
    public function searchAdmin(\Base $f3)
364
    {
365
        // must be an admin
366
        $isAdmin = $f3->get('isAdmin');
367
        if (!$isAdmin) {
368
            $this->failure('authentication_error', "User does not have permission.", 401);
369
            return $this->setOAuthError('access_denied');
370
        }
371
372
        $this->data = $this->getSearchResults($f3, $this->getMapper());
373
    }
374
375
376
    /**
377
     * search objects
378
     *
379
     * @param \Base $f3
380
     * @param array $params
381
     * @return null|array|boolean
382
     */
383
    public function search(\Base $f3, array $params)
384
    {
385
        $isAdmin = $f3->get('isAdmin');
386
        if (!$isAdmin && array_key_exists('id', $params)) {
387
            $this->failure('authentication_error', "User does not have permission.", 401);
388
            return $this->setOAuthError('access_denied');
389
        } elseif ($isAdmin && array_key_exists('id', $params)) {
390
            $users_uuid = $params['id'];
391
        } elseif (!$isAdmin) {
392
            $users_uuid = $f3->get('uuid');
393
        } else {
394
            $users_uuid = null;
395
        }
396
397
        $this->data = $this->getSearchResults($f3, $this->getMapper(), $users_uuid);
398
    }
399
400
}
401