Passed
Push — master ( e5acb2...2b13b1 )
by Evgeny
07:45
created

Action::setExtensions()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
4
 *
5
 * Licensed under The MIT License
6
 * Redistributions of files must retain the above copyright notice.
7
 *
8
 * @copyright Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
9
 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
10
 */
11
12
namespace CakeDC\Api\Service\Action;
13
14
use CakeDC\Api\Exception\ValidationException;
15
use CakeDC\Api\Service\Auth\Auth;
16
use CakeDC\Api\Service\Service;
17
use Cake\Core\InstanceConfigTrait;
18
use Cake\Event\Event;
19
use Cake\Event\EventDispatcherInterface;
20
use Cake\Event\EventDispatcherTrait;
21
use Cake\Event\EventListenerInterface;
22
use Cake\Event\EventManager;
23
use Cake\Utility\Hash;
24
use Cake\Validation\ValidatorAwareTrait;
25
use Exception;
26
use ReflectionMethod;
27
28
/**
29
 * Class Action
30
 *
31
 * @package CakeDC\Api\Service\Action
32
 */
33
abstract class Action implements EventListenerInterface, EventDispatcherInterface
34
{
35
    use EventDispatcherTrait;
36
    use InstanceConfigTrait;
37
    use ValidatorAwareTrait;
38
39
    /**
40
     * Extensions to load and attach to listener
41
     *
42
     * @var array
43
     */
44
    public $extensions = [];
45
46
    /**
47
     * An Auth instance.
48
     *
49
     * @var Auth
50
     */
51
    public $Auth;
52
53
    /**
54
     * Default Action options.
55
     *
56
     * @var array
57
     */
58
    protected $_defaultConfig = [];
59
60
    /**
61
     * A Service reference.
62
     *
63
     * @var Service
64
     */
65
    protected $_service;
66
67
    /**
68
     * Activated route.
69
     *
70
     * @var array
71
     */
72
    protected $_route = null;
73
74
    /**
75
     * Extension registry.
76
     *
77
     * @var \CakeDC\Api\Service\Action\ExtensionRegistry
78
     */
79
    protected $_extensions;
80
81
    /**
82
     * Action name.
83
     *
84
     * @var string
85
     */
86
    protected $_name;
87
88
    /**
89
     * Action constructor.
90
     *
91
     * @param array $config Configuration options passed to the constructor
92
     */
93 123
    public function __construct(array $config = [])
94
    {
95 123
        if (!empty($config['name'])) {
96 81
            $this->setName($config['name']);
97 81
        }
98 123
        if (!empty($config['service'])) {
99 123
            $this->setService($config['service']);
100 123
        }
101 123
        if (!empty($config['route'])) {
102 64
            $this->setRoute($config['route']);
103 64
        }
104 123
        if (!empty($config['Extension'])) {
105 49
            $this->extensions = (Hash::merge($this->extensions, $config['Extension']));
106 49
        }
107 123
        $extensionRegistry = $eventManager = null;
108 123
        if (!empty($config['eventManager'])) {
109
            $eventManager = $config['eventManager'];
110
        }
111 123
        $this->_eventManager = $eventManager ?: new EventManager();
112 123
        $this->setConfig($config);
113 123
        $this->initialize($config);
114 123
        $this->_eventManager->on($this);
115 123
        $this->setExtensions($extensionRegistry);
116 123
        $this->_loadExtensions();
117 123
    }
118
119
    /**
120
     * Initialize an action instance
121
     *
122
     * @param array $config Configuration options passed to the constructor
123
     * @return void
124
     */
125 123
    public function initialize(array $config)
126
    {
127 123
        $this->Auth = $this->_initializeAuth();
128 123
    }
129
130
    /**
131
     * Gets an action name.
132
     *
133
     * @return string
134
     */
135 79
    public function getName()
136
    {
137 79
        return $this->_name;
138
    }
139
140
    /**
141
     * Sets an action name.
142
     *
143
     * @param string $name An action name.
144
     * @return $this
145
     */
146 81
    public function setName($name)
147
    {
148 81
        $this->_name = $name;
149
150 81
        return $this;
151
    }
152
153
    /**
154
     * Get and set service name.
155
     *
156
     * @param string $name Action name.
157
     * @deprecated 3.4.0 Use setName()/getName() instead.
158
     * @return string
159
     */
160
    public function name($name = null)
161
    {
162
        deprecationWarning(
163
            'Action::name() is deprecated. ' .
164
            'Use Action::setName()/getName() instead.'
165
        );
166
167
        if ($name !== null) {
168
            return $this->setName($name);
169
        }
170
171
        return $this->getName();
172
    }
173
174
    /**
175
     * Returns activated route.
176
     *
177
     * @return array
178
     */
179 13
    public function getRoute()
180
    {
181 13
        return $this->_route;
182
    }
183
184
    /**
185
     * Sets activated route.
186
     *
187
     * @param array $route Route config.
188
     * @return $this
189
     */
190 64
    public function setRoute(array $route)
191
    {
192 64
        $this->_route = $route;
193
194 64
        return $this;
195
    }
196
197
    /**
198
     * Api method for activated route.
199
     *
200
     * @param array $route Activated route.
201
     * @deprecated 3.4.0 Use setRoute()/getRoute() instead.
202
     * @return array
203
     */
204
    public function route($route = null)
205
    {
206
        deprecationWarning(
207
            'Action::route() is deprecated. ' .
208
            'Use Action::setRoute()/getRoute() instead.'
209
        );
210
211
        if ($route !== null) {
212
            return $this->setRoute($route);
213
        }
214
215
        return $this->getRoute();
216
    }
217
218
    /**
219
     * @return Service
220
     */
221 9
    public function getService()
222
    {
223 9
        return $this->_service;
224
    }
225
226
    /**
227
     * Set a service
228
     *
229
     * @param Service $service service
230
     */
231 123
    public function setService(Service $service)
232
    {
233 123
        $this->_service = $service;
234 123
    }
235
236
    /**
237
     * Set or get service.
238
     *
239
     * @param Service $service An Service instance.
240
     * @return Service
241
     * @deprecated 3.4.0 Use setService()/getService() instead.
242
     */
243
    public function service($service = null)
244
    {
245
        deprecationWarning(
246
            'Action::service() is deprecated. ' .
247
            'Use Action::setService()/getService() instead.'
248
        );
249
250
        if ($service !== null) {
251
            return $this->setService($service);
252
        }
253
254
        return $this->getService();
255
    }
256
257
    /**
258
     * Apply validation process.
259
     *
260
     * @return bool
261
     */
262 47
    public function validates()
263
    {
264 47
        return true;
265
    }
266
267
    /**
268
     * Execute action.
269
     *
270
     * @return mixed
271
     */
272
    abstract public function execute();
273
274
    /**
275
     * Action execution life cycle.
276
     *
277
     * @return mixed
278
     */
279 60
    public function process()
280
    {
281 60
        $event = $this->dispatchEvent('Action.beforeProcess', ['action' => $this]);
282
283 60
        if ($event->isStopped()) {
284
            return $event->result;
285
        }
286
287 60
        $event = new Event('Action.onAuth', $this, ['action' => $this]);
288 60
        $this->Auth->authCheck($event);
289
290 59
        $event = $this->dispatchEvent('Action.beforeValidate', compact('data'));
291
292 59
        if ($event->isStopped()) {
293
            $this->dispatchEvent('Action.beforeValidateStopped', []);
294
295
            return $event->result;
296
        }
297
298 59
        if (!$this->validates()) {
299
            $this->dispatchEvent('Action.validationFailed', []);
300
            throw new ValidationException(__('Validation failed'), 0, null, []);
301
        }
302
303 58
        $event = $this->dispatchEvent('Action.beforeExecute', compact('data'));
304
305 58
        if ($event->isStopped()) {
306
            $this->dispatchEvent('Action.beforeExecuteStopped', []);
307
308
            return $event->result;
309
        }
310
311
        // thrown before execute action event (with stop on false)
312 58
        if (method_exists($this, 'action')) {
313 1
            $result = $this->_executeAction();
314 1
        } else {
315 57
            $result = $this->execute();
316
        }
317 56
        $this->dispatchEvent('Action.afterProcess', compact('result'));
318
319 56
        return $result;
320
    }
321
322
    /**
323
     * Execute action call to the method.
324
     *
325
     * This method pass action params as method params.
326
     *
327
     * @param string $methodName Method name.
328
     * @return mixed
329
     * @throws Exception
330
     */
331 1
    protected function _executeAction($methodName = 'action')
332
    {
333 1
        $parser = $this->getService()->getParser();
334 1
        $params = $parser->getParams();
335 1
        $arguments = [];
336 1
        $reflection = new ReflectionMethod($this, $methodName);
337 1
        foreach ($reflection->getParameters() as $param) {
338 1
            $paramName = $param->getName();
339 1
            if (!isset($params[$paramName])) {
340
                if ($param->isOptional()) {
341
                    $arguments[] = $param->getDefaultValue();
342
                } else {
343
                    throw new Exception('Missing required param: ' . $paramName, 409);
344
                }
345
            } else {
346 1
                if ($params[$paramName] === '' && $param->isOptional()) {
347
                    $arguments[] = $param->getDefaultValue();
348
                } else {
349 1
                    $value = $params[$paramName];
350 1
                    $arguments[] = (is_numeric($value)) ? 0 + $value : $value;
351
                }
352
            }
353 1
        }
354 1
        $result = call_user_func_array([$this, $methodName], $arguments);
355
356 1
        return $result;
357
    }
358
359
    /**
360
     * @return array
361
     */
362 123
    public function implementedEvents()
363
    {
364
        $eventMap = [
365 123
            'Action.beforeProcess' => 'beforeProcess',
366 123
            'Action.beforeValidate' => 'beforeValidate',
367 123
            'Action.beforeExecute' => 'beforeExecute',
368 123
            'Action.afterProcess' => 'afterProcess',
369 123
        ];
370 123
        $events = [];
371
372 123
        foreach ($eventMap as $event => $method) {
373 123
            if (!method_exists($this, $method)) {
374 123
                continue;
375
            }
376
            $events[$event] = $method;
377 123
        }
378
379 123
        return $events;
380
    }
381
382
    /**
383
     * Returns action input params
384
     *
385
     * @return mixed
386
     */
387 66
    public function getData()
388
    {
389 66
        return $this->getService()->getParser()->getParams();
390
    }
391
392
    /**
393
     * Returns action input params
394
     *
395
     * @return mixed
396
     * @deprecated 3.6.0 Use getData() instead.
397
     */
398
    public function data()
399
    {
400
        deprecationWarning(
401
            'Action::data() is deprecated. ' .
402
            'Use Action::getData() instead.'
403
        );
404
405
        return $this->getData();
406
    }
407
408
    /**
409
     * @return \CakeDC\Api\Service\Action\ExtensionRegistry
410
     */
411 49
    public function getExtensions()
412
    {
413 49
        return $this->_extensions;
414
    }
415
416
    /**
417
     * Set a service
418
     *
419
     * @param \CakeDC\Api\Service\Action\ExtensionRegistry|null $extensions Extension registry.
420
     */
421 123
    public function setExtensions($extensions = null)
422
    {
423 123
        if ($extensions === null && $this->_extensions === null) {
424 123
            $this->_extensions = new ExtensionRegistry($this);
425 123
        } else {
426
            $this->_extensions = $extensions;
427
        }
428
429 123
        return $this;
430
    }
431
432
    /**
433
     * Get the extension registry for this action.
434
     *
435
     * If called with the first parameter, it will be set as the action $this->_extensions property
436
     *
437
     * @param \CakeDC\Api\Service\Action\ExtensionRegistry|null $extensions Extension registry.
438
     *
439
     * @return \CakeDC\Api\Service\Action\ExtensionRegistry
440
     * @deprecated 3.6.0 Use setExtensions()/getExtensions() instead.
441
     */
442
    public function extensions($extensions = null)
443
    {
444
        deprecationWarning(
445
            'Action::extensions() is deprecated. ' .
446
            'Use Action::setExtensions()/getExtensions() instead.'
447
        );
448
449
        if ($this->_extensions === null && $extensions === null) {
450
            $this->setExtensions($extensions);
451
452
            return $this->getExtensions();
453
        }
454
455
        if ($service !== null) {
0 ignored issues
show
Bug introduced by
The variable $service does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
456
            return $this->setExtensions($extensions);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->setExtensions($extensions); (CakeDC\Api\Service\Action\Action) is incompatible with the return type documented by CakeDC\Api\Service\Action\Action::extensions of type CakeDC\Api\Service\Action\ExtensionRegistry|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
457
        }
458
    }
459
460
    /**
461
     * Loads the defined extensions using the Extension factory.
462
     *
463
     * @return void
464
     */
465 123
    protected function _loadExtensions()
466
    {
467 123
        if (empty($this->extensions)) {
468 74
            return;
469
        }
470 49
        $registry = $this->getExtensions();
471 49
        $extensions = $registry->normalizeArray($this->extensions);
472 49
        foreach ($extensions as $properties) {
473 49
            $instance = $registry->load($properties['class'], $properties['config']);
474 49
            $this->_eventManager->on($instance);
475 49
        }
476 49
    }
477
478
    /**
479
     * Initialize auth.
480
     *
481
     * @return Auth
482
     */
483 123
    protected function _initializeAuth()
484
    {
485 123
        $config = $this->_authConfig();
486 123
        $auth = new Auth($config);
487 123
        if (array_key_exists('allow', $config)) {
488
            $auth->allow($config['allow']);
489
490
            return $auth;
491
        }
492
493 123
        return $auth;
494
    }
495
496
    /**
497
     * Prepare Auth configuration.
498
     *
499
     * @return array
500
     */
501 123
    protected function _authConfig()
502
    {
503 123
        $defaultConfig = (array)$this->getConfig('Auth');
504
505 123
        return Hash::merge($defaultConfig, [
506 123
            'service' => $this->_service,
507 123
            'request' => $this->_service->getRequest(),
508 123
            'response' => $this->_service->getResponse(),
509 123
            'action' => $this,
510 123
        ]);
511
    }
512
}
513