1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @copyright Copyright (c) 2016 ublaboo <[email protected]> |
5
|
|
|
* @author Pavel Janda <[email protected]> |
6
|
|
|
* @package Ublaboo |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Ublaboo\ApiRouter\DI; |
10
|
|
|
|
11
|
|
|
use Ublaboo\ApiRouter\ApiRoute; |
12
|
|
|
use Nette\Reflection\ClassType; |
13
|
|
|
use Nette; |
14
|
|
|
use Doctrine\Common\Annotations\AnnotationReader; |
15
|
|
|
use Doctrine\Common\Annotations\AnnotationRegistry; |
16
|
|
|
use Doctrine\Common\Annotations\CachedReader; |
17
|
|
|
use Doctrine\Common\Cache\FilesystemCache; |
18
|
|
|
|
19
|
|
|
class ApiRouterExtension extends Nette\DI\CompilerExtension |
20
|
|
|
{ |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @var array |
24
|
|
|
*/ |
25
|
|
|
private $defaults = [ |
26
|
|
|
'ignoreAnnotation' => [] |
27
|
|
|
]; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var Doctrine\Common\Annotations\Reader |
31
|
|
|
*/ |
32
|
|
|
private $reader; |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @return void |
37
|
|
|
*/ |
38
|
|
|
public function beforeCompile() |
39
|
|
|
{ |
40
|
|
|
$config = $this->_getConfig(); |
41
|
|
|
|
42
|
|
|
$builder = $this->getContainerBuilder(); |
43
|
|
|
$compiler_config = $this->compiler->getConfig(); |
44
|
|
|
|
45
|
|
|
$this->setupReader($compiler_config, $config); |
46
|
|
|
|
47
|
|
|
$routes = $this->findRoutes($builder, $compiler_config, $config); |
|
|
|
|
48
|
|
|
|
49
|
|
|
$builder->addDefinition($this->prefix('resolver')) |
50
|
|
|
->setClass('Ublaboo\ApiRouter\DI\ApiRoutesResolver') |
51
|
|
|
->addSetup('prepandRoutes', [$builder->getDefinition('router'), $routes]) |
52
|
|
|
->addTag('run'); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @param array $compiler_config |
58
|
|
|
* @param array $config |
59
|
|
|
* @return void |
60
|
|
|
*/ |
61
|
|
|
public function setupReader($compiler_config, $config) |
62
|
|
|
{ |
63
|
|
|
/** |
64
|
|
|
* Prepare AnnotationRegistry |
65
|
|
|
*/ |
66
|
|
|
AnnotationRegistry::registerFile(__DIR__ . '/../ApiRoute.php'); |
67
|
|
|
AnnotationRegistry::registerFile(__DIR__ . '/../ApiRouteSpec.php'); |
68
|
|
|
|
69
|
|
|
AnnotationReader::addGlobalIgnoredName('persistent'); |
70
|
|
|
AnnotationReader::addGlobalIgnoredName('inject'); |
71
|
|
|
|
72
|
|
|
foreach ($config['ignoreAnnotation'] as $ignore) { |
73
|
|
|
AnnotationReader::addGlobalIgnoredName($ignore); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
$cache_path = $compiler_config['parameters']['tempDir'] . '/cache/ApiRouter.Annotations'; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Prepare AnnotationReader - use cached values |
80
|
|
|
*/ |
81
|
|
|
$this->reader = new CachedReader( |
|
|
|
|
82
|
|
|
new AnnotationReader, |
83
|
|
|
new FilesystemCache($cache_path), |
84
|
|
|
$compiler_config['parameters']['debugMode'] |
85
|
|
|
); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @param Nette\DI\ContainerBuilder $builder |
91
|
|
|
* @param array $compiler_config |
92
|
|
|
* @return array |
93
|
|
|
*/ |
94
|
|
|
private function findRoutes(Nette\DI\ContainerBuilder $builder, $compiler_config) |
|
|
|
|
95
|
|
|
{ |
96
|
|
|
/** |
97
|
|
|
* Find all presenters and their routes |
98
|
|
|
*/ |
99
|
|
|
$presenters = $builder->findByTag('nette.presenter'); |
100
|
|
|
$routes = []; |
101
|
|
|
|
102
|
|
|
foreach ($presenters as $presenter) { |
103
|
|
|
$this->findRoutesInPresenter($presenter, $routes); |
|
|
|
|
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Return routes sorted by priority |
108
|
|
|
*/ |
109
|
|
|
return $this->sortByPriority($routes); |
|
|
|
|
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @param string $presenter |
115
|
|
|
* @param string $routes |
116
|
|
|
* @return void |
117
|
|
|
*/ |
118
|
|
|
public function findRoutesInPresenter($presenter, & $routes) |
119
|
|
|
{ |
120
|
|
|
$r = ClassType::from($presenter); |
121
|
|
|
|
122
|
|
|
$route = $this->reader->getClassAnnotation($r, ApiRoute::class); |
123
|
|
|
|
124
|
|
|
if (!$route) { |
125
|
|
|
return []; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Add route to priority-sorted list |
130
|
|
|
*/ |
131
|
|
|
if (empty($routes[$route->getPriority()])) { |
132
|
|
|
$routes[$route->getPriority()] = []; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$route->setDescription($r->getAnnotation('description')); |
136
|
|
|
|
137
|
|
|
if (!$route->getPresenter()) { |
138
|
|
|
$route->setPresenter(preg_replace('/Presenter$/', '', $r->getShortName())); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Find apropriate methods |
143
|
|
|
*/ |
144
|
|
|
foreach ($r->getMethods() as $method_r) { |
145
|
|
|
$route_action = $this->reader->getMethodAnnotation($method_r, ApiRoute::class); |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Get action without that ^action string |
149
|
|
|
*/ |
150
|
|
|
$action = lcfirst(preg_replace('/^action/', '', $method_r->name)); |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Route can be defined also for perticular action |
154
|
|
|
*/ |
155
|
|
|
if ($route_action) { |
156
|
|
|
if ($method_r instanceof Nette\Reflection\Method) { |
157
|
|
|
$route_action->setDescription($method_r->getAnnotation('description')); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Action route will inherit presenter name nad priority from parent route |
162
|
|
|
*/ |
163
|
|
|
if (!$route_action->getPresenter()) { |
164
|
|
|
$route_action->setPresenter($route->getPresenter()); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
if (!$route_action->getPriority()) { |
168
|
|
|
$route_action->setPriority($route->getPriority()); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
if (!$route_action->getFormat()) { |
172
|
|
|
$route_action->setFormat($route->getFormat()); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
if (!$route_action->getSection()) { |
176
|
|
|
$route_action->setSection($route->getSection()); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
if ($route_action->getMethod()) { |
180
|
|
|
$route_action->setAction($action, $route_action->getMethod()); |
181
|
|
|
} else { |
182
|
|
|
$route_action->setAction($action); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
$routes[$route->getPriority()][] = $route_action; |
186
|
|
|
} else { |
187
|
|
|
$route->setAction($action); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$routes[$route->getPriority()][] = $route; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @param array $routes |
197
|
|
|
* @return array |
198
|
|
|
*/ |
199
|
|
|
private function sortByPriority(array $routes) |
200
|
|
|
{ |
201
|
|
|
$return = []; |
202
|
|
|
|
203
|
|
|
foreach ($routes as $priority => $priority_routes) { |
204
|
|
|
foreach ($priority_routes as $route) { |
205
|
|
|
$return[] = $route; |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return $return; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @return array |
215
|
|
|
*/ |
216
|
|
|
private function _getConfig() |
217
|
|
|
{ |
218
|
|
|
$config = $this->validateConfig($this->defaults, $this->config); |
219
|
|
|
|
220
|
|
|
if (!is_array($config['ignoreAnnotation'])) { |
221
|
|
|
$config['ignoreAnnotation'] = [$config['ignoreAnnotation']]; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
return (array) $config; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.