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); |
|
|
|
|
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
|
|
|
return $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
|
|
|
return $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); |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|
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: