Service::_initializeParser()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.4425

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 1
dl 0
loc 16
rs 9.1111
c 0
b 0
f 0
ccs 10
cts 13
cp 0.7692
crap 6.4425
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;
13
14
use CakeDC\Api\Exception\ValidationException;
15
use CakeDC\Api\Routing\ApiRouter;
16
use CakeDC\Api\Service\Action\DummyAction;
17
use CakeDC\Api\Service\Action\Result;
18
use CakeDC\Api\Service\Exception\MissingActionException;
19
use CakeDC\Api\Service\Exception\MissingParserException;
20
use CakeDC\Api\Service\Exception\MissingRendererException;
21
use CakeDC\Api\Service\Renderer\BaseRenderer;
22
use CakeDC\Api\Service\RequestParser\BaseParser;
23
24
use Cake\Core\App;
25
use Cake\Core\Configure;
26
use Cake\Datasource\Exception\RecordNotFoundException;
27
28
use Cake\Event\EventDispatcherInterface;
29
use Cake\Event\EventDispatcherTrait;
30
use Cake\Event\EventListenerInterface;
31
use Cake\Event\EventManager;
32
use Cake\Http\Response;
33
use Cake\Http\ServerRequest;
34
use Cake\Routing\RouteBuilder;
35
use Cake\Utility\Hash;
36
use Cake\Utility\Inflector;
37
use Exception;
38
39
/**
40
 * Class Service
41
 */
42
abstract class Service implements EventListenerInterface, EventDispatcherInterface
43
{
44
    use EventDispatcherTrait;
45
46
    /**
47
     * Extensions to load and attach to listener
48
     *
49
     * @var array
50
     */
51
    public $extensions = [];
52
53
    /**
54
     * Actions routes description map, indexed by action name.
55
     *
56
     * @var array
57
     */
58
    protected $_actions = [];
59
60
    /**
61
     * Actions classes map, indexed by action name.
62
     *
63
     * @var array
64
     */
65
    protected $_actionsClassMap = [];
66
67
    /**
68
     * Service url acceptable extensions list.
69
     *
70
     * @var array
71
     */
72
    protected $_routeExtensions = ['json'];
73
74
    /**
75
     *
76
     *
77
     * @var string
78
     */
79
    protected $_routePrefix = '';
80
81
    /**
82
     * Service name
83
     *
84
     * @var string
85
     */
86
    protected $_name = null;
87
88
    /**
89
     * Service version.
90
     *
91
     * @var int
92
     */
93
    protected $_version;
94
95
    /**
96
     * Parser class to process the HTTP request.
97
     *
98
     * @var BaseParser
99
     */
100
    protected $_parser;
101
102
    /**
103
     * Renderer class to build the HTTP response.
104
     *
105
     * @var BaseRenderer
106
     */
107
    protected $_renderer;
108
109
    /**
110
     * The parser class.
111
     *
112
     * @var string
113
     */
114
    protected $_parserClass = null;
115
116
    /**
117
     * The Renderer class.
118
     *
119
     * @var string
120
     */
121
    protected $_rendererClass = null;
122
123
    /**
124
     * Dependent services names list
125
     *
126
     * @var array<string>
127
     */
128
    protected $_innerServices = [];
129
130
    /**
131
     * Parent service instance.
132
     *
133
     * @var Service
134
     */
135
    protected $_parentService;
136
137
    /**
138
     * Service Action Result object.
139
     *
140
     * @var Result
141
     */
142
    protected $_result;
143
144
    /**
145
     * Base url for service.
146
     *
147
     * @var string
148
     */
149
    protected $_baseUrl;
150
151
    /**
152
     * Request
153
     *
154
     * @var \Cake\Http\ServerRequest
155
     */
156
    protected $_request;
157
158
    /**
159
     * Request
160
     *
161
     * @var \Cake\Http\Response
162
     */
163
    protected $_response;
164
165
    /**
166
     * @var string
167
     */
168
    protected $_corsSuffix = '_cors';
169
170
    /**
171
     * Extension registry.
172
     *
173
     * @var \CakeDC\Api\Service\ExtensionRegistry
174
     */
175
    protected $_extensions;
176
177
    /**
178
     * Service constructor.
179
     *
180
     * @param array $config Service configuration.
181
     */
182 140
    public function __construct(array $config = [])
183
    {
184 140
        if (isset($config['request'])) {
185 140
            $this->setRequest($config['request']);
186 140
        }
187 140
        if (isset($config['response'])) {
188 140
            $this->setResponse($config['response']);
189 140
        }
190 140
        if (isset($config['baseUrl'])) {
191 93
            $this->_baseUrl = $config['baseUrl'];
192 93
        }
193 140
        if (isset($config['service'])) {
194 111
            $this->setName($config['service']);
195 111
        }
196 140
        if (isset($config['version'])) {
197
            $this->setVersion($config['version']);
198
        }
199 140
        if (isset($config['classMap'])) {
200 1
            $this->_actionsClassMap = Hash::merge($this->_actionsClassMap, $config['classMap']);
201 1
        }
202
203 140
        if (!empty($config['Extension'])) {
204
            $this->extensions = (Hash::merge($this->extensions, $config['Extension']));
205
        }
206 140
        $extensionRegistry = $eventManager = null;
207 140
        if (!empty($config['eventManager'])) {
208
            $eventManager = $config['eventManager'];
209
        }
210 140
        $this->_eventManager = $eventManager ?: new EventManager();
211
212 140
        $this->initialize();
213 140
        $this->_initializeParser($config);
214 140
        $this->_initializeRenderer($config);
215 140
        $this->_eventManager->on($this);
216 140
        $this->setExtensions($extensionRegistry);
0 ignored issues
show
Documentation introduced by
$extensionRegistry is of type null, but the function expects a object<CakeDC\Api\Service\ExtensionRegistry>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
217 140
        $this->_loadExtensions();
218 140
    }
219
220
    /**
221
     * Initialize method
222
     *
223
     * @return void
224
     */
225 140
    public function initialize()
226
    {
227 140
        if ($this->_name === null) {
228 46
            $className = (new \ReflectionClass($this))->getShortName();
229 46
            $this->setName(Inflector::underscore(str_replace('Service', '', $className)));
230 46
        }
231 140
    }
232
233
    /**
234
     * Gets service name.
235
     *
236
     * @return string
237
     */
238 139
    public function getName()
239
    {
240 139
        return $this->_name;
241
    }
242
243
    /**
244
     * Sets service name.
245
     *
246
     * @param string $name Service name.
247
     * @return $this
248
     */
249 140
    public function setName($name)
250
    {
251 140
        $this->_name = $name;
252
253 140
        return $this;
254
    }
255
256
    /**
257
     * Get and set service name.
258
     *
259
     * @param string $name Service name.
260
     * @deprecated 3.4.0 Use setName()/getName() instead.
261
     * @return string
262
     */
263
    public function name($name = null)
264
    {
265
        deprecationWarning(
266
            'Service::name() is deprecated. ' .
267
            'Use Service::setName()/getName() instead.'
268
        );
269
270
        if ($name !== null) {
271
            return $this->setName($name);
272
        }
273
274
        return $this->getName();
275
    }
276
277
    /**
278
     * Gets service version number.
279
     *
280
     * @return int
281
     */
282 81
    public function getVersion()
283
    {
284 81
        return $this->_version;
285
    }
286
287
    /**
288
     * Sets service version.
289
     *
290
     * @param int $version Version number.
291
     * @return void
292
     */
293
    public function setVersion($version)
294
    {
295
        $this->_version = $version;
296
    }
297
298
    /**
299
     * Get and set service version.
300
     *
301
     * @param int $version Version number.
302
     * @deprecated 3.4.0 Use setVersion()/getVersion() instead.
303
     * @return int|$this
304
     */
305
    public function version($version = null)
306
    {
307
        deprecationWarning(
308
            'Service::version() is deprecated. ' .
309
            'Use Service::setVersion()/getVersion() instead.'
310
        );
311
312
        if ($version !== null) {
313
            $this->setVersion($version);
314
        }
315
316
        return $this->getVersion();
317
    }
318
319
    /**
320
     * Gets the service parser.
321
     *
322
     * @return BaseParser
323
     */
324 66
    public function getParser()
325
    {
326 66
        return $this->_parser;
327
    }
328
329
    /**
330
     * Sets the service parser.
331
     *
332
     * @param BaseParser $parser A Parser instance.
333
     * @return $this
334
     */
335
    public function setParser(BaseParser $parser)
336
    {
337
        $this->_parser = $parser;
338
339
        return $this;
340
    }
341
342
    /**
343
     * Service parser configuration method.
344
     *
345
     * @param BaseParser $parser A Parser instance.
346
     * @deprecated 3.4.0 Use getParser()/setParser() instead.
347
     * @return BaseParser|$this
348
     */
349
    public function parser(BaseParser $parser = null)
350
    {
351
        deprecationWarning(
352
            'Service::parser() is deprecated. ' .
353
            'Use Service::setParser()/getParser() instead.'
354
        );
355
356
        if ($parser !== null) {
357
            return $this->setParser($parser);
358
        }
359
360
        return $this->getParser();
361
    }
362
363
    /**
364
     * Gets the Request.
365
     *
366
     * @return \Cake\Http\ServerRequest
367
     */
368 123
    public function getRequest()
369
    {
370 123
        return $this->_request;
371
    }
372
373
    /**
374
     * Sets the Request.
375
     *
376
     * @param \Cake\Http\ServerRequest $request A Request object.
377
     * @return void
378
     */
379 140
    public function setRequest(ServerRequest $request)
380
    {
381 140
        $this->_request = $request;
382 140
    }
383
384
    /**
385
     * Get and set request.
386
     *
387
     * @param \Cake\Http\ServerRequest $request A Request object.
388
     * @deprecated 3.4.0 Use getRequest()/setRequest() instead.
389
     * @return \Cake\Http\ServerRequest|$this
390
     */
391
    public function request($request = null)
392
    {
393
        deprecationWarning(
394
            'Service::request() is deprecated. ' .
395
            'Use Service::setRequest()/getRequest() instead.'
396
        );
397
398
        if ($request !== null) {
399
            $this->setRequest($request);
400
        }
401
402
        return $this->getRequest();
403
    }
404
405
    /**
406
     * Get the service route scopes and their connected routes.
407
     *
408
     * @return array
409
     */
410 3
    public function routes()
411
    {
412
        return $this->_routesWrapper(function () {
413 3
            return ApiRouter::routes();
414 3
        });
415
    }
416
417
    /**
418
     * @param callable $callable Wrapped router instance.
419
     * @return mixed
420
     */
421 65
    protected function _routesWrapper(callable $callable)
422
    {
423 65
        $this->resetRoutes();
424 65
        $this->loadRoutes();
425 65
        ApiRouter::$initialized = true;
426 65
        $result = $callable();
427 64
        $this->resetRoutes();
428
429 64
        return $result;
430
    }
431
432
    /**
433
     * Reset to default application routes.
434
     *
435
     * @return void
436
     */
437 65
    public function resetRoutes()
438
    {
439 65
        ApiRouter::reload();
440 65
    }
441
442
    /**
443
     * Initialize service level routes
444
     *
445
     * @return void
446
     */
447 8
    public function loadRoutes()
448
    {
449 8
        $defaultOptions = $this->routerDefaultOptions();
450
        ApiRouter::scope('/', $defaultOptions, function (RouteBuilder $routes) use ($defaultOptions) {
451 8
            if (is_array($this->_routeExtensions)) {
452 8
                $routes->setExtensions($this->_routeExtensions);
453 8
            }
454 8
            if (!empty($defaultOptions['map'])) {
455 8
                $routes->resources($this->getName(), $defaultOptions);
456 8
            }
457 8
        });
458 8
    }
459
460
    /**
461
     * Build router settings.
462
     * This implementation build action map for resource routes based on Service actions.
463
     *
464
     * @return array
465
     */
466 64
    public function routerDefaultOptions()
467
    {
468 64
        $mapList = [];
469 64
        foreach ($this->_actions as $alias => $map) {
470 43
            if (is_numeric($alias)) {
471
                $alias = $map;
472
                $map = [];
473
            }
474 43
            $mapCors = false;
475 43
            if (!empty($map['mapCors'])) {
476 9
                $mapCors = $map['mapCors'];
477 9
                unset($map['mapCors']);
478 9
            }
479 43
            $mapList[$alias] = $map;
480 43
            $mapList[$alias] += ['method' => 'GET', 'path' => '', 'action' => $alias];
481 43
            if ($mapCors) {
482 9
                $map['method'] = 'OPTIONS';
483 9
                $map += ['path' => '', 'action' => $alias . $this->_corsSuffix];
484 9
                $mapList[$alias . $this->_corsSuffix] = $map;
485 9
            }
486 64
        }
487
488
        return [
489
            'map' => $mapList
490 64
        ];
491
    }
492
493
    /**
494
     * Finds URL for specified action.
495
     *
496
     * Returns an URL pointing to a combination of controller and action.
497
     *
498
     * @param string|array|null $route An array specifying any of the following:
499
     *   'controller', 'action', 'plugin' additionally, you can provide routed
500
     *   elements or query string parameters. If string it can be name any valid url
501
     *   string.
502
     * @return string Full translated URL with base path.
503
     * @throws \Cake\Core\Exception\Exception When the route name is not found
504
     */
505
    public function routeUrl($route)
506
    {
507
        return $this->_routesWrapper(function () use ($route) {
508
            return ApiRouter::url($route);
509
        });
510
    }
511
512
    /**
513
     * Reverses a parsed parameter array into a string.
514
     *
515
     * @param \Cake\Http\ServerRequest|array $params The params array or
516
     *     Cake\Http\ServerRequest object that needs to be reversed.
517
     * @return string The string that is the reversed result of the array
518
     */
519 13
    public function routeReverse($params)
520
    {
521
        return $this->_routesWrapper(function () use ($params) {
522
            try {
523 13
                return ApiRouter::reverse($params);
524 1
            } catch (Exception $e) {
525 1
                return null;
526
            }
527 13
        });
528
    }
529
530
    /**
531
     * Dispatch service call.
532
     *
533
     * @return \CakeDC\Api\Service\Action\Result
534
     */
535 56
    public function dispatch()
536
    {
537
        try {
538 56
            $result = $this->_dispatch();
539
540 52
            if ($result instanceof Result) {
541
                $this->setResult($result);
542
            } else {
543 52
                $this->getResult()->setData($result);
544 52
                $this->getResult()->setCode(200);
545
            }
546 56
        } catch (RecordNotFoundException $e) {
547 6
            $this->getResult()->setCode(404);
548 6
            $this->getResult()->setException($e);
549 8
        } catch (ValidationException $e) {
550 1
            $this->getResult()->setCode(422);
551 1
            $this->getResult()->setException($e);
552 2
        } catch (Exception $e) {
553 1
            $code = $e->getCode();
554 1
            if (!is_int($code) || $code < 100 || $code >= 600) {
555
                $this->getResult()->setCode(500);
556
            }
557 1
            $this->getResult()->setException($e);
558
        }
559 56
        $this->dispatchEvent('Service.afterDispatch', ['service' => $this]);
560
561 56
        return $this->getResult();
562
    }
563
564
    /**
565
     * Dispatch service call through callbacks and action.
566
     *
567
     * @return Result|mixed
568
     */
569 56
    protected function _dispatch()
570
    {
571 56
        $event = $this->dispatchEvent('Service.beforeDispatch', ['service' => $this]);
572 56
        if ($event->result instanceof Result) {
573
            return $event->result;
574
        }
575
576 56
        $action = $this->buildAction();
577 56
        $this->dispatchEvent('Service.beforeProcess', ['service' => $this, 'action' => $this]);
578 56
        if ($event->result instanceof Result) {
579
            return $event->result;
580
        }
581
582 56
        return $action->process();
583
    }
584
585
    /**
586
     * Build action instance
587
     *
588
     * @return \CakeDC\Api\Service\Action\Action
589
     * @throws Exception
590
     */
591 64
    public function buildAction()
592
    {
593 64
        $route = $this->parseRoute($this->getBaseUrl());
594 63
        if (empty($route)) {
595
            throw new MissingActionException('Invalid Action Route:' . $this->getBaseUrl()); // InvalidActionException
596
        }
597 63
        $service = null;
598 63
        $serviceName = Inflector::underscore($route['controller']);
599 63
        if ($serviceName == $this->getName()) {
600 54
            $service = $this;
601 54
        }
602 63
        if (in_array($serviceName, $this->_innerServices)) {
603
            $options = [
604 9
                'version' => $this->getVersion(),
605 9
                'request' => $this->getRequest(),
606 9
                'response' => $this->getResponse(),
607 9
                'refresh' => true,
608 9
            ];
609 9
            $service = ServiceRegistry::getServiceLocator()->get($serviceName, $options);
610 9
            $service->setParentService($this);
611 9
        }
612 63
        $action = $route['action'];
613 63
        list($namespace, $serviceClass) = namespaceSplit(get_class($service));
614 63
        $actionPrefix = substr($serviceClass, 0, -7);
615 63
        $actionClass = $namespace . '\\Action\\' . $actionPrefix . Inflector::camelize($action) . 'Action';
616 63
        if (class_exists($actionClass)) {
617 2
            return $service->buildActionClass($actionClass, $route);
618
        }
619 61
        $actionsClassMap = $service->getActionsClassMap();
620 61
        if (array_key_exists($action, $actionsClassMap)) {
621 61
            return $service->buildActionClass($actionsClassMap[$action], $route);
622
        }
623
        throw new MissingActionException(['class' => $actionClass]);
624
    }
625
626
    /**
627
     * Parses given URL string. Returns 'routing' parameters for that URL.
628
     *
629
     * @param string $url URL to be parsed
630
     * @return array Parsed elements from URL
631
     * @throws \Cake\Routing\Exception\MissingRouteException When a route cannot be handled
632
     */
633
    public function parseRoute($url)
634
    {
635 64
        return $this->_routesWrapper(function () use ($url) {
636 64
            return ApiRouter::parseRequest(new ServerRequest([
637 64
                'url' => $url,
638
                'environment' => [
639 64
                    'REQUEST_METHOD' => $this->_request->getEnv('REQUEST_METHOD')
640 64
                ]
641 64
            ]));
642 64
        });
643
    }
644
645
    /**
646
     * Returns action class map.
647
     *
648
     * @return array
649
     */
650 61
    public function getActionsClassMap()
651
    {
652 61
        return $this->_actionsClassMap;
653
    }
654
655
    /**
656
     * Build base url
657
     *
658
     * @return string
659
     */
660 65
    public function getBaseUrl()
661
    {
662 65
        if (!empty($this->_baseUrl)) {
663 65
            return $this->_baseUrl;
664
        }
665
666
        $result = '/' . $this->getName();
667
668
        return $result;
669
    }
670
671
    /**
672
     * Gets the parent service method.
673
     *
674
     * @return Service
675
     */
676 55
    public function getParentService()
677
    {
678 55
        return $this->_parentService;
679
    }
680
681
    /**
682
     * Sets the parent service method.
683
     *
684
     * @param Service $parentService Parent Service
685
     * @return $this
686
     */
687 9
    public function setParentService(Service $parentService)
688
    {
689 9
        $this->_parentService = $parentService;
690
691 9
        return $this;
692
    }
693
694
    /**
695
     * Parent service get and set methods.
696
     *
697
     * @param Service $service Parent Service instance.
698
     * @deprecated 3.4.0 Use getParentService()/setParentService() instead.
699
     * @return Service|$this
700
     */
701
    public function parent(Service $service = null)
702
    {
703
        deprecationWarning(
704
            'Service::parent() is deprecated. ' .
705
            'Use Service::setParentService()/getParentService() instead.'
706
        );
707
708
        if ($service !== null) {
709
            return $this->setParentService($service);
710
        }
711
712
        return $this->getParentService();
713
    }
714
715
    /**
716
     * Build action class
717
     *
718
     * @param string $class Class name.
719
     * @param array $route Activated route.
720
     * @return mixed
721
     */
722 64
    public function buildActionClass($class, $route)
723
    {
724 64
        $instance = new $class($this->_actionOptions($route));
725
726 64
        return $instance;
727
    }
728
729
    /**
730
     * Action constructor options.
731
     *
732
     * @param array $route Activated route.
733
     * @return array
734
     */
735 64
    protected function _actionOptions($route)
736
    {
737 64
        $actionName = $route['action'];
738
739
        $options = [
740 64
            'name' => $actionName,
741 64
            'service' => $this,
742 64
            'route' => $route,
743 64
        ];
744 64
        $options += (new ConfigReader())->actionOptions($this->getName(), $actionName, $this->getVersion());
745
746 64
        return $options;
747
    }
748
749
    /**
750
     * Gets the result for service.
751
     *
752
     * @return Result
753
     */
754 56
    public function getResult()
755
    {
756 56
        if ($this->_parentService !== null) {
757 2
            return $this->_parentService->getResult();
758
        }
759 56
        if ($this->_result === null) {
760 56
            $this->_result = new Result();
761 56
        }
762
763 56
        return $this->_result;
764
    }
765
766
    /**
767
     * Sets the result for service.
768
     *
769
     * @param Result $result A Result object.
770
     * @return $this
771
     */
772
    public function setResult(Result $result)
773
    {
774
        if ($this->_parentService !== null) {
775
            $this->_parentService->setResult($result);
776
777
            return $this;
778
        }
779
        $this->_result = $result;
780
781
        return $this;
782
    }
783
784
    /**
785
     * @param null $value value
786
     * @deprecated 3.4.0 Use getResult()/setResult() instead.
787
     * @return Result
788
     */
789
    public function result($value = null)
790
    {
791
        deprecationWarning(
792
            'Service::result() is deprecated. ' .
793
            'Use Service::setResult()/getResult() instead.'
794
        );
795
796
        if ($value !== null) {
797
            return $this->setResult($value);
0 ignored issues
show
Documentation introduced by
$value is of type null, but the function expects a object<CakeDC\Api\Service\Action\Result>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug Best Practice introduced by
The return type of return $this->setResult($value); (CakeDC\Api\Service\Service) is incompatible with the return type documented by CakeDC\Api\Service\Service::result of type CakeDC\Api\Service\Action\Result.

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...
798
        }
799
800
        return $this->getResult();
801
    }
802
803
    /**
804
     * Fill up response and stop execution.
805
     *
806
     * @param Result $result A Result instance.
807
     * @return Response
808
     */
809 56
    public function respond($result = null)
810
    {
811 56
        if ($result === null) {
812
            $result = $this->getResult();
813
        }
814 56
        $this->setResponse($this->getResponse()->withStatus($result->getCode()));
815 56
        if ($result->getException() !== null) {
816 8
            $this->getRenderer()
817 8
                 ->error($result->getException());
818 8
        } else {
819 52
            $this->getRenderer()
820 52
                 ->response($result);
821
        }
822
823 56
        return $this->getResponse();
824
    }
825
826
    /**
827
     * Gets the response.
828
     *
829
     * @return \Cake\Http\Response
830
     */
831 131
    public function getResponse()
832
    {
833 131
        return $this->_response;
834
    }
835
836
    /**
837
     * Sets the response.
838
     *
839
     * @param \Cake\Http\Response $response Response
840
     * @return $this
841
     */
842 140
    public function setResponse(Response $response)
843
    {
844 140
        $this->_response = $response;
845
846 140
        return $this;
847
    }
848
849
    /**
850
     * Get and set response.
851
     *
852
     * @param \Cake\Http\Response $response  A Response object.
853
     * @deprecated 3.4.0 Use getResponse()/setResponse() instead.
854
     * @return \Cake\Http\Response
855
     */
856
    public function response(Response $response = null)
857
    {
858
        deprecationWarning(
859
            'Service::response() is deprecated. ' .
860
            'Use Service::setResponse()/getResponse() instead.'
861
        );
862
863
        if ($response !== null) {
864
            return $this->setResponse($response);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->setResponse($response); (CakeDC\Api\Service\Service) is incompatible with the return type documented by CakeDC\Api\Service\Service::response of type Cake\Http\Response.

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...
865
        }
866
867
        return $this->getResponse();
868
    }
869
870
    /**
871
     * Gets the service renderer.
872
     *
873
     * @return BaseRenderer
874
     */
875 68
    public function getRenderer()
876
    {
877 68
        return $this->_renderer;
878
    }
879
880
    /**
881
     * Sets the service renderer.
882
     *
883
     * @param BaseRenderer $renderer Rendered
884
     * @return $this
885
     */
886 140
    public function setRenderer(BaseRenderer $renderer)
887
    {
888 140
        $this->_renderer = $renderer;
889
890 140
        return $this;
891
    }
892
893
    /**
894
     * Service renderer configuration method.
895
     *
896
     * @param BaseRenderer $renderer A Renderer instance.
897
     * @deprecated 3.4.0 Use getRenderer()/setRenderer() instead.
898
     * @return BaseRenderer|$this
899
     */
900
    public function renderer(BaseRenderer $renderer = null)
901
    {
902
        deprecationWarning(
903
            'Service::renderer() is deprecated. ' .
904
            'Use Service::setRenderer()/getRenderer() instead.'
905
        );
906
907
        if ($renderer !== null) {
908
            return $this->setRenderer($renderer);
909
        }
910
911
        return $this->getRenderer();
912
    }
913
914
    /**
915
     * Define action config.
916
     *
917
     * @param string $actionName Action name.
918
     * @param string $className Class name.
919
     * @param array $route Route config.
920
     * @return void
921
     */
922 23
    public function mapAction($actionName, $className, $route)
923
    {
924 23
        $route += ['mapCors' => false];
925 23
        $this->_actionsClassMap[$actionName] = $className;
926 23
        if ($route['mapCors']) {
927 23
            $this->_actionsClassMap[$actionName . $this->_corsSuffix] = DummyAction::class;
928 23
        }
929 23
        if (!isset($route['path'])) {
930 8
            $route['path'] = $actionName;
931 8
        }
932 23
        $this->_actions[$actionName] = $route;
933 23
    }
934
935
    /**
936
     * Lists supported events.
937
     *
938
     * @return array
939
     */
940 140
    public function implementedEvents()
941
    {
942
        $eventMap = [
943 140
            'Service.beforeDispatch' => 'beforeDispatch',
944 140
            'Service.beforeProcess' => 'beforeProcess',
945 140
            'Service.afterDispatch' => 'afterDispatch',
946 140
        ];
947 140
        $events = [];
948
949 140
        foreach ($eventMap as $event => $method) {
950 140
            if (!method_exists($this, $method)) {
951 140
                continue;
952
            }
953
            $events[$event] = $method;
954 140
        }
955
956 140
        return $events;
957
    }
958
959
    /**
960
     * Gets the extension registry instance.
961
     *
962
     * @return \CakeDC\Api\Service\ExtensionRegistry
963
     */
964
    public function getExtensions()
965
    {
966
        if ($this->_extensions === null) {
967
            $this->_extensions = new ExtensionRegistry($this);
968
        }
969
970
        return $this->_extensions;
971
    }
972
973
    /**
974
     * Sets the extension registry for this service.
975
     *
976
     * @param \CakeDC\Api\Service\ExtensionRegistry $extensions The extension registry instance.
977
     * @return $this
978
     */
979 140
    public function setExtensions($extensions)
980
    {
981 140
        if ($extensions === null) {
982 140
            $extensions = new ExtensionRegistry($this);
983 140
        }
984 140
        $this->_extensions = $extensions;
985
986 140
        return $this;
987
    }
988
989
    /**
990
     * Get the extension registry for this service.
991
     *
992
     * If called with the first parameter, it will be set as the action $this->_extensions property
993
     *
994
     * @param \CakeDC\Api\Service\ExtensionRegistry|null $extensions Extension registry.
995
     * @deprecated 3.4.0 Use getExtensions()/setExtensions() instead.
996
     * @return \CakeDC\Api\Service\ExtensionRegistry|$this
997
     */
998
    public function extensions($extensions = null)
999
    {
1000
        deprecationWarning(
1001
            'Service::extensions() is deprecated. ' .
1002
            'Use Service::setExtensions()/getExtensions() instead.'
1003
        );
1004
1005
        if ($extensions !== null) {
1006
            $this->setExtensions($extensions);
1007
        }
1008
1009
        return $this->getExtensions();
1010
    }
1011
1012
    /**
1013
     * Loads the defined extensions using the Extension factory.
1014
     *
1015
     * @return void
1016
     */
1017 140
    protected function _loadExtensions()
1018
    {
1019 140
        if (empty($this->extensions)) {
1020 140
            return;
1021
        }
1022
        $registry = $this->getExtensions();
1023
        $extensions = $registry->normalizeArray($this->extensions);
1024
        foreach ($extensions as $properties) {
1025
            $instance = $registry->load($properties['class'], $properties['config']);
1026
            $this->_eventManager->on($instance);
1027
        }
1028
    }
1029
1030
    /**
1031
     * Initialize parser.
1032
     *
1033
     * @param array $config Service options
1034
     * @return void
1035
     */
1036 140
    protected function _initializeParser(array $config)
1037
    {
1038 140
        if (empty($this->_parserClass) && isset($config['parserClass'])) {
1039
            $this->_parserClass = $config['parserClass'];
1040
        }
1041 140
        $parserClass = Configure::read('Api.parser');
1042 140
        if (empty($this->_parserClass) && !empty($parserClass)) {
1043 140
            $this->_parserClass = $parserClass;
1044 140
        }
1045
1046 140
        $class = App::className($this->_parserClass, 'Service/RequestParser', 'Parser');
1047 140
        if (!class_exists($class)) {
1048
            throw new MissingParserException(['class' => $this->_parserClass]);
1049
        }
1050 140
        $this->_parser = new $class($this);
1051 140
    }
1052
1053
    /**
1054
     * Initialize renderer.
1055
     *
1056
     * @param array $config Service options.
1057
     * @return void
1058
     */
1059 140
    protected function _initializeRenderer(array $config)
1060
    {
1061 140
        if (empty($this->_rendererClass) && isset($config['rendererClass'])) {
1062 13
            $this->_rendererClass = $config['rendererClass'];
1063 13
        }
1064 140
        $rendererClass = Configure::read('Api.renderer');
1065 140
        if (empty($this->_rendererClass) && !empty($rendererClass)) {
1066 127
            $this->_rendererClass = $rendererClass;
1067 127
        }
1068
1069 140
        $class = App::className($this->_rendererClass, 'Service/Renderer', 'Renderer');
1070 140
        if (!class_exists($class)) {
1071
            throw new MissingRendererException(['class' => $this->_rendererClass]);
1072
        }
1073 140
        $this->setRenderer(new $class($this));
1074 140
    }
1075
}
1076