1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Copyright 2016, 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, 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 Cake\Event\EventDispatcherInterface; |
15
|
|
|
use Cake\Event\EventDispatcherTrait; |
16
|
|
|
use Cake\Event\EventListenerInterface; |
17
|
|
|
use Cake\Event\EventManager; |
18
|
|
|
use CakeDC\Api\Routing\ApiRouter; |
19
|
|
|
use CakeDC\Api\Service\Action\DummyAction; |
20
|
|
|
use CakeDC\Api\Service\Action\Result; |
21
|
|
|
use CakeDC\Api\Service\Exception\MissingActionException; |
22
|
|
|
use CakeDC\Api\Service\Exception\MissingParserException; |
23
|
|
|
use CakeDC\Api\Service\Exception\MissingRendererException; |
24
|
|
|
use CakeDC\Api\Service\Renderer\BaseRenderer; |
25
|
|
|
use CakeDC\Api\Service\RequestParser\BaseParser; |
26
|
|
|
use Cake\Core\App; |
27
|
|
|
use Cake\Core\Configure; |
28
|
|
|
use Cake\Datasource\Exception\RecordNotFoundException; |
29
|
|
|
use Cake\Http\Client\Response; |
30
|
|
|
use Cake\Routing\RouteBuilder; |
31
|
|
|
use Cake\Utility\Hash; |
32
|
|
|
use Cake\Utility\Inflector; |
33
|
|
|
use Exception; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Class Service |
37
|
|
|
*/ |
38
|
|
|
abstract class Service implements EventListenerInterface, EventDispatcherInterface |
39
|
|
|
{ |
40
|
|
|
use EventDispatcherTrait; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Extensions to load and attach to listener |
44
|
|
|
* |
45
|
|
|
* @var array |
46
|
|
|
*/ |
47
|
|
|
public $extensions = []; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Actions routes description map, indexed by action name. |
51
|
|
|
* |
52
|
|
|
* @var array |
53
|
|
|
*/ |
54
|
|
|
protected $_actions = []; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Actions classes map, indexed by action name. |
58
|
|
|
* |
59
|
|
|
* @var array |
60
|
|
|
*/ |
61
|
|
|
protected $_actionsClassMap = []; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Service url acceptable extensions list. |
65
|
|
|
* |
66
|
|
|
* @var array |
67
|
|
|
*/ |
68
|
|
|
protected $_routeExtensions = ['json']; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* |
72
|
|
|
* |
73
|
|
|
* @var string |
74
|
|
|
*/ |
75
|
|
|
protected $_routePrefix = ''; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Service name |
79
|
|
|
* |
80
|
|
|
* @var string |
81
|
|
|
*/ |
82
|
|
|
protected $_name = null; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Service version. |
86
|
|
|
* |
87
|
|
|
* @var int |
88
|
|
|
*/ |
89
|
|
|
protected $_version; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Parser class to process the HTTP request. |
93
|
|
|
* |
94
|
|
|
* @var BaseParser |
95
|
|
|
*/ |
96
|
|
|
protected $_parser; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Renderer class to build the HTTP response. |
100
|
|
|
* |
101
|
|
|
* @var BaseRenderer |
102
|
|
|
*/ |
103
|
|
|
protected $_renderer; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* The parser class. |
107
|
|
|
* |
108
|
|
|
* @var string |
109
|
|
|
*/ |
110
|
|
|
protected $_parserClass = null; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* The Renderer class. |
114
|
|
|
* |
115
|
|
|
* @var string |
116
|
|
|
*/ |
117
|
|
|
protected $_rendererClass = null; |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Dependent services names list |
121
|
|
|
* |
122
|
|
|
* @var array<string> |
123
|
|
|
*/ |
124
|
|
|
protected $_innerServices = []; |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Parent service instance. |
128
|
|
|
* |
129
|
|
|
* @var Service |
130
|
|
|
*/ |
131
|
|
|
protected $_parentService; |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Service Action Result object. |
135
|
|
|
* |
136
|
|
|
* @var Result |
137
|
|
|
*/ |
138
|
|
|
protected $_result; |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Base url for service. |
142
|
|
|
* |
143
|
|
|
* @var string |
144
|
|
|
*/ |
145
|
|
|
protected $_baseUrl; |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Request |
149
|
|
|
* |
150
|
|
|
* @var \Cake\Network\Request |
151
|
|
|
*/ |
152
|
|
|
protected $_request; |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Request |
156
|
|
|
* |
157
|
|
|
* @var \Cake\Network\Response |
158
|
|
|
*/ |
159
|
|
|
protected $_response; |
160
|
118 |
|
|
161
|
|
|
/** |
162
|
118 |
|
* @var string |
163
|
118 |
|
*/ |
164
|
118 |
|
protected $_corsSuffix = '_cors'; |
165
|
118 |
|
|
166
|
118 |
|
/** |
167
|
118 |
|
* Extension registry. |
168
|
118 |
|
* |
169
|
78 |
|
* @var \CakeDC\Api\Service\ExtensionRegistry |
170
|
78 |
|
*/ |
171
|
118 |
|
protected $_extensions; |
172
|
96 |
|
|
173
|
96 |
|
/** |
174
|
118 |
|
* Service constructor. |
175
|
|
|
* |
176
|
|
|
* @param array $config Service configuration. |
177
|
118 |
|
*/ |
178
|
1 |
|
public function __construct(array $config = []) |
179
|
1 |
|
{ |
180
|
118 |
|
if (isset($config['request'])) { |
181
|
118 |
|
$this->request($config['request']); |
182
|
118 |
|
} |
183
|
118 |
|
if (isset($config['response'])) { |
184
|
|
|
$this->response($config['response']); |
185
|
|
|
} |
186
|
|
|
if (isset($config['baseUrl'])) { |
187
|
|
|
$this->_baseUrl = $config['baseUrl']; |
188
|
|
|
} |
189
|
|
|
if (isset($config['service'])) { |
190
|
|
|
$this->name($config['service']); |
191
|
118 |
|
} |
192
|
|
|
if (isset($config['version'])) { |
193
|
118 |
|
$this->version($config['version']); |
194
|
117 |
|
} |
195
|
|
|
if (isset($config['classMap'])) { |
196
|
118 |
|
$this->_actionsClassMap = Hash::merge($this->_actionsClassMap, $config['classMap']); |
197
|
|
|
} |
198
|
118 |
|
|
199
|
|
|
if (!empty($config['Extension'])) { |
200
|
|
|
$this->extensions = (Hash::merge($this->extensions, $config['Extension'])); |
201
|
|
|
} |
202
|
|
|
$extensionRegistry = $eventManager = null; |
203
|
|
|
if (!empty($config['eventManager'])) { |
204
|
|
|
$eventManager = $config['eventManager']; |
205
|
|
|
} |
206
|
|
|
$this->_eventManager = $eventManager ?: new EventManager(); |
207
|
80 |
|
|
208
|
|
|
$this->initialize(); |
209
|
80 |
|
$this->_initializeParser($config); |
210
|
80 |
|
$this->_initializeRenderer($config); |
211
|
|
|
$this->_eventManager->on($this); |
212
|
|
|
$this->extensions($extensionRegistry); |
213
|
|
|
$this->_loadExtensions(); |
214
|
|
|
|
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Get and set service name. |
219
|
|
|
* |
220
|
|
|
* @param string $name Service name. |
221
|
|
|
* @return string |
222
|
118 |
|
*/ |
223
|
|
|
public function name($name = null) |
224
|
118 |
|
{ |
225
|
39 |
|
if ($name === null) { |
226
|
39 |
|
return $this->_name; |
227
|
39 |
|
} |
228
|
118 |
|
$this->_name = $name; |
229
|
|
|
|
230
|
|
|
return $this->_name; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Get and set service version. |
235
|
|
|
* |
236
|
51 |
|
* @param int $version Version number. |
237
|
|
|
* @return int |
238
|
51 |
|
*/ |
239
|
51 |
|
public function version($version = null) |
240
|
|
|
{ |
241
|
|
|
if ($version === null) { |
242
|
|
|
return $this->_version; |
243
|
|
|
} |
244
|
|
|
$this->_version = $version; |
245
|
|
|
|
246
|
|
|
return $this->_version; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Initialize method |
251
|
|
|
* |
252
|
118 |
|
* @return void |
253
|
|
|
*/ |
254
|
118 |
|
public function initialize() |
255
|
101 |
|
{ |
256
|
|
|
if ($this->_name === null) { |
257
|
|
|
$className = (new \ReflectionClass($this))->getShortName(); |
258
|
118 |
|
$this->name(Inflector::underscore(str_replace('Service', '', $className))); |
|
|
|
|
259
|
|
|
} |
260
|
118 |
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Service parser configuration method. |
264
|
|
|
* |
265
|
|
|
* @param BaseParser $parser A Parser instance. |
266
|
|
|
* @return BaseParser |
267
|
|
|
*/ |
268
|
3 |
|
public function parser(BaseParser $parser = null) |
269
|
|
|
{ |
270
|
|
|
if ($parser === null) { |
271
|
3 |
|
return $this->_parser; |
272
|
3 |
|
} |
273
|
|
|
$this->_parser = $parser; |
274
|
|
|
|
275
|
|
|
return $this->_parser; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
64 |
|
* Get and set request. |
280
|
|
|
* |
281
|
64 |
|
* @param \Cake\Network\Request $request A request object. |
282
|
64 |
|
* @return \Cake\Network\Request |
283
|
64 |
|
*/ |
284
|
64 |
|
public function request($request = null) |
285
|
63 |
|
{ |
286
|
|
|
if ($request === null) { |
287
|
63 |
|
return $this->_request; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
$this->_request = $request; |
291
|
|
|
|
292
|
|
|
return $this->_request; |
293
|
|
|
} |
294
|
|
|
|
295
|
64 |
|
/** |
296
|
|
|
* Get the service route scopes and their connected routes. |
297
|
64 |
|
* |
298
|
64 |
|
* @return array |
299
|
|
|
*/ |
300
|
|
|
public function routes() |
301
|
|
|
{ |
302
|
|
|
return $this->_routesWrapper(function () { |
303
|
|
|
return ApiRouter::routes(); |
304
|
|
|
}); |
305
|
8 |
|
} |
306
|
|
|
|
307
|
8 |
|
/** |
308
|
|
|
* @param callable $callable Wrapped router instance. |
309
|
8 |
|
* @return mixed |
310
|
8 |
|
*/ |
311
|
8 |
|
protected function _routesWrapper(callable $callable) |
312
|
8 |
|
{ |
313
|
8 |
|
$this->resetRoutes(); |
314
|
8 |
|
$this->loadRoutes(); |
315
|
8 |
|
ApiRouter::$initialized = true; |
316
|
8 |
|
$result = $callable(); |
317
|
|
|
$this->resetRoutes(); |
318
|
|
|
|
319
|
|
|
return $result; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Reset to default application routes. |
324
|
63 |
|
* |
325
|
|
|
* @return void |
326
|
63 |
|
*/ |
327
|
63 |
|
public function resetRoutes() |
328
|
42 |
|
{ |
329
|
|
|
ApiRouter::reload(); |
330
|
|
|
} |
331
|
|
|
|
332
|
42 |
|
/** |
333
|
42 |
|
* Initialize service level routes |
334
|
8 |
|
* |
335
|
8 |
|
* @return void |
336
|
8 |
|
*/ |
337
|
42 |
|
public function loadRoutes() |
338
|
42 |
|
{ |
339
|
42 |
|
$defaultOptions = $this->routerDefaultOptions(); |
340
|
8 |
|
ApiRouter::scope('/', $defaultOptions, function (RouteBuilder $routes) use ($defaultOptions) { |
341
|
8 |
|
if (is_array($this->_routeExtensions)) { |
342
|
8 |
|
$routes->extensions($this->_routeExtensions); |
343
|
8 |
|
} |
344
|
63 |
|
if (!empty($defaultOptions['map'])) { |
345
|
|
|
$routes->resources($this->name(), $defaultOptions); |
346
|
|
|
} |
347
|
|
|
}); |
348
|
63 |
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* Build router settings. |
352
|
|
|
* This implementation build action map for resource routes based on Service actions. |
353
|
|
|
* |
354
|
|
|
* @return array |
355
|
|
|
*/ |
356
|
|
|
public function routerDefaultOptions() |
357
|
|
|
{ |
358
|
|
|
$mapList = []; |
359
|
|
|
foreach ($this->_actions as $alias => $map) { |
360
|
|
|
if (is_numeric($alias)) { |
361
|
|
|
$alias = $map; |
362
|
|
|
$map = []; |
363
|
|
|
} |
364
|
|
|
$mapCors = false; |
365
|
|
|
if (!empty($map['mapCors'])) { |
366
|
|
|
$mapCors = $map['mapCors']; |
367
|
|
|
unset($map['mapCors']); |
368
|
|
|
} |
369
|
|
|
$mapList[$alias] = $map; |
370
|
|
|
$mapList[$alias] += ['method' => 'GET', 'path' => '', 'action' => $alias]; |
371
|
|
|
if ($mapCors) { |
372
|
|
|
$map['method'] = 'OPTIONS'; |
373
|
|
|
$map += ['path' => '', 'action' => $alias . $this->_corsSuffix]; |
374
|
|
|
$mapList[$alias . $this->_corsSuffix] = $map; |
375
|
|
|
} |
376
|
|
|
} |
377
|
13 |
|
|
378
|
|
|
return [ |
379
|
|
|
'map' => $mapList |
380
|
|
|
]; |
381
|
13 |
|
} |
382
|
1 |
|
|
383
|
1 |
|
/** |
384
|
|
|
* Finds URL for specified action. |
385
|
13 |
|
* |
386
|
|
|
* Returns an URL pointing to a combination of controller and action. |
387
|
|
|
* |
388
|
|
|
* @param string|array|null $route An array specifying any of the following: |
389
|
|
|
* 'controller', 'action', 'plugin' additionally, you can provide routed |
390
|
|
|
* elements or query string parameters. If string it can be name any valid url |
391
|
|
|
* string. |
392
|
|
|
* @return string Full translated URL with base path. |
393
|
56 |
|
* @throws \Cake\Core\Exception\Exception When the route name is not found |
394
|
|
|
*/ |
395
|
|
|
public function routeUrl($route) |
396
|
56 |
|
{ |
397
|
56 |
|
return $this->_routesWrapper(function () use ($route) { |
398
|
|
|
return ApiRouter::url($route); |
399
|
52 |
|
}); |
400
|
|
|
} |
401
|
|
|
|
402
|
52 |
|
/** |
403
|
52 |
|
* Reverses a parsed parameter array into a string. |
404
|
|
|
* |
405
|
56 |
|
* @param \Cake\Network\Request|array $params The params array or |
406
|
6 |
|
* Cake\Network\Request object that needs to be reversed. |
407
|
6 |
|
* @return string The string that is the reversed result of the array |
408
|
8 |
|
*/ |
409
|
2 |
|
public function routeReverse($params) |
410
|
2 |
|
{ |
411
|
|
|
return $this->_routesWrapper(function () use ($params) { |
412
|
|
|
try { |
413
|
2 |
|
return ApiRouter::reverse($params); |
414
|
|
|
} catch (Exception $e) { |
415
|
|
|
return null; |
416
|
56 |
|
} |
417
|
|
|
}); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Dispatch service call. |
422
|
|
|
* |
423
|
|
|
* @return \CakeDC\Api\Service\Action\Result |
424
|
|
|
*/ |
425
|
63 |
|
public function dispatch() |
426
|
|
|
{ |
427
|
63 |
|
try { |
428
|
62 |
|
$this->dispatchEvent('Service.beforeDispatch', ['service' => $this]); |
429
|
|
|
$action = $this->buildAction(); |
430
|
|
|
$this->dispatchEvent('Service.beforeProcess', ['service' => $this, 'action' => $this]); |
431
|
62 |
|
$result = $action->process(); |
432
|
62 |
|
|
433
|
62 |
|
if ($result instanceof Result) { |
434
|
53 |
|
$this->result($result); |
435
|
53 |
|
} else { |
436
|
62 |
|
$this->result()->data($result); |
437
|
|
|
$this->result()->code(200); |
438
|
|
|
} |
439
|
9 |
|
} catch (RecordNotFoundException $e) { |
440
|
9 |
|
$this->result()->code(404); |
441
|
9 |
|
$this->result()->exception($e); |
442
|
|
|
} catch (Exception $e) { |
443
|
9 |
|
$code = $e->getCode(); |
444
|
9 |
|
if (!is_int($code) || $code < 100 || $code >= 600) { |
445
|
9 |
|
$this->result()->code(500); |
446
|
9 |
|
} |
447
|
9 |
|
$this->result()->exception($e); |
448
|
62 |
|
} |
449
|
62 |
|
$this->dispatchEvent('Service.afterDispatch', ['service' => $this]); |
450
|
62 |
|
|
451
|
62 |
|
return $this->result(); |
452
|
62 |
|
} |
453
|
2 |
|
|
454
|
|
|
/** |
455
|
60 |
|
* Build action instance |
456
|
60 |
|
* |
457
|
|
|
* @return \CakeDC\Api\Service\Action\Action |
458
|
60 |
|
* @throws Exception |
459
|
|
|
*/ |
460
|
|
|
public function buildAction() |
461
|
|
|
{ |
462
|
|
|
$route = $this->parseRoute($this->baseUrl()); |
463
|
|
|
if (empty($route)) { |
464
|
|
|
throw new MissingActionException('Invalid Action Route:' . $this->baseUrl()); // InvalidActionException |
465
|
|
|
} |
466
|
|
|
$service = null; |
467
|
|
|
$serviceName = Inflector::underscore($route['controller']); |
468
|
|
|
if ($serviceName == $this->name()) { |
469
|
|
|
$service = $this; |
470
|
|
|
} |
471
|
|
|
if (in_array($serviceName, $this->_innerServices)) { |
472
|
63 |
|
$options = [ |
473
|
63 |
|
'version' => $this->version(), |
474
|
63 |
|
'request' => $this->request(), |
475
|
|
|
'response' => $this->response(), |
476
|
|
|
'refresh' => true, |
477
|
|
|
]; |
478
|
|
|
$service = ServiceRegistry::get($serviceName, $options); |
|
|
|
|
479
|
|
|
$service->parent($this); |
480
|
|
|
} |
481
|
|
|
$action = $route['action']; |
482
|
64 |
|
list($namespace, $serviceClass) = namespaceSplit(get_class($service)); |
483
|
|
|
$actionPrefix = substr($serviceClass, 0, -7); |
484
|
64 |
|
$actionClass = $namespace . '\\Action\\' . $actionPrefix . Inflector::camelize($action) . 'Action'; |
485
|
64 |
|
if (class_exists($actionClass)) { |
486
|
|
|
return $service->buildActionClass($actionClass, $route); |
487
|
|
|
} |
488
|
|
|
if (array_key_exists($action, $this->_actionsClassMap)) { |
489
|
|
|
$actionClass = $this->_actionsClassMap[$action]; |
490
|
|
|
|
491
|
|
|
return $service->buildActionClass($actionClass, $route); |
492
|
|
|
} |
493
|
|
|
throw new MissingActionException(['class' => $actionClass]); |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Parses given URL string. Returns 'routing' parameters for that URL. |
498
|
|
|
* |
499
|
54 |
|
* @param string $url URL to be parsed |
500
|
|
|
* @return array Parsed elements from URL |
501
|
54 |
|
* @throws \Cake\Routing\Exception\MissingRouteException When a route cannot be handled |
502
|
54 |
|
*/ |
503
|
|
|
public function parseRoute($url) |
504
|
9 |
|
{ |
505
|
|
|
return $this->_routesWrapper(function () use ($url) { |
506
|
9 |
|
return ApiRouter::parse($url); |
507
|
|
|
}); |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Build base url |
512
|
|
|
* |
513
|
|
|
* @return string |
514
|
|
|
*/ |
515
|
|
|
public function baseUrl() |
516
|
63 |
|
{ |
517
|
|
|
if (!empty($this->_baseUrl)) { |
518
|
63 |
|
return $this->_baseUrl; |
519
|
|
|
} |
520
|
63 |
|
|
521
|
|
|
$result = '/' . $this->name(); |
522
|
|
|
|
523
|
|
|
return $result; |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
/** |
527
|
|
|
* Parent service get and set methods |
528
|
|
|
* |
529
|
63 |
|
* @param Service $service Parent Service instance. |
530
|
|
|
* @return Service |
531
|
63 |
|
*/ |
532
|
|
|
public function parent(Service $service = null) |
533
|
|
|
{ |
534
|
63 |
|
if ($service === null) { |
535
|
63 |
|
return $this->_parentService; |
536
|
63 |
|
} |
537
|
63 |
|
$this->_parentService = $service; |
538
|
63 |
|
|
539
|
|
|
return $this->_parentService; |
540
|
63 |
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Build action class |
544
|
|
|
* |
545
|
|
|
* @param string $class Class name. |
546
|
56 |
|
* @param array $route Activated route. |
547
|
|
|
* @return mixed |
548
|
56 |
|
*/ |
549
|
2 |
|
public function buildActionClass($class, $route) |
550
|
|
|
{ |
551
|
56 |
|
$instance = new $class($this->_actionOptions($route)); |
552
|
|
|
|
553
|
|
|
return $instance; |
554
|
56 |
|
} |
555
|
56 |
|
|
556
|
56 |
|
/** |
557
|
|
|
* Action constructor options. |
558
|
56 |
|
* |
559
|
|
|
* @param array $route Activated route. |
560
|
|
|
* @return array |
561
|
|
|
*/ |
562
|
|
|
protected function _actionOptions($route) |
563
|
|
|
{ |
564
|
|
|
$actionName = $route['action']; |
565
|
|
|
|
566
|
|
|
$options = [ |
567
|
56 |
|
'name' => $actionName, |
568
|
|
|
'service' => $this, |
569
|
56 |
|
'route' => $route, |
570
|
|
|
]; |
571
|
|
|
$options += (new ConfigReader())->actionOptions($this->name(), $actionName, $this->version()); |
572
|
56 |
|
|
573
|
56 |
|
return $options; |
574
|
56 |
|
} |
575
|
8 |
|
|
576
|
8 |
|
/** |
577
|
8 |
|
* @return \CakeDC\Api\Service\Action\Result |
578
|
52 |
|
*/ |
579
|
52 |
|
public function result($value = null) |
580
|
|
|
{ |
581
|
|
|
if ($this->_parentService !== null) { |
582
|
56 |
|
return $this->_parentService->result($value); |
583
|
|
|
} |
584
|
|
|
if ($value instanceof Result) { |
585
|
|
|
$this->_result = $value; |
586
|
|
|
} |
587
|
|
|
if ($this->_result === null) { |
588
|
|
|
$this->_result = new Result(); |
589
|
|
|
} |
590
|
|
|
|
591
|
118 |
|
return $this->_result; |
592
|
|
|
} |
593
|
118 |
|
|
594
|
109 |
|
/** |
595
|
|
|
* Fill up response and stop execution. |
596
|
|
|
* |
597
|
118 |
|
* @param Result $result A Result instance. |
598
|
|
|
* @return Response |
599
|
118 |
|
*/ |
600
|
|
|
public function respond($result = null) |
601
|
|
|
{ |
602
|
|
|
if ($result === null) { |
603
|
|
|
$result = $this->result(); |
604
|
|
|
} |
605
|
|
|
$this->response() |
606
|
|
|
->statusCode($result->code()); |
607
|
|
|
if ($result->exception() !== null) { |
608
|
68 |
|
$this->renderer() |
609
|
|
|
->error($result->exception()); |
610
|
68 |
|
} else { |
611
|
68 |
|
$this->renderer() |
612
|
|
|
->response($result); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
return $this->response(); |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
/** |
619
|
|
|
* Get and set response. |
620
|
|
|
* |
621
|
|
|
* @param \Cake\Network\Response $response A Response object. |
622
|
|
|
* @return \Cake\Network\Response |
623
|
|
|
*/ |
624
|
|
|
public function response($response = null) |
625
|
|
|
{ |
626
|
8 |
|
if ($response === null) { |
627
|
|
|
return $this->_response; |
628
|
8 |
|
} |
629
|
8 |
|
|
630
|
8 |
|
$this->_response = $response; |
631
|
8 |
|
|
632
|
8 |
|
return $this->_response; |
633
|
8 |
|
} |
634
|
8 |
|
|
635
|
8 |
|
/** |
636
|
8 |
|
* Service renderer configuration method. |
637
|
8 |
|
* |
638
|
|
|
* @param BaseRenderer $renderer A Renderer instance. |
639
|
|
|
* @return BaseRenderer |
640
|
|
|
*/ |
641
|
|
|
public function renderer(BaseRenderer $renderer = null) |
642
|
|
|
{ |
643
|
|
|
if ($renderer === null) { |
644
|
|
|
return $this->_renderer; |
645
|
118 |
|
} |
646
|
|
|
$this->_renderer = $renderer; |
647
|
118 |
|
|
648
|
|
|
return $this->_renderer; |
649
|
|
|
} |
650
|
118 |
|
|
651
|
118 |
|
/** |
652
|
118 |
|
* Define action config. |
653
|
118 |
|
* |
654
|
|
|
* @param string $actionName Action name. |
655
|
118 |
|
* @param string $className Class name. |
656
|
118 |
|
* @param array $route Route config. |
657
|
|
|
* @return void |
658
|
|
|
*/ |
659
|
118 |
|
public function mapAction($actionName, $className, $route) |
660
|
118 |
|
{ |
661
|
|
|
$route += ['mapCors' => false]; |
662
|
|
|
$this->_actionsClassMap[$actionName] = $className; |
663
|
|
|
if ($route['mapCors']) { |
664
|
|
|
$this->_actionsClassMap[$actionName . $this->_corsSuffix] = DummyAction::class; |
665
|
|
|
} |
666
|
|
|
if (!isset($route['path'])) { |
667
|
|
|
$route['path'] = $actionName; |
668
|
118 |
|
} |
669
|
|
|
$this->_actions[$actionName] = $route; |
670
|
118 |
|
} |
671
|
13 |
|
|
672
|
13 |
|
/** |
673
|
118 |
|
* @return array |
674
|
118 |
|
*/ |
675
|
105 |
|
public function implementedEvents() |
676
|
105 |
|
{ |
677
|
|
|
$eventMap = [ |
678
|
118 |
|
'Service.beforeDispatch' => 'beforeDispatch', |
679
|
118 |
|
'Service.beforeProcess' => 'beforeProcess', |
680
|
|
|
'Service.afterDispatch' => 'afterDispatch', |
681
|
|
|
]; |
682
|
118 |
|
$events = []; |
683
|
118 |
|
|
684
|
|
|
foreach ($eventMap as $event => $method) { |
685
|
|
|
if (!method_exists($this, $method)) { |
686
|
|
|
continue; |
687
|
|
|
} |
688
|
|
|
$events[$event] = $method; |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
return $events; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
/** |
695
|
|
|
* Get the extension registry for this service. |
696
|
|
|
* |
697
|
|
|
* If called with the first parameter, it will be set as the action $this->_extensions property |
698
|
|
|
* |
699
|
|
|
* @param \CakeDC\Api\Service\ExtensionRegistry|null $extensions Extension registry. |
700
|
|
|
* |
701
|
|
|
* @return \CakeDC\Api\Service\ExtensionRegistry |
702
|
|
|
*/ |
703
|
|
|
public function extensions($extensions = null) |
704
|
|
|
{ |
705
|
|
|
if ($extensions === null && $this->_extensions === null) { |
706
|
|
|
$this->_extensions = new ExtensionRegistry($this); |
707
|
|
|
} |
708
|
|
|
if ($extensions !== null) { |
709
|
|
|
$this->_extensions = $extensions; |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
return $this->_extensions; |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
/** |
716
|
|
|
* Loads the defined extensions using the Extension factory. |
717
|
|
|
* |
718
|
|
|
* @return void |
719
|
|
|
*/ |
720
|
|
|
protected function _loadExtensions() |
721
|
|
|
{ |
722
|
|
|
if (empty($this->extensions)) { |
723
|
|
|
return; |
724
|
|
|
} |
725
|
|
|
$registry = $this->extensions(); |
726
|
|
|
$extensions = $registry->normalizeArray($this->extensions); |
727
|
|
|
foreach ($extensions as $properties) { |
728
|
|
|
$instance = $registry->load($properties['class'], $properties['config']); |
729
|
|
|
$this->_eventManager->on($instance); |
730
|
|
|
} |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
/** |
734
|
|
|
* Initialize parser. |
735
|
|
|
* |
736
|
|
|
* @param array $config Service options |
737
|
|
|
* @return void |
738
|
|
|
*/ |
739
|
|
|
protected function _initializeParser(array $config) |
740
|
|
|
{ |
741
|
|
|
if (empty($this->_parserClass) && isset($config['parserClass'])) { |
742
|
|
|
$this->_parserClass = $config['parserClass']; |
743
|
|
|
} |
744
|
|
|
$parserClass = Configure::read('Api.parser'); |
745
|
|
|
if (empty($this->_parserClass) && !empty($parserClass)) { |
746
|
|
|
$this->_parserClass = $parserClass; |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
$class = App::className($this->_parserClass, 'Service/RequestParser', 'Parser'); |
750
|
|
|
if (!class_exists($class)) { |
751
|
|
|
throw new MissingParserException(['class' => $this->_parserClass]); |
752
|
|
|
} |
753
|
|
|
$this->_parser = new $class($this); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Initialize renderer. |
758
|
|
|
* |
759
|
|
|
* @param array $config Service options. |
760
|
|
|
* @return void |
761
|
|
|
*/ |
762
|
|
|
protected function _initializeRenderer(array $config) |
763
|
|
|
{ |
764
|
|
|
if (empty($this->_rendererClass) && isset($config['rendererClass'])) { |
765
|
|
|
$this->_rendererClass = $config['rendererClass']; |
766
|
|
|
} |
767
|
|
|
$rendererClass = Configure::read('Api.renderer'); |
768
|
|
|
if (empty($this->_rendererClass) && !empty($rendererClass)) { |
769
|
|
|
$this->_rendererClass = $rendererClass; |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
$class = App::className($this->_rendererClass, 'Service/Renderer', 'Renderer'); |
773
|
|
|
if (!class_exists($class)) { |
774
|
|
|
throw new MissingRendererException(['class' => $this->_rendererClass]); |
775
|
|
|
} |
776
|
|
|
$this->_renderer = new $class($this); |
777
|
|
|
} |
778
|
|
|
} |
779
|
|
|
|
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.