Completed
Push — dev-master ( 8c1809...065bc3 )
by Vijay
03:22
created

Mapper   C

Complexity

Total Complexity 62

Size/Duplication

Total Lines 394
Duplicated Lines 19.29 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 76
loc 394
rs 5.9493
c 0
b 0
f 0
wmc 62
lcom 1
cbo 6

13 Methods

Rating   Name   Duplication   Size   Complexity  
D getIdObjectIfAdmin() 4 36 9
B get() 14 14 7
C __construct() 0 31 8
A getMapper() 0 4 1
C getIdObjectIfUser() 4 33 7
A put() 0 14 3
A post() 0 14 2
A patch() 0 14 3
B delete() 0 20 6
A listingAdmin() 11 11 2
B listing() 16 16 6
A searchAdmin() 11 11 2
B search() 16 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($f3);
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
        }
83
84
        $deny = false;
85
        $isAdmin = $f3->get('isAdmin');
86
        if (!$isAdmin && !empty($this->adminOnly)) {
87
            $deny = true;
88
        }
89
90
        $this->isAuthorised = empty($deny);
91
        if ($deny) {
92
            $this->failure('authentication_error', "User does not have permission.", 401);
93
            $this->setOAuthError('access_denied');
94
        }
95
    }
96
97
98
    /**
99
     * Get the associated mapper for the table
100
     *
101
     * @return
102
     */
103
    public function &getMapper()
104
    {
105
        return $this->mapper;
106
    }
107
108
109
    /**
110
     * Check permissions and load the mapper with the object in the URL param @id
111
     * for the user
112
     *
113
     * @param \Base $f3
114
     * @param array $params
115
     * @param string $idField the field used for the unique id to load by
116
     * @param string|null $defaultId defaule value to use if not found
117
     * @return null|array|boolean|\FFMVC\Mappers\Mapper
118
     */
119
    public function getIdObjectIfUser(\Base $f3, array $params, string $idField = 'uuid', $defaultId = null)
120
    {
121
        // valid user?
122
        if (empty($this->isAuthorised)) {
123
            return;
124
        }
125
126
        // only admin has permission to specify @id param
127
        $isAdmin = $f3->get('isAdmin');
128
        $id = !empty($params['id']) ? $params['id'] : $f3->get('REQUEST.id');
129
130
        if ((!$isAdmin && !empty($this->adminOnly))) {
131
            $this->failure('authentication_error', "User does not have permission.", 401);
132
            return $this->setOAuthError('access_denied');
133
        }
134
135
        // use default user id
136
        if (empty($id)) {
137
            $id = $defaultId;
138
        }
139
140
        // load object by correct id
141
        $db = \Registry::get('db');
142
        $m = $this->getMapper();
143
        $m->load([$db->quotekey($idField) . ' = ?', $id]);
144 View Code Duplication
        if (null == $m->$idField) {
145
            $this->failure('authentication_error', "Object with @id does not exist.", 404);
146
            return $this->setOAuthError('invalid_request');
147
        }
148
149
        $this->mapper =& $m;
150
        return $m;
151
    }
152
153
154
    /**
155
     * Check permissions and load the mapper with the object in the URL param @id
156
     * if the user is an admin
157
     *
158
     * @param \Base $f3
159
     * @param array $params
160
     * @param string $idField the field used for the unique id to load by
161
     * @param string|null $defaultId defaule value to use if not found
162
     * @return null|array|boolean|\FFMVC\Mappers\Mapper
163
     */
164
    public function getIdObjectIfAdmin(\Base $f3, array $params, string $idField = 'uuid', $defaultId = null)
165
    {
166
        if (empty($this->isAuthorised)) {
167
            return;
168
        }
169
170
        // only admin has permission to delete @id
171
        $isAdmin = $f3->get('isAdmin');
172
        if (!$isAdmin) {
173
            $this->failure('authentication_error', "User does not have permission.", 401);
174
            return $this->setOAuthError('access_denied');
175
        }
176
177
        // invalid id
178
        $id = !empty($params['id']) ? $params['id'] : $f3->get('REQUEST.id');
179
        if (empty($id)) {
180
            $id = $defaultId;
181
        }
182
183
        if (!empty($id) && ('uuid' == $idField && 36 !== strlen($id))) {
184
            $this->failure('authentication_error', "Invalid @id parameter.", 400);
185
            return $this->setOAuthError('invalid_request');
186
        }
187
188
        // check id exists
189
        $db = \Registry::get('db');
190
        $m = $this->getMapper();
191
        $m->load([$db->quotekey($idField) . ' = ?', $id]);
192 View Code Duplication
        if (null == $m->$idField) {
193
            $this->failure('authentication_error', "Object with @id does not exist.", 404);
194
            return $this->setOAuthError('invalid_request');
195
        }
196
197
        $this->mapper =& $m;
198
        return $m;
199
    }
200
201
202
    /**
203
     * Display the data item
204
     *
205
     * @param \Base $f3
206
     * @param array $params
207
     * @return null|array|boolean
208
     */
209 View Code Duplication
    public function get(\Base $f3, array $params)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
    {
211
        $isAdmin = $f3->get('isAdmin');
212
        $m = $this->getIdObjectIfUser($f3, $params, 'uuid', $params['id']);
213
        if (!is_object($m) || null == $m->uuid) {
214
            return;
215
        } elseif (!$isAdmin && $m->users_uuid !== $f3->get('uuid')) {
216
            $this->failure('authentication_error', "User does not have permission.", 401);
217
            return $this->setOAuthError('access_denied');
218
        }
219
        // return raw data for object?
220
        $adminView = $f3->get('isAdmin') && 'admin' == $f3->get('REQUEST.view');
221
        $this->data = $adminView ? $m->castFields($f3->get('REQUEST.fields')) : $m->exportArray($f3->get('REQUEST.fields'));
222
    }
223
224
225
    /**
226
     * Replace data
227
     *
228
     * @param \Base $f3
229
     * @param array $params
230
     * @return null|array|boolean
231
     */
232
    public function put(\Base $f3, array $params)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
233
    {
234
        $m = $this->getIdObjectIfAdmin($f3, $params, 'uuid', $params['id']);
235
        if (!is_object($m) || null == $m->uuid) {
236
            return;
237
        }
238
239
        $f3->set('REQUEST.uuid', $m->uuid);
240
241
        // these fields can't be modified
242
        return $this->save($f3, [
243
            'id'
244
        ]);
245
    }
246
247
248
    /**
249
     * Create new data
250
     *
251
     * @param \Base $f3
252
     * @return null|array|boolean
253
     */
254
    public function post(\Base $f3)
255
    {
256
        $isAdmin = $f3->get('isAdmin');
257
        if (!$isAdmin) {
258
            return;
259
        }
260
261
        // this fields can't be modified
262
        $prohibitedFields = [
263
            'id', 'uuid'
264
        ];
265
266
        return $this->save($f3, $prohibitedFields);
267
    }
268
269
270
    /**
271
     * Update data
272
     *
273
     * @param \Base $f3
274
     * @param array $params
275
     * @return null|array|boolean
276
     */
277
    public function patch(\Base $f3, array $params)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
    {
279
        $m = $this->getIdObjectIfAdmin($f3, $params, 'uuid', $params['id']);
280
        if (!is_object($m) || null == $m->uuid) {
281
            return;
282
        }
283
284
        $f3->set('REQUEST.uuid', $m->uuid);
285
286
        // these fields can't be modified
287
        return $this->save($f3, [
288
            'id', 'uuid'
289
        ]);
290
    }
291
292
293
    /**
294
     * Delete the data object indicated by @id in the request
295
     *
296
     * @param \Base $f3
297
     * @param array $params
298
     * @return null|array|boolean
299
     */
300
    public function delete(\Base $f3, array $params)
301
    {
302
        $m = $this->getIdObjectIfUser($f3, $params);
303
        if (!is_object($m) || null == $m->uuid) {
304
            return;
305
        }
306
307
        // a user can only delete if they own the object
308
        $isAdmin = $f3->get('isAdmin');
309
        if (!$isAdmin) {
310
            if (!empty($m->users_uuid) && $m->users_uuid !== $f3->get('uuid')) {
311
                $this->failure('authentication_error', "User does not have permission.", 401);
312
                return $this->setOAuthError('access_denied');
313
            }
314
        }
315
316
        $this->data = [
317
            'deleted' => $m->erase()
318
        ];
319
    }
320
321
322
    /**
323
     * list objects (list is a reserved keyword)
324
     *
325
     * @param \Base $f3
326
     * @return null|array|boolean
327
     */
328 View Code Duplication
    public function listingAdmin(\Base $f3)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
329
    {
330
        // must be an admin
331
        $isAdmin = $f3->get('isAdmin');
332
        if (!$isAdmin) {
333
            $this->failure('authentication_error', "User does not have permission.", 401);
334
            return $this->setOAuthError('access_denied');
335
        }
336
337
        $this->data = $this->getListingResults($f3, $this->getMapper());
338
    }
339
340
341
    /**
342
     * list objects (list is a reserved keyword)
343
     *
344
     * @param \Base $f3
345
     * @param array $params
346
     * @return array|boolean|null
347
     */
348 View Code Duplication
    public function listing(\Base $f3, array $params)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
349
    {
350
        $isAdmin = $f3->get('isAdmin');
351
        if (!$isAdmin && array_key_exists('id', $params)) {
352
            $this->failure('authentication_error', "User does not have permission.", 401);
353
            return $this->setOAuthError('access_denied');
354
        } elseif ($isAdmin && array_key_exists('id', $params)) {
355
            $users_uuid = $params['id'];
356
        } elseif (!$isAdmin) {
357
            $users_uuid = $f3->get('uuid');
358
        } else {
359
            $users_uuid = null;
360
        }
361
362
        $this->data = $this->getListingResults($f3, $this->getMapper(), $users_uuid);
363
    }
364
365
366
    /**
367
     * search objects
368
     *
369
     * @param \Base $f3
370
     * @return null|array|boolean
371
     */
372 View Code Duplication
    public function searchAdmin(\Base $f3)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
373
    {
374
        // must be an admin
375
        $isAdmin = $f3->get('isAdmin');
376
        if (!$isAdmin) {
377
            $this->failure('authentication_error', "User does not have permission.", 401);
378
            return $this->setOAuthError('access_denied');
379
        }
380
381
        $this->data = $this->getSearchResults($f3, $this->getMapper());
382
    }
383
384
385
    /**
386
     * search objects
387
     *
388
     * @param \Base $f3
389
     * @param array $params
390
     * @return null|array|boolean
391
     */
392 View Code Duplication
    public function search(\Base $f3, array $params)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
393
    {
394
        $isAdmin = $f3->get('isAdmin');
395
        if (!$isAdmin && array_key_exists('id', $params)) {
396
            $this->failure('authentication_error', "User does not have permission.", 401);
397
            return $this->setOAuthError('access_denied');
398
        } elseif ($isAdmin && array_key_exists('id', $params)) {
399
            $users_uuid = $params['id'];
400
        } elseif (!$isAdmin) {
401
            $users_uuid = $f3->get('uuid');
402
        } else {
403
            $users_uuid = null;
404
        }
405
406
        $this->data = $this->getSearchResults($f3, $this->getMapper(), $users_uuid);
407
    }
408
409
}
410