Completed
Push — master ( d63c61...2740b6 )
by Pavel
02:54
created

src/DI/ApiRouterExtension.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright   Copyright (c) 2016 ublaboo <[email protected]>
7
 * @author      Pavel Janda <[email protected]>
8
 * @package     Ublaboo
9
 */
10
11
namespace Ublaboo\ApiRouter\DI;
12
13
use Doctrine\Common\Annotations\AnnotationReader;
14
use Doctrine\Common\Annotations\AnnotationRegistry;
15
use Doctrine\Common\Annotations\CachedReader;
16
use Doctrine\Common\Annotations\Reader;
17
use Doctrine\Common\Cache\FilesystemCache;
18
use Nette\DI\CompilerExtension;
19
use Nette\DI\ContainerBuilder;
20
use Nette\Reflection\ClassType;
21
use Nette\Reflection\Method;
22
use Ublaboo\ApiRouter\ApiRoute;
23
use Ublaboo\ApiRouter\DI\ApiRoutesResolver;
24
25
class ApiRouterExtension extends CompilerExtension
26
{
27
28
	/**
29
	 * @var array
30
	 */
31
	private $defaults = [
32
		'ignoreAnnotation' => [],
33
	];
34
35
	/**
36
	 * @var Reader
37
	 */
38
	private $reader;
39
40
41
	public function beforeCompile(): void
42
	{
43
		$config = $this->_getConfig();
44
45
		$builder = $this->getContainerBuilder();
46
		$compiler_config = $this->compiler->getConfig();
47
48
		$this->setupReaderAnnotations($config);
49
		$this->setupReader($compiler_config);
50
51
		$routes = $this->findRoutes($builder);
52
53
		$builder->addDefinition($this->prefix('resolver'))
54
			->setClass(ApiRoutesResolver::class)
55
			->addSetup('prepandRoutes', [$builder->getDefinition('router'), $routes])
56
			->addTag('run');
57
	}
58
59
60
	private function setupReaderAnnotations(array $config): void
61
	{
62
		/**
63
		 * Prepare AnnotationRegistry
64
		 */
65
		AnnotationRegistry::registerFile(__DIR__ . '/../ApiRoute.php');
66
		AnnotationRegistry::registerFile(__DIR__ . '/../ApiRouteSpec.php');
67
68
		AnnotationReader::addGlobalIgnoredName('persistent');
69
		AnnotationReader::addGlobalIgnoredName('inject');
70
71
		foreach ($config['ignoreAnnotation'] as $ignore) {
72
			AnnotationReader::addGlobalIgnoredName($ignore);
73
		}
74
	}
75
76
77
	private function setupReader(array $compiler_config): void
78
	{
79
		$cache_path = $compiler_config['parameters']['tempDir'] . '/cache/ApiRouter.Annotations';
80
81
		/**
82
		 * Prepare AnnotationReader - use cached values
83
		 */
84
		$this->reader = new CachedReader(
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Common\Ann...ameters']['debugMode']) of type object<Doctrine\Common\Annotations\CachedReader> is incompatible with the declared type object<Doctrine\Common\Annotations\Reader> of property $reader.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
85
			new AnnotationReader,
86
			new FilesystemCache($cache_path),
87
			$compiler_config['parameters']['debugMode']
88
		);
89
	}
90
91
92
	private function findRoutes(ContainerBuilder $builder): array
93
	{
94
		/**
95
		 * Find all presenters and their routes
96
		 */
97
		$presenters = $builder->findByTag('nette.presenter');
98
		$routes = [];
99
100
		foreach ($presenters as $presenter) {
101
			$this->findRoutesInPresenter($presenter, $routes);
102
		}
103
104
		/**
105
		 * Return routes sorted by priority
106
		 */
107
		return $this->sortByPriority($routes);
108
	}
109
110
111
	private function findRoutesInPresenter(string $presenter, array &$routes): void
112
	{
113
		$r = ClassType::from($presenter);
114
115
		$route = $this->reader->getClassAnnotation($r, ApiRoute::class);
116
117
		if (!$route) {
118
			return;
119
		}
120
121
		/**
122
		 * Add route to priority-half-sorted list
123
		 */
124
		if (empty($routes[$route->getPriority()])) {
125
			$routes[$route->getPriority()] = [];
126
		}
127
128
		$route->setDescription($r->getAnnotation('description'));
129
130
		if (!$route->getPresenter()) {
131
			$route->setPresenter(preg_replace('/Presenter$/', '', $r->getShortName()));
132
		}
133
134
		/**
135
		 * Find apropriate methods
136
		 */
137
		foreach ($r->getMethods() as $method_r) {
138
			$this->findPresenterMethodRoute($method_r, $routes, $route);
139
		}
140
141
		/**
142
		 * Add ApiRouter annotated presenter route only if there are some remaining
143
		 * methods without ApiRouter annotated presenter method
144
		 */
145
		if (!empty($route->getMethods())) {
146
			$routes[$route->getPriority()][] = $route;
147
		}
148
	}
149
150
151
	private function findPresenterMethodRoute(\ReflectionMethod $method_reflection, array &$routes, ApiRoute $route): void
152
	{
153
		$action_route = $this->reader->getMethodAnnotation($method_reflection, ApiRoute::class);
154
155
		/**
156
		 * Get action without that ^action string
157
		 */
158
		$action = lcfirst(preg_replace('/^action/', '', $method_reflection->name));
159
160
		/**
161
		 * Route can be defined also for particular action
162
		 */
163
		if (!$action_route) {
164
			$route->setAction($action);
165
166
			return;
167
		}
168
169
		if ($method_reflection instanceof Method) {
170
			$action_route->setDescription($method_reflection->getAnnotation('description'));
171
		}
172
173
		/**
174
		 * Action route will inherit presenter name, priority, etc from parent route
175
		 */
176
		$action_route->setPresenter($action_route->getPresenter() ?: $route->getPresenter());
177
		$action_route->setPriority($action_route->getPriority() ?: $route->getPriority());
178
		$action_route->setFormat($action_route->getFormat() ?: $route->getFormat());
179
		$action_route->setSection($action_route->getSection() ?: $route->getSection());
180
		$action_route->setAction($action, $action_route->getMethod() ?: null);
181
182
		$routes[$route->getPriority()][] = $action_route;
183
	}
184
185
186
	private function sortByPriority(array $routes): array
187
	{
188
		$return = [];
189
190
		foreach ($routes as $priority => $priority_routes) {
191
			foreach ($priority_routes as $route) {
192
				$return[] = $route;
193
			}
194
		}
195
196
		return $return;
197
	}
198
199
200
	private function _getConfig(): array
201
	{
202
		$config = $this->validateConfig($this->defaults, $this->config);
203
204
		if (!is_array($config['ignoreAnnotation'])) {
205
			$config['ignoreAnnotation'] = [$config['ignoreAnnotation']];
206
		}
207
208
		return (array) $config;
209
	}
210
}
211