CrudController   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 781
Duplicated Lines 0 %

Importance

Changes 30
Bugs 1 Features 0
Metric Value
wmc 64
eloc 248
c 30
b 1
f 0
dl 0
loc 781
rs 3.28

17 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 26 2
B postRemap() 0 48 7
B read() 0 34 6
A delete() 0 11 2
A update() 0 15 2
A list() 0 48 5
A getRemap() 0 17 3
B deleteRemap() 0 43 6
B putRemap() 0 44 6
A getLookupData() 0 4 1
A getPostedData() 0 4 1
A userCan() 0 2 1
A buildUrl() 0 30 6
A __construct() 0 14 3
B lookUpResource() 0 44 7
A validateUserInput() 0 35 4
A formatObject() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like CrudController 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.

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 CrudController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Nails\Api\Controller;
4
5
use ApiRouter;
6
use Nails\Api\Constants;
7
use Nails\Api\Exception\ApiException;
8
use Nails\Api\Factory\ApiResponse;
9
use Nails\Common\Exception\FactoryException;
10
use Nails\Common\Exception\ModelException;
11
use Nails\Common\Exception\NailsException;
12
use Nails\Common\Exception\ValidationException;
13
use Nails\Common\Helper\ArrayHelper;
14
use Nails\Common\Resource;
15
use Nails\Common\Service\FormValidation;
16
use Nails\Common\Service\HttpCodes;
17
use Nails\Common\Service\Input;
18
use Nails\Common\Service\Uri;
19
use Nails\Factory;
20
use ReflectionException;
21
22
/**
23
 * Class CrudController
24
 *
25
 * @package Nails\Api\Controller
26
 */
27
class CrudController extends Base
28
{
29
    /**
30
     * The model to use
31
     *
32
     * @var string
33
     */
34
    const CONFIG_MODEL_NAME = '';
35
36
    /**
37
     * The model's provider
38
     *
39
     * @var string
40
     */
41
    const CONFIG_MODEL_PROVIDER = '';
42
43
    /**
44
     * The URI segment which will contain the item's identifier
45
     *
46
     * @var int
47
     */
48
    const CONFIG_URI_SEGMENT_IDENTIFIER = 4;
49
50
    /**
51
     * What to use for looking up resources; ID, SLUG, or TOKEN
52
     *
53
     * @var string
54
     */
55
    const CONFIG_LOOKUP_METHOD = 'ID';
56
57
    /**
58
     * The $_GET parameter with the search query in it
59
     *
60
     * @var string
61
     */
62
    const CONFIG_SEARCH_PARAM = 'search';
63
64
    /**
65
     * The $_GET parameter with the ID restrictions in it
66
     *
67
     * @var string
68
     */
69
    const CONFIG_IDS_PARAM = 'ids';
70
71
    /**
72
     * The $_GET parameter with the page query in it
73
     *
74
     * @var string
75
     */
76
    const CONFIG_PAGE_PARAM = 'page';
77
78
    /**
79
     * The number of items to return per page
80
     *
81
     * @var int
82
     */
83
    const CONFIG_PER_PAGE = 25;
84
85
    /**
86
     * The default data array to use when looking up an item
87
     *
88
     * @var array
89
     */
90
    const CONFIG_LOOKUP_DATA = [];
91
92
    /**
93
     * Actions which can be performed
94
     *
95
     * @var string
96
     */
97
    const ACTION_LIST   = 'LIST';
98
    const ACTION_CREATE = 'CREATE';
99
    const ACTION_READ   = 'READ';
100
    const ACTION_UPDATE = 'UPDATE';
101
    const ACTION_DELETE = 'DELETE';
102
103
    /**
104
     * An array of fields which should be ignored when reading
105
     *
106
     * @var array
107
     */
108
    const IGNORE_FIELDS_READ = [];
109
110
    /**
111
     * An array of fields which should be ignored when writing
112
     *
113
     * @var array
114
     */
115
    const IGNORE_FIELDS_WRITE = [
116
        'id',
117
        'token',
118
    ];
119
120
    // --------------------------------------------------------------------------
121
122
    /**
123
     * The model instance
124
     *
125
     * @var \Nails\Common\Model\Base
126
     */
127
    protected $oModel;
128
129
    // --------------------------------------------------------------------------
130
131
    /**
132
     * CrudController constructor.
133
     *
134
     * @param ApiRouter $oApiRouter the ApiRouter object
135
     *
136
     * @throws ApiException
137
     * @throws FactoryException
138
     * @throws ReflectionException
139
     * @throws NailsException
140
     */
141
    public function __construct($oApiRouter)
142
    {
143
        parent::__construct($oApiRouter);
144
145
        if (empty(static::CONFIG_MODEL_NAME)) {
0 ignored issues
show
introduced by Pablo de la Peña
The condition empty(static::CONFIG_MODEL_NAME) is always true.
Loading history...
146
            throw new ApiException('"static::CONFIG_MODEL_NAME" is required.');
147
        }
148
        if (empty(static::CONFIG_MODEL_PROVIDER)) {
149
            throw new ApiException('"static::CONFIG_MODEL_PROVIDER" is required.');
150
        }
151
152
        $this->oModel = Factory::model(
153
            static::CONFIG_MODEL_NAME,
154
            static::CONFIG_MODEL_PROVIDER
155
        );
156
157
    }
158
159
    /** -------------------------------------------------------------------------
160
     * ROUTING METHODS
161
     * The following methods route requests to the appropriate CRUD method
162
     * --------------------------------------------------------------------------
163
     */
164
165
    /**
166
     * Handles GET requests
167
     *
168
     * @param string $sMethod The method being called
169
     * @param array  $aData   Any data to apply to the requests
170
     *
171
     * @return ApiResponse
172
     * @throws ApiException
173
     * @throws FactoryException
174
     * @throws ModelException
175
     */
176
    public function getRemap($sMethod, array $aData = [])
177
    {
178
        /** @var Uri $oUri */
179
        $oUri = Factory::service('Uri');
180
181
        if ($oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER)) {
182
183
            //  Test that there's not an explicit method defined for this request
184
            $sExplicitMethod = 'get' . ucfirst($oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER));
185
            if (method_exists($this, $sExplicitMethod)) {
186
                return $this->$sExplicitMethod();
187
            }
188
189
            return $this->read($aData);
190
191
        } else {
192
            return $this->list($aData);
193
        }
194
    }
195
196
    // --------------------------------------------------------------------------
197
198
    /**
199
     * Handles POST requests
200
     *
201
     * @param string $sMethod The method being called
202
     * @param array  $aData   Any data to apply to the requests
203
     *
204
     * @return ApiResponse
205
     * @throws ApiException
206
     * @throws FactoryException
207
     * @throws ModelException
208
     * @throws ValidationException
209
     */
210
    public function postRemap($sMethod, array $aData = [])
211
    {
212
        /** @var Input $oInput */
213
        $oInput = Factory::service('Input');
0 ignored issues
show
Unused Code introduced by Pablo de la Peña
The assignment to $oInput is dead and can be removed.
Loading history...
214
        /** @var HttpCodes $oHttpCodes */
215
        $oHttpCodes = Factory::service('HttpCodes');
216
        /** @var Uri $oUri */
217
        $oUri = Factory::service('Uri');
218
219
        //  Test that there's not an explicit method defined for this action
220
        $sExplicitMethod = 'post' . ucfirst($oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER));
221
        if (method_exists($this, $sExplicitMethod)) {
222
            return $this->$sExplicitMethod();
223
        }
224
225
        if (empty($sMethod) || $sMethod === 'index') {
226
            return $this->create();
227
        }
228
229
        //  If there's a submethod defined, verify that the resource is valid and then call the sub method
230
        $sSubMethod = $oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER + 1);
231
        if (empty($sSubMethod)) {
232
            throw new ApiException(
233
                'A subresource must be specified when posting against an existing item',
234
                $oHttpCodes::STATUS_NOT_FOUND
235
            );
236
        } elseif (!method_exists($this, $sSubMethod)) {
237
            throw new ApiException(
238
                '"' . $sSubMethod . '" is not a valid subresource',
239
                $oHttpCodes::STATUS_NOT_FOUND
240
            );
241
        }
242
243
        $aData = $this->getLookupData(static::ACTION_READ, $aData);
244
        $oItem = $this->lookUpResource($aData);
245
        if (!$oItem) {
0 ignored issues
show
introduced by Pablo de la Peña
$oItem is of type Nails\Common\Resource\Entity, thus it always evaluated to true.
Loading history...
246
            throw new ApiException(
247
                'Resource not found',
248
                $oHttpCodes::STATUS_NOT_FOUND
249
            );
250
        }
251
252
        /** @var ApiResponse $oApiResponse */
253
        $oApiResponse = Factory::factory('ApiResponse', Constants::MODULE_SLUG);
254
255
        $this->$sSubMethod($oApiResponse, $oItem);
256
257
        return $oApiResponse;
258
    }
259
260
    // --------------------------------------------------------------------------
261
262
    /**
263
     * Handles PUT requests
264
     *
265
     * @param string $sMethod The method being called
266
     * @param array  $aData   Any data to apply to the requests
267
     *
268
     * @return ApiResponse
269
     * @throws ApiException
270
     * @throws FactoryException
271
     * @throws ModelException
272
     * @throws ValidationException
273
     */
274
    public function putRemap($sMethod, array $aData = [])
275
    {
276
        /** @var Uri $oUri */
277
        $oUri = Factory::service('Uri');
278
        /** @var HttpCodes $oHttpCodes */
279
        $oHttpCodes = Factory::service('HttpCodes');
280
281
        //  Test that there's not an explicit method defined for this action
282
        $sMethod = 'put' . ucfirst($oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER));
283
        if (method_exists($this, $sMethod)) {
284
            return $this->$sMethod();
285
        }
286
287
        // --------------------------------------------------------------------------
288
289
        //  If there's a submethod defined, verify that the resource is valid and then call the sub method
290
        $sSubMethod = $oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER + 1);
291
        if (!empty($sSubMethod) && !method_exists($this, $sSubMethod)) {
292
            throw new ApiException(
293
                '"' . $sSubMethod . '" is not a valid subresource',
294
                $oHttpCodes::STATUS_NOT_FOUND
295
            );
296
        }
297
298
        $aData = $this->getLookupData(static::ACTION_UPDATE, $aData);
299
        $oItem = $this->lookUpResource($aData);
300
301
        if (!$oItem) {
0 ignored issues
show
introduced by Pablo de la Peña
$oItem is of type Nails\Common\Resource\Entity, thus it always evaluated to true.
Loading history...
302
            throw new ApiException(
303
                'Resource not found',
304
                $oHttpCodes::STATUS_NOT_FOUND
305
            );
306
        }
307
308
        /** @var ApiResponse $oApiResponse */
309
        $oApiResponse = Factory::factory('ApiResponse', Constants::MODULE_SLUG);
310
311
        if (empty($sSubMethod)) {
312
            $this->update($oApiResponse, $oItem);
313
        } else {
314
            $this->$sSubMethod($oApiResponse, $oItem);
315
        }
316
317
        return $oApiResponse;
318
    }
319
320
    // --------------------------------------------------------------------------
321
322
    /**
323
     * Handles DELETE requests
324
     *
325
     * @param string $sMethod The method being called
326
     * @param array  $aData   Any data to apply to the requests
327
     *
328
     * @return ApiResponse
329
     * @throws ApiException
330
     * @throws FactoryException
331
     * @throws ModelException
332
     */
333
    public function deleteRemap($sMethod, array $aData = [])
334
    {
335
        /** @var Uri $oUri */
336
        $oUri = Factory::service('Uri');
337
        /** @var HttpCodes $oHttpCodes */
338
        $oHttpCodes = Factory::service('HttpCodes');
339
340
        //  Test that there's not an explicit method defined for this action
341
        $sMethod = 'delete' . ucfirst($oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER));
342
        if (method_exists($this, $sMethod)) {
343
            return $this->$sMethod();
344
        }
345
346
        // --------------------------------------------------------------------------
347
348
        //  If there's a submethod defined, verify that the resource is valid and then call the sub method
349
        $sSubMethod = $oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER + 1);
350
        if (!empty($sSubMethod) && !method_exists($this, $sSubMethod)) {
351
            throw new ApiException(
352
                '"' . $sSubMethod . '" is not a valid subresource',
353
                $oHttpCodes::STATUS_NOT_FOUND
354
            );
355
        }
356
357
        $aData = $this->getLookupData(static::ACTION_DELETE, $aData);
358
        $oItem = $this->lookUpResource($aData);
359
        if (!$oItem) {
0 ignored issues
show
introduced by Pablo de la Peña
$oItem is of type Nails\Common\Resource\Entity, thus it always evaluated to true.
Loading history...
360
            throw new ApiException(
361
                'Resource not found',
362
                $oHttpCodes::STATUS_NOT_FOUND
363
            );
364
        }
365
366
        /** @var ApiResponse $oApiResponse */
367
        $oApiResponse = Factory::factory('ApiResponse', Constants::MODULE_SLUG);
368
369
        if (empty($sSubMethod)) {
370
            $this->delete($oApiResponse, $oItem);
371
        } else {
372
            $this->$sSubMethod($oApiResponse, $oItem);
373
        }
374
375
        return $oApiResponse;
376
    }
377
378
    /** -------------------------------------------------------------------------
379
     * CRUD METHODS
380
     * The following methods provide (C)reate (R)ead (U)pdate and (D)elete
381
     * functionality, as well as a listing method for browsing resources.
382
     * --------------------------------------------------------------------------
383
     */
384
385
    /**
386
     * Lists resources in a paginated fashion
387
     *
388
     * @param array $aData Any data to apply to the requests
389
     *
390
     * @return ApiResponse
391
     * @throws FactoryException
392
     * @throws ModelException
393
     */
394
    protected function list(array $aData = []): ApiResponse
395
    {
396
        $this->userCan(static::ACTION_LIST);
397
398
        /** @var Input $oInput */
399
        $oInput = Factory::service('Input');
400
        $aData  = $this->getLookupData(static::ACTION_READ, $aData);
401
402
        //  Paging
403
        $iPage = (int) $oInput->get(static::CONFIG_PAGE_PARAM) ?: 1;
404
        $iPage = $iPage < 0 ? $iPage * -1 : $iPage;
405
406
        //  Searching
407
        if ($oInput->get(static::CONFIG_SEARCH_PARAM)) {
408
            $aData['keywords'] = $oInput->get(static::CONFIG_SEARCH_PARAM);
409
        }
410
411
        // Requesting specific IDs
412
        if ($oInput->get(static::CONFIG_IDS_PARAM)) {
413
            $aData['where_in'] = [
414
                [$this->oModel->getcolumn('id'), explode(',', $oInput->get(static::CONFIG_IDS_PARAM))],
0 ignored issues
show
Bug introduced by Pablo de la Peña
It seems like $oInput->get(static::CONFIG_IDS_PARAM) can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

414
                [$this->oModel->getcolumn('id'), explode(',', /** @scrutinizer ignore-type */ $oInput->get(static::CONFIG_IDS_PARAM))],
Loading history...
415
            ];
416
        }
417
418
        $iTotal   = $this->oModel->countAll($aData);
419
        $aResults = array_map(
420
            [$this, 'formatObject'],
421
            $this->oModel->getAll(
422
                $iPage,
423
                static::CONFIG_PER_PAGE,
424
                $aData
425
            )
426
        );
427
428
        /** @var ApiResponse $oApiResponse */
429
        $oApiResponse = Factory::factory('ApiResponse', Constants::MODULE_SLUG)
430
            ->setData($aResults)
431
            ->setMeta([
432
                'pagination' => [
433
                    'page'     => $iPage,
434
                    'per_page' => static::CONFIG_PER_PAGE,
435
                    'total'    => $iTotal,
436
                    'previous' => $this->buildUrl($iTotal, $iPage, -1),
437
                    'next'     => $this->buildUrl($iTotal, $iPage, 1),
438
                ],
439
            ]);
440
441
        return $oApiResponse;
442
    }
443
444
    // --------------------------------------------------------------------------
445
446
    /**
447
     * Creates a new resource
448
     *
449
     * @return ApiResponse
450
     * @throws ApiException
451
     * @throws FactoryException
452
     * @throws ModelException
453
     * @throws ValidationException
454
     */
455
    protected function create(): ApiResponse
456
    {
457
        $this->userCan(static::ACTION_CREATE);
458
459
        /** @var HttpCodes $oHttpCodes */
460
        $oHttpCodes = Factory::service('HttpCodes');
461
        /** @var ApiResponse $oApiResponse */
462
        $oApiResponse = Factory::factory('ApiResponse', Constants::MODULE_SLUG);
463
464
        $aData   = $this->getRequestData();
465
        $aData   = $this->validateUserInput($aData);
466
        $iItemId = $this->oModel->create($aData);
467
468
        if (empty($iItemId)) {
469
            throw new ApiException(
470
                'Failed to create resource. ' . $this->oModel->lastError(),
471
                $oHttpCodes::STATUS_INTERNAL_SERVER_ERROR
472
            );
473
        }
474
475
        $aData = $this->getLookupData(static::ACTION_READ, $aData);
476
        /** @var Resource\Entity $oItem */
477
        $oItem = $this->oModel->getById($iItemId, $aData);
478
        $oApiResponse->setData($this->formatObject($oItem));
479
480
        return $oApiResponse;
481
    }
482
483
    // --------------------------------------------------------------------------
484
485
    /**
486
     * Returns a single resource
487
     *
488
     * @param array $aData Any data to apply to the requests
489
     *
490
     * @return ApiResponse
491
     * @throws ApiException
492
     * @throws FactoryException
493
     * @throws ModelException
494
     */
495
    protected function read(array $aData = []): ApiResponse
496
    {
497
        /** @var Uri $oUri */
498
        $oUri = Factory::service('Uri');
499
        /** @var HttpCodes $oHttpCodes */
500
        $oHttpCodes = Factory::service('HttpCodes');
501
502
        $aData = $this->getLookupData(static::ACTION_READ, $aData);
503
        $oItem = $this->lookUpResource($aData);
504
        if (!$oItem) {
0 ignored issues
show
introduced by Pablo de la Peña
$oItem is of type Nails\Common\Resource\Entity, thus it always evaluated to true.
Loading history...
505
            throw new ApiException(
506
                'Resource not found',
507
                $oHttpCodes::STATUS_NOT_FOUND
508
            );
509
        }
510
511
        $this->userCan(static::ACTION_READ, $oItem);
512
        /** @var ApiResponse $oApiResponse */
513
        $oApiResponse = Factory::factory('ApiResponse', Constants::MODULE_SLUG);
514
515
        //  If there's a submethod defined, call that
516
        $sSubMethod = $oUri->segment(static::CONFIG_URI_SEGMENT_IDENTIFIER + 1);
517
        if ($sSubMethod && method_exists($this, $sSubMethod)) {
518
            $this->$sSubMethod($oApiResponse, $oItem);
519
        } elseif ($sSubMethod && !method_exists($this, $sSubMethod)) {
520
            throw new ApiException(
521
                '"' . $sSubMethod . '" is not a valid subresource',
522
                $oHttpCodes::STATUS_NOT_FOUND
523
            );
524
        } else {
525
            $oApiResponse->setData($this->formatObject($oItem));
526
        }
527
528
        return $oApiResponse;
529
    }
530
531
    // --------------------------------------------------------------------------
532
533
    /**
534
     * Updates an existing resource
535
     *
536
     * @param ApiResponse     $oApiResponse The API Response
537
     * @param Resource\Entity $oItem        The item resource being updated
538
     *
539
     * @throws ApiException
540
     * @throws FactoryException
541
     * @throws ModelException
542
     * @throws ValidationException
543
     */
544
    protected function update(ApiResponse $oApiResponse, Resource\Entity $oItem): void
545
    {
546
        $this->userCan(static::ACTION_UPDATE, $oItem);
547
548
        /** @var HttpCodes $oHttpCodes */
549
        $oHttpCodes = Factory::service('HttpCodes');
550
551
        //  Read from php:://input as using PUT; expecting a JSON object as the payload
552
        $aData = $this->getRequestData();
553
        $aData = $this->validateUserInput($aData, $oItem);
554
555
        if (!$this->oModel->update($oItem->id, $aData)) {
556
            throw new ApiException(
557
                'Failed to update resource. ' . $this->oModel->lastError(),
558
                $oHttpCodes::STATUS_INTERNAL_SERVER_ERROR
559
            );
560
        }
561
    }
562
563
    // --------------------------------------------------------------------------
564
565
    /**
566
     * Deletes an existing resource
567
     *
568
     * @param ApiResponse      $oApiResponse The API Response
569
     * @param Resource\Entity  $oItem        The item resource being updated
570
     *
571
     * @throws ApiException
572
     * @throws FactoryException
573
     * @throws ModelException
574
     */
575
    protected function delete(ApiResponse $oApiResponse, Resource\Entity $oItem): void
576
    {
577
        $this->userCan(static::ACTION_DELETE, $oItem);
578
579
        /** @var HttpCodes $oHttpCodes */
580
        $oHttpCodes = Factory::service('HttpCodes');
581
582
        if (!$this->oModel->delete($oItem->id)) {
583
            throw new ApiException(
584
                'Failed to delete resource. ' . $this->oModel->lastError(),
585
                $oHttpCodes::STATUS_INTERNAL_SERVER_ERROR
586
            );
587
        }
588
    }
589
590
    /** -------------------------------------------------------------------------
591
     * UTILITY METHODS
592
     * The following methods are utility methods which provide additional bits
593
     * of functionality and/or hooks for overloading code.
594
     * --------------------------------------------------------------------------
595
     */
596
597
    /**
598
     * Fetches an object by it's ID, SLUG, or TOKEN
599
     *
600
     * @param array $aData    Any data to pass to the lookup
601
     * @param int   $iSegment The segment containing the item's ID/Token/Slug
602
     *
603
     * @return Resource\Entity|null
604
     * @throws FactoryException
605
     * @throws ModelException
606
     */
607
    protected function lookUpResource($aData = [], $iSegment = null): ?Resource\Entity
608
    {
609
        if ($iSegment === null) {
610
            $iSegment = static::CONFIG_URI_SEGMENT_IDENTIFIER;
611
        }
612
613
        /** @var Uri $oUri */
614
        $oUri        = Factory::service('Uri');
615
        $sIdentifier = $oUri->segment($iSegment);
616
617
        //  Handle requests for expansions
618
        /** @var Input $oInput */
619
        $oInput      = Factory::service('Input');
620
        $aExpansions = array_filter((array) $oInput->get('expand'));
621
        if ($aExpansions) {
0 ignored issues
show
Bug Best Practice introduced by Pablo de la Peña
The expression $aExpansions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
622
            if (!array_key_exists('expand', $aData)) {
623
                $aData['expand'] = [];
624
            }
625
626
            $aData['expand'] = array_merge($aData['expand'], $aExpansions);
627
        }
628
629
        switch (static::CONFIG_LOOKUP_METHOD) {
630
            case 'ID':
631
                $oItem = $this->oModel->getById($sIdentifier, $aData);
632
                break;
633
634
            case 'SLUG':
635
                $oItem = $this->oModel->getBySlug($sIdentifier, $aData);
636
                break;
637
638
            case 'TOKEN':
639
                $oItem = $this->oModel->getByToken($sIdentifier, $aData);
640
                break;
641
642
            default:
643
                throw new ApiException(sprintf(
644
                    'Value for %s::CONFIG_LOOKUP_METHOD is invalid',
645
                    static::class
646
                ));
647
        }
648
649
        /** @var Resource\Entity $oItem */
650
        return $oItem;
651
    }
652
653
    // --------------------------------------------------------------------------
654
655
    /**
656
     * Provides a hook for manipulating the lookup data
657
     *
658
     * @param string $sMode The lookup mode
659
     * @param array  $aData The lookup data
660
     *
661
     * @return array
662
     */
663
    protected function getLookupData(string $sMode, array $aData): array
664
    {
665
        //  This method should be overridden to implement specific behaviour
666
        return array_merge(static::CONFIG_LOOKUP_DATA, $aData);
667
    }
668
669
    // --------------------------------------------------------------------------
670
671
    /**
672
     * Validates a user can perform this action
673
     *
674
     * @param string          $sAction The action being performed
675
     * @param Resource\Entity $oItem   The item the action is being performed against
676
     */
677
    protected function userCan($sAction, Resource\Entity $oItem = null)
678
    {
679
        /**
680
         * By default users can perform any action, apply restrictions by
681
         * overloading this method and throwing a ValidationException.
682
         */
683
    }
684
685
    // --------------------------------------------------------------------------
686
687
    /**
688
     * Validates user input
689
     *
690
     * @param array           $aData The user data to validate
691
     * @param Resource\Entity $oItem The current object (when editing)
692
     *
693
     * @return array
694
     * @throws FactoryException
695
     * @throws ValidationException
696
     */
697
    protected function validateUserInput($aData, Resource\Entity $oItem = null)
698
    {
699
        $aOut    = [];
700
        $aFields = $this->oModel->describeFields();
701
        $aKeys   = array_unique(
702
            array_merge(
703
                array_keys($aFields),
704
                ArrayHelper::extract($this->oModel->getExpandableFields(), 'trigger')
705
            )
706
        );
707
708
        $aValidKeys = array_diff($aKeys, static::IGNORE_FIELDS_WRITE);
709
        $aRules     = [];
710
711
        foreach ($aValidKeys as $sValidKey) {
712
713
            $oField = ArrayHelper::get($sValidKey, $aFields);
0 ignored issues
show
Unused Code introduced by Pablo de la Peña
The assignment to $oField is dead and can be removed.
Loading history...
714
            if (array_key_exists($sValidKey, $aData)) {
715
716
                $aOut[$sValidKey] = ArrayHelper::get($sValidKey, $aData);
717
                $oField           = ArrayHelper::get($sValidKey, $aFields);
718
719
                if (!empty($oField->validation)) {
720
                    $aRules[$sValidKey] = $oField->validation;
721
                }
722
            }
723
        }
724
725
        /** @var FormValidation $oFormValidation */
726
        $oFormValidation = Factory::service('FormValidation');
727
        $oFormValidation
728
            ->buildValidator($aRules, [], $aOut)
729
            ->run();
730
731
        return $aOut;
732
    }
733
734
    // --------------------------------------------------------------------------
735
736
    /**
737
     * Builds pagination URL
738
     *
739
     * @param int $iTotal      The total number of items
740
     * @param int $iPage       The current page number
741
     * @param int $iPageOffset The offset to the page number
742
     *
743
     * @return null|string
744
     * @throws FactoryException
745
     */
746
    protected function buildUrl($iTotal, $iPage, $iPageOffset)
747
    {
748
        /** @var Input $oInput */
749
        $oInput = Factory::service('Input');
750
751
        $aParams = array_merge(
752
            $oInput->get(),
753
            [
754
                'page' => $iPage + $iPageOffset,
755
            ]
756
        );
757
758
        if ($aParams['page'] <= 0) {
759
            return null;
760
        } elseif ($aParams['page'] === 1) {
761
            unset($aParams['page']);
762
        }
763
764
        $iTotalPages = ceil($iTotal / static::CONFIG_PER_PAGE);
765
        if (!empty($aParams['page']) && $aParams['page'] > $iTotalPages) {
766
            return null;
767
        }
768
769
        $sUrl = siteUrl() . uri_string();
770
771
        if (!empty($aParams)) {
772
            $sUrl .= '?' . http_build_query($aParams);
773
        }
774
775
        return $sUrl;
776
    }
777
778
    // --------------------------------------------------------------------------
779
780
    /**
781
     * Formats an object
782
     *
783
     * @param Resource\Entity $oObj The object to format
784
     *
785
     * @return Resource\Entity
786
     */
787
    protected function formatObject(Resource\Entity $oObj)
788
    {
789
        foreach (static::IGNORE_FIELDS_READ as $sIgnoredField) {
790
            unset($oObj->{$sIgnoredField});
791
        }
792
        return $oObj;
793
    }
794
795
    // --------------------------------------------------------------------------
796
797
    /**
798
     * Gets the request data from the POST vars, falling back to the request body
799
     *
800
     * @return array
801
     * @throws FactoryException
802
     * @deprecated
803
     */
804
    protected function getPostedData(): array
805
    {
806
        deprecatedError(__METHOD__, 'Base::getRequestData');
807
        return parent::getRequestData();
808
    }
809
}
810