Completed
Pull Request — master (#40)
by
unknown
10:59
created

Service::response()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 3
cts 4
cp 0.75
crap 2.0625
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright 2016 - 2017, 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 - 2017, 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)));
0 ignored issues
show
Bug introduced by
It seems like \Cake\Utility\Inflector:...vice', '', $className)) targeting Cake\Utility\Inflector::underscore() can also be of type boolean; however, CakeDC\Api\Service\Service::setName() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
        if ($name !== null) {
266
            return $this->setName($name);
267
        }
268
269
        return $this->getName();
270
    }
271
272
    /**
273
     * Gets service version number.
274
     *
275
     * @return int
276
     */
277 81
    public function getVersion()
278
    {
279 81
        return $this->_version;
280
    }
281
282
    /**
283
     * Sets service version.
284
     *
285
     * @param int $version Version number.
286
     * @return void
287
     */
288
    public function setVersion($version)
289
    {
290
        $this->_version = $version;
291
    }
292
293
    /**
294
     * Get and set service version.
295
     *
296
     * @param int $version Version number.
297
     * @deprecated 3.4.0 Use setVersion()/getVersion() instead.
298
     * @return int|$this
299
     */
300
    public function version($version = null)
301
    {
302
        if ($version !== null) {
303
            return $this->setVersion($version);
304
        }
305
306
        return $this->getVersion();
307
    }
308
309
    /**
310
     * Gets the service parser.
311
     *
312
     * @return BaseParser
313
     */
314 66
    public function getParser()
315
    {
316 66
        return $this->_parser;
317
    }
318
319
    /**
320
     * Sets the service parser.
321
     *
322
     * @param BaseParser $parser A Parser instance.
323
     * @return $this
324
     */
325
    public function setParser(BaseParser $parser)
326
    {
327
        $this->_parser = $parser;
328
329
        return $this;
330
    }
331
332
    /**
333
     * Service parser configuration method.
334
     *
335
     * @param BaseParser $parser A Parser instance.
336
     * @deprecated 3.4.0 Use getParser()/setParser() instead.
337
     * @return BaseParser|$this
338
     */
339
    public function parser(BaseParser $parser = null)
340
    {
341
        if ($parser !== null) {
342
            return $this->setParser($parser);
343
        }
344
345
        return $this->getParser();
346
    }
347
348
    /**
349
     * Gets the Request.
350
     *
351
     * @return \Cake\Http\ServerRequest
352
     */
353 123
    public function getRequest()
354
    {
355 123
        return $this->_request;
356
    }
357
358
    /**
359
     * Sets the Request.
360
     *
361
     * @param \Cake\Http\ServerRequest $request A Request object.
362
     * @return void
363
     */
364 140
    public function setRequest(ServerRequest $request)
365
    {
366 140
        $this->_request = $request;
367 140
    }
368
369
    /**
370
     * Get and set request.
371
     *
372
     * @param \Cake\Http\ServerRequest $request A Request object.
373
     * @deprecated 3.4.0 Use getRequest()/setRequest() instead.
374
     * @return \Cake\Http\ServerRequest|$this
375
     */
376 1
    public function request($request = null)
377
    {
378 1
        if ($request !== null) {
379
            return $this->setRequest($request);
380
        }
381
382 1
        return $this->getRequest();
383
    }
384
385
    /**
386
     * Get the service route scopes and their connected routes.
387
     *
388
     * @return array
389
     */
390 3
    public function routes()
391
    {
392
        return $this->_routesWrapper(function () {
393 3
            return ApiRouter::routes();
394 3
        });
395
    }
396
397
    /**
398
     * @param callable $callable Wrapped router instance.
399
     * @return mixed
400
     */
401 65
    protected function _routesWrapper(callable $callable)
402
    {
403 65
        $this->resetRoutes();
404 65
        $this->loadRoutes();
405 65
        ApiRouter::$initialized = true;
406 65
        $result = $callable();
407 64
        $this->resetRoutes();
408
409 64
        return $result;
410
    }
411
412
    /**
413
     * Reset to default application routes.
414
     *
415
     * @return void
416
     */
417 65
    public function resetRoutes()
418
    {
419 65
        ApiRouter::reload();
420 65
    }
421
422
    /**
423
     * Initialize service level routes
424
     *
425
     * @return void
426
     */
427 8
    public function loadRoutes()
428
    {
429 8
        $defaultOptions = $this->routerDefaultOptions();
430
        ApiRouter::scope('/', $defaultOptions, function (RouteBuilder $routes) use ($defaultOptions) {
431 8
            if (is_array($this->_routeExtensions)) {
432 8
                $routes->extensions($this->_routeExtensions);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Routing\RouteBuilder::extensions() has been deprecated with message: 3.5.0 Use getExtensions/setExtensions instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
433 8
            }
434 8
            if (!empty($defaultOptions['map'])) {
435 8
                $routes->resources($this->getName(), $defaultOptions);
436 8
            }
437 8
        });
438 8
    }
439
440
    /**
441
     * Build router settings.
442
     * This implementation build action map for resource routes based on Service actions.
443
     *
444
     * @return array
445
     */
446 64
    public function routerDefaultOptions()
447
    {
448 64
        $mapList = [];
449 64
        foreach ($this->_actions as $alias => $map) {
450 43
            if (is_numeric($alias)) {
451
                $alias = $map;
452
                $map = [];
453
            }
454 43
            $mapCors = false;
455 43
            if (!empty($map['mapCors'])) {
456 9
                $mapCors = $map['mapCors'];
457 9
                unset($map['mapCors']);
458 9
            }
459 43
            $mapList[$alias] = $map;
460 43
            $mapList[$alias] += ['method' => 'GET', 'path' => '', 'action' => $alias];
461 43
            if ($mapCors) {
462 9
                $map['method'] = 'OPTIONS';
463 9
                $map += ['path' => '', 'action' => $alias . $this->_corsSuffix];
464 9
                $mapList[$alias . $this->_corsSuffix] = $map;
465 9
            }
466 64
        }
467
468
        return [
469
            'map' => $mapList
470 64
        ];
471
    }
472
473
    /**
474
     * Finds URL for specified action.
475
     *
476
     * Returns an URL pointing to a combination of controller and action.
477
     *
478
     * @param string|array|null $route An array specifying any of the following:
479
     *   'controller', 'action', 'plugin' additionally, you can provide routed
480
     *   elements or query string parameters. If string it can be name any valid url
481
     *   string.
482
     * @return string Full translated URL with base path.
483
     * @throws \Cake\Core\Exception\Exception When the route name is not found
484
     */
485
    public function routeUrl($route)
486
    {
487
        return $this->_routesWrapper(function () use ($route) {
488
            return ApiRouter::url($route);
489
        });
490
    }
491
492
    /**
493
     * Reverses a parsed parameter array into a string.
494
     *
495
     * @param \Cake\Http\ServerRequest|array $params The params array or
496
     *     Cake\Http\ServerRequest object that needs to be reversed.
497
     * @return string The string that is the reversed result of the array
498
     */
499 13
    public function routeReverse($params)
500
    {
501
        return $this->_routesWrapper(function () use ($params) {
502
            try {
503 13
                return ApiRouter::reverse($params);
504 1
            } catch (Exception $e) {
505 1
                return null;
506
            }
507 13
        });
508
    }
509
510
    /**
511
     * Dispatch service call.
512
     *
513
     * @return \CakeDC\Api\Service\Action\Result
514
     */
515 56
    public function dispatch()
516
    {
517
        try {
518 56
            $result = $this->_dispatch();
519
520 52
            if ($result instanceof Result) {
521
                $this->setResult($result);
522
            } else {
523 52
                $this->getResult()->data($result);
524 52
                $this->getResult()->code(200);
525
            }
526 56
        } catch (RecordNotFoundException $e) {
527 6
            $this->getResult()->code(404);
528 6
            $this->getResult()->exception($e);
529 8
        } catch (ValidationException $e) {
530 1
            $this->getResult()->code(422);
531 1
            $this->getResult()->exception($e);
532 2
        } catch (Exception $e) {
533 1
            $code = $e->getCode();
534 1
            if (!is_int($code) || $code < 100 || $code >= 600) {
535
                $this->getResult()->code(500);
536
            }
537 1
            $this->getResult()->exception($e);
538
        }
539 56
        $this->dispatchEvent('Service.afterDispatch', ['service' => $this]);
540
541 56
        return $this->getResult();
542
    }
543
544
    /**
545
     * Dispatch service call through callbacks and action.
546
     *
547
     * @return Result|mixed
548
     */
549 56
    protected function _dispatch()
550
    {
551 56
        $event = $this->dispatchEvent('Service.beforeDispatch', ['service' => $this]);
552 56
        if ($event->result instanceof Result) {
553
            return $event->result;
554
        }
555
556 56
        $action = $this->buildAction();
557 56
        $this->dispatchEvent('Service.beforeProcess', ['service' => $this, 'action' => $this]);
558 56
        if ($event->result instanceof Result) {
559
            return $event->result;
560
        }
561
562 56
        return $action->process();
563
    }
564
565
    /**
566
     * Build action instance
567
     *
568
     * @return \CakeDC\Api\Service\Action\Action
569
     * @throws Exception
570
     */
571 64
    public function buildAction()
572
    {
573 64
        $route = $this->parseRoute($this->getBaseUrl());
574 63
        if (empty($route)) {
575
            throw new MissingActionException('Invalid Action Route:' . $this->getBaseUrl()); // InvalidActionException
576
        }
577 63
        $service = null;
578 63
        $serviceName = Inflector::underscore($route['controller']);
579 63
        if ($serviceName == $this->getName()) {
580 54
            $service = $this;
581 54
        }
582 63
        if (in_array($serviceName, $this->_innerServices)) {
583
            $options = [
584 9
                'version' => $this->getVersion(),
585 9
                'request' => $this->getRequest(),
586 9
                'response' => $this->getResponse(),
587 9
                'refresh' => true,
588 9
            ];
589 9
            $service = ServiceRegistry::get($serviceName, $options);
0 ignored issues
show
Bug introduced by
It seems like $serviceName defined by \Cake\Utility\Inflector:...e($route['controller']) on line 578 can also be of type boolean; however, CakeDC\Api\Service\ServiceRegistry::get() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
590 9
            $service->setParentService($this);
591 9
        }
592 63
        $action = $route['action'];
593 63
        list($namespace, $serviceClass) = namespaceSplit(get_class($service));
594 63
        $actionPrefix = substr($serviceClass, 0, -7);
595 63
        $actionClass = $namespace . '\\Action\\' . $actionPrefix . Inflector::camelize($action) . 'Action';
596 63
        if (class_exists($actionClass)) {
597 2
            return $service->buildActionClass($actionClass, $route);
598
        }
599 61
        $actionsClassMap = $service->getActionsClassMap();
600 61
        if (array_key_exists($action, $actionsClassMap)) {
601 61
            return $service->buildActionClass($actionsClassMap[$action], $route);
602
        }
603
        throw new MissingActionException(['class' => $actionClass]);
604
    }
605
606
    /**
607
     * Parses given URL string. Returns 'routing' parameters for that URL.
608
     *
609
     * @param string $url URL to be parsed
610
     * @return array Parsed elements from URL
611
     * @throws \Cake\Routing\Exception\MissingRouteException When a route cannot be handled
612
     */
613
    public function parseRoute($url)
614
    {
615 64
        return $this->_routesWrapper(function () use ($url) {
616 64
            return ApiRouter::parseRequest(new ServerRequest([
617 64
                'url' => $url,
618
                'environment' => [
619 64
                    'REQUEST_METHOD' => $this->_request->env('REQUEST_METHOD')
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\ServerRequest::env() has been deprecated with message: 3.5.0 Use getEnv()/withEnv() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
620 64
                ]
621 64
            ]));
622 64
        });
623
    }
624
625
    /**
626
     * Returns action class map.
627
     *
628
     * @return array
629
     */
630 61
    public function getActionsClassMap()
631
    {
632 61
        return $this->_actionsClassMap;
633
    }
634
635
    /**
636
     * Build base url
637
     *
638
     * @return string
639
     */
640 65
    public function getBaseUrl()
641
    {
642 65
        if (!empty($this->_baseUrl)) {
643 65
            return $this->_baseUrl;
644
        }
645
646
        $result = '/' . $this->getName();
647
648
        return $result;
649
    }
650
651
    /**
652
     * Gets the parent service method.
653
     *
654
     * @return Service
655
     */
656 55
    public function getParentService()
657
    {
658 55
        return $this->_parentService;
659
    }
660
661
    /**
662
     * Sets the parent service method.
663
     *
664
     * @param Service $parentService Parent Service
665
     * @return $this
666
     */
667 9
    public function setParentService(Service $parentService)
668
    {
669 9
        $this->_parentService = $parentService;
670
671 9
        return $this;
672
    }
673
674
    /**
675
     * Parent service get and set methods.
676
     *
677
     * @param Service $service Parent Service instance.
678
     * @deprecated 3.4.0 Use getParentService()/setParentService() instead.
679
     * @return Service|$this
680
     */
681 4
    public function parent(Service $service = null)
682
    {
683 4
        if ($service !== null) {
684
            return $this->setParentService($service);
685
        }
686
687 4
        return $this->getParentService();
688
    }
689
690
    /**
691
     * Build action class
692
     *
693
     * @param string $class Class name.
694
     * @param array $route Activated route.
695
     * @return mixed
696
     */
697 64
    public function buildActionClass($class, $route)
698
    {
699 64
        $instance = new $class($this->_actionOptions($route));
700
701 64
        return $instance;
702
    }
703
704
    /**
705
     * Action constructor options.
706
     *
707
     * @param array $route Activated route.
708
     * @return array
709
     */
710 64
    protected function _actionOptions($route)
711
    {
712 64
        $actionName = $route['action'];
713
714
        $options = [
715 64
            'name' => $actionName,
716 64
            'service' => $this,
717 64
            'route' => $route,
718 64
        ];
719 64
        $options += (new ConfigReader())->actionOptions($this->getName(), $actionName, $this->getVersion());
720
721 64
        return $options;
722
    }
723
724
    /**
725
     * Gets the result for service.
726
     *
727
     * @return Result
728
     */
729 56
    public function getResult()
730
    {
731 56
        if ($this->_parentService !== null) {
732 2
            return $this->_parentService->getResult();
733
        }
734 56
        if ($this->_result === null) {
735 56
            $this->_result = new Result();
736 56
        }
737
738 56
        return $this->_result;
739
    }
740
741
    /**
742
     * Sets the result for service.
743
     *
744
     * @param Result $result A Result object.
745
     * @return $this
746
     */
747
    public function setResult(Result $result)
748
    {
749
        if ($this->_parentService !== null) {
750
            $this->_parentService->setResult($result);
751
752
            return $this;
753
        }
754
        $this->_result = $result;
755
756
        return $this;
757
    }
758
759
    /**
760
     * @param null $value value
761
     * @deprecated 3.4.0 Use getResult()/setResult() instead.
762
     * @return Result
763
     */
764 30
    public function result($value = null)
765
    {
766 30
        if ($value !== null) {
767
            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...
768
        }
769
770 30
        return $this->getResult();
771
    }
772
773
    /**
774
     * Fill up response and stop execution.
775
     *
776
     * @param Result $result A Result instance.
777
     * @return Response
778
     */
779 56
    public function respond($result = null)
780
    {
781 56
        if ($result === null) {
782
            $result = $this->getResult();
783
        }
784 56
        $this->setResponse($this->getResponse()->withStatus($result->code()));
785 56
        if ($result->exception() !== null) {
786 8
            $this->getRenderer()
787 8
                 ->error($result->exception());
788 8
        } else {
789 52
            $this->getRenderer()
790 52
                 ->response($result);
791
        }
792
793 56
        return $this->getResponse();
794
    }
795
796
    /**
797
     * Gets the response.
798
     *
799
     * @return \Cake\Http\Response
800
     */
801 131
    public function getResponse()
802
    {
803 131
        return $this->_response;
804
    }
805
806
    /**
807
     * Sets the response.
808
     *
809
     * @param \Cake\Http\Response $response Response
810
     * @return $this
811
     */
812 140
    public function setResponse(Response $response)
813
    {
814 140
        $this->_response = $response;
815
816 140
        return $this;
817
    }
818
819
    /**
820
     * Get and set response.
821
     *
822
     * @param \Cake\Http\Response $response  A Response object.
823
     * @deprecated 3.4.0 Use getResponse()/setResponse() instead.
824
     * @return \Cake\Http\Response
825
     */
826 1
    public function response(Response $response = null)
827
    {
828 1
        if ($response !== null) {
829
            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...
830
        }
831
832 1
        return $this->getResponse();
833
    }
834
835
    /**
836
     * Gets the service renderer.
837
     *
838
     * @return BaseRenderer
839
     */
840 68
    public function getRenderer()
841
    {
842 68
        return $this->_renderer;
843
    }
844
845
    /**
846
     * Sets the service renderer.
847
     *
848
     * @param BaseRenderer $renderer Rendered
849
     * @return $this
850
     */
851 140
    public function setRenderer(BaseRenderer $renderer)
852
    {
853 140
        $this->_renderer = $renderer;
854
855 140
        return $this;
856
    }
857
858
    /**
859
     * Service renderer configuration method.
860
     *
861
     * @param BaseRenderer $renderer A Renderer instance.
862
     * @deprecated 3.4.0 Use getRenderer()/setRenderer() instead.
863
     * @return BaseRenderer|$this
864
     */
865
    public function renderer(BaseRenderer $renderer = null)
866
    {
867
        if ($renderer !== null) {
868
            return $this->setRenderer($renderer);
869
        }
870
871
        return $this->getRenderer();
872
    }
873
874
    /**
875
     * Define action config.
876
     *
877
     * @param string $actionName Action name.
878
     * @param string $className Class name.
879
     * @param array $route Route config.
880
     * @return void
881
     */
882 23
    public function mapAction($actionName, $className, $route)
883
    {
884 23
        $route += ['mapCors' => false];
885 23
        $this->_actionsClassMap[$actionName] = $className;
886 23
        if ($route['mapCors']) {
887 23
            $this->_actionsClassMap[$actionName . $this->_corsSuffix] = DummyAction::class;
888 23
        }
889 23
        if (!isset($route['path'])) {
890 8
            $route['path'] = $actionName;
891 8
        }
892 23
        $this->_actions[$actionName] = $route;
893 23
    }
894
895
    /**
896
     * Lists supported events.
897
     *
898
     * @return array
899
     */
900 140
    public function implementedEvents()
901
    {
902
        $eventMap = [
903 140
            'Service.beforeDispatch' => 'beforeDispatch',
904 140
            'Service.beforeProcess' => 'beforeProcess',
905 140
            'Service.afterDispatch' => 'afterDispatch',
906 140
        ];
907 140
        $events = [];
908
909 140
        foreach ($eventMap as $event => $method) {
910 140
            if (!method_exists($this, $method)) {
911 140
                continue;
912
            }
913
            $events[$event] = $method;
914 140
        }
915
916 140
        return $events;
917
    }
918
919
    /**
920
     * Gets the extension registry instance.
921
     *
922
     * @return \CakeDC\Api\Service\ExtensionRegistry
923
     */
924
    public function getExtensions()
925
    {
926
        if ($this->_extensions === null) {
927
            $this->_extensions = new ExtensionRegistry($this);
928
        }
929
930
        return $this->_extensions;
931
    }
932
933
    /**
934
     * Sets the extension registry for this service.
935
     *
936
     * @param \CakeDC\Api\Service\ExtensionRegistry $extensions The extension registry instance.
937
     * @return $this
938
     */
939 140
    public function setExtensions($extensions)
940
    {
941 140
        if ($extensions === null) {
942 140
            $extensions = new ExtensionRegistry($this);
943 140
        }
944 140
        $this->_extensions = $extensions;
945
946 140
        return $this;
947
    }
948
949
    /**
950
     * Get the extension registry for this service.
951
     *
952
     * If called with the first parameter, it will be set as the action $this->_extensions property
953
     *
954
     * @param \CakeDC\Api\Service\ExtensionRegistry|null $extensions Extension registry.
955
     * @deprecated 3.4.0 Use getExtensions()/setExtensions() instead.
956
     * @return \CakeDC\Api\Service\ExtensionRegistry|$this
957
     */
958
    public function extensions($extensions = null)
959
    {
960
        if ($extensions !== null) {
961
            $this->setExtensions($extensions);
962
        }
963
964
        return $this->getExtensions();
965
    }
966
967
    /**
968
     * Loads the defined extensions using the Extension factory.
969
     *
970
     * @return void
971
     */
972 140
    protected function _loadExtensions()
973
    {
974 140
        if (empty($this->extensions)) {
975 140
            return;
976
        }
977
        $registry = $this->getExtensions();
978
        $extensions = $registry->normalizeArray($this->extensions);
979
        foreach ($extensions as $properties) {
980
            $instance = $registry->load($properties['class'], $properties['config']);
981
            $this->_eventManager->on($instance);
982
        }
983
    }
984
985
    /**
986
     * Initialize parser.
987
     *
988
     * @param array $config Service options
989
     * @return void
990
     */
991 140
    protected function _initializeParser(array $config)
992
    {
993 140
        if (empty($this->_parserClass) && isset($config['parserClass'])) {
994
            $this->_parserClass = $config['parserClass'];
995
        }
996 140
        $parserClass = Configure::read('Api.parser');
997 140
        if (empty($this->_parserClass) && !empty($parserClass)) {
998 140
            $this->_parserClass = $parserClass;
999 140
        }
1000
1001 140
        $class = App::className($this->_parserClass, 'Service/RequestParser', 'Parser');
1002 140
        if (!class_exists($class)) {
1003
            throw new MissingParserException(['class' => $this->_parserClass]);
1004
        }
1005 140
        $this->_parser = new $class($this);
1006 140
    }
1007
1008
    /**
1009
     * Initialize renderer.
1010
     *
1011
     * @param array $config Service options.
1012
     * @return void
1013
     */
1014 140
    protected function _initializeRenderer(array $config)
1015
    {
1016 140
        if (empty($this->_rendererClass) && isset($config['rendererClass'])) {
1017 13
            $this->_rendererClass = $config['rendererClass'];
1018 13
        }
1019 140
        $rendererClass = Configure::read('Api.renderer');
1020 140
        if (empty($this->_rendererClass) && !empty($rendererClass)) {
1021 127
            $this->_rendererClass = $rendererClass;
1022 127
        }
1023
1024 140
        $class = App::className($this->_rendererClass, 'Service/Renderer', 'Renderer');
1025 140
        if (!class_exists($class)) {
1026
            throw new MissingRendererException(['class' => $this->_rendererClass]);
1027
        }
1028 140
        $this->setRenderer(new $class($this));
1029 140
    }
1030
}
1031