Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.48% |
76 / 84 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
ApieRouteLoader | |
90.48% |
76 / 84 |
|
66.67% |
2 / 3 |
20.35 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
load | |
90.24% |
74 / 82 |
|
0.00% |
0 / 1 |
18.30 | |||
supports | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | namespace Apie\ApieBundle\Routing; |
3 | |
4 | use Apie\Cms\RouteDefinitions\CmsRouteDefinitionProvider; |
5 | use Apie\Common\Interfaces\GlobalRouteDefinitionProviderInterface; |
6 | use Apie\Common\Interfaces\HasRouteDefinition; |
7 | use Apie\Common\Interfaces\RouteDefinitionProviderInterface; |
8 | use Apie\Common\Lists\UrlPrefixList; |
9 | use Apie\Common\RouteDefinitions\ActionHashmap; |
10 | use Apie\Common\RouteDefinitions\PossibleRoutePrefixProvider; |
11 | use Apie\Core\ApieLib; |
12 | use Apie\Core\Attributes\Route as AttributesRoute; |
13 | use Apie\Core\BoundedContext\BoundedContextHashmap; |
14 | use Apie\Core\ContextBuilders\ContextBuilderFactory; |
15 | use Apie\Core\Enums\RequestMethod; |
16 | use Apie\Core\ValueObjects\UrlRouteDefinition; |
17 | use Apie\RestApi\RouteDefinitions\RestApiRouteDefinitionProvider; |
18 | use Psr\Log\LoggerInterface; |
19 | use Psr\Log\NullLogger; |
20 | use ReflectionClass; |
21 | use Symfony\Component\Config\Loader\Loader; |
22 | use Symfony\Component\Config\Resource\DirectoryResource; |
23 | use Symfony\Component\Config\Resource\FileExistenceResource; |
24 | use Symfony\Component\Config\Resource\GlobResource; |
25 | use Symfony\Component\Config\Resource\ReflectionClassResource; |
26 | use Symfony\Component\Routing\Route; |
27 | use Symfony\Component\Routing\RouteCollection; |
28 | |
29 | /** |
30 | * Loads the Apie routing into the symfony loader system. |
31 | */ |
32 | final class ApieRouteLoader extends Loader |
33 | { |
34 | private bool $loaded = false; |
35 | |
36 | /** |
37 | * @param array<string, string> $scanBoundedContexts |
38 | */ |
39 | public function __construct( |
40 | private readonly RouteDefinitionProviderInterface $routeProvider, |
41 | private readonly BoundedContextHashmap $boundedContextHashmap, |
42 | private readonly PossibleRoutePrefixProvider $routePrefixProvider, |
43 | private readonly ContextBuilderFactory $contextBuilder, |
44 | private readonly LoggerInterface $logger = new NullLogger(), |
45 | private readonly array $scanBoundedContexts = [], |
46 | ) { |
47 | } |
48 | |
49 | /** |
50 | * @param mixed $resource |
51 | * @param mixed $type |
52 | */ |
53 | public function load($resource, $type = null): RouteCollection |
54 | { |
55 | if (true === $this->loaded) { |
56 | throw new \RuntimeException('Do not add the "apie" loader twice'); |
57 | } |
58 | |
59 | $routes = new RouteCollection(); |
60 | $classesForCaching = [ |
61 | __CLASS__, |
62 | $this->routeProvider, |
63 | $this->boundedContextHashmap, |
64 | $this->routePrefixProvider, |
65 | RestApiRouteDefinitionProvider::class, |
66 | CmsRouteDefinitionProvider::class, |
67 | RouteDefinitionProviderInterface::class, |
68 | HasRouteDefinition::class, |
69 | UrlRouteDefinition::class, |
70 | RequestMethod::class, |
71 | UrlPrefixList::class, |
72 | ActionHashmap::class, |
73 | ApieLib::class, |
74 | AttributesRoute::class, |
75 | ]; |
76 | if (!empty($this->scanBoundedContexts['search_path'])) { |
77 | if (!is_dir($this->scanBoundedContexts['search_path'])) { |
78 | if (!@mkdir($this->scanBoundedContexts['search_path'], recursive: true)) { |
79 | $this->logger->error('I could not create path: "' . $this->scanBoundedContexts['search_path'] . '"'); |
80 | } |
81 | } |
82 | if (is_dir($this->scanBoundedContexts['search_path'])) { |
83 | $routes->addResource(new GlobResource($this->scanBoundedContexts['search_path'], '*', true)); |
84 | } else { |
85 | $routes->addResource(new FileExistenceResource($this->scanBoundedContexts['search_path'])); |
86 | } |
87 | } |
88 | |
89 | foreach ($classesForCaching as $classForCaching) { |
90 | if (is_object($classForCaching) || class_exists($classForCaching)) { |
91 | $routes->addResource(new ReflectionClassResource(new ReflectionClass($classForCaching))); |
92 | } |
93 | } |
94 | $pathsHandled = []; |
95 | foreach ($this->boundedContextHashmap as $boundedContext) { |
96 | foreach ($boundedContext->resources as $resource) { |
97 | $routes->addResource(new ReflectionClassResource($resource)); |
98 | $path = dirname($resource->getFileName()); |
99 | if (!isset($pathsHandled[$path])) { |
100 | $pathsHandled[$path] = true; |
101 | $routes->addResource(new DirectoryResource($path)); |
102 | } |
103 | } |
104 | } |
105 | $apieContext = $this->contextBuilder->createGeneralContext([ |
106 | 'route-gen' => true, |
107 | ]); |
108 | if ($this->routeProvider instanceof GlobalRouteDefinitionProviderInterface) { |
109 | foreach ($this->routeProvider->getGlobalRoutes() as $routeDefinition) { |
110 | $routes->addResource(new ReflectionClassResource(new ReflectionClass($routeDefinition))); |
111 | /** @var HasRouteDefinition $routeDefinition */ |
112 | |
113 | $requirements = []; |
114 | $url = $routeDefinition->getUrl(); |
115 | $placeholders = $url->getPlaceholders(); |
116 | if (in_array('properties', $placeholders)) { |
117 | $requirements['properties'] = '[a-zA-Z0-9]+(/[a-zA-Z0-9]+)*'; |
118 | } |
119 | $path = ltrim($url, '/'); |
120 | $method = $routeDefinition->getMethod(); |
121 | $defaults = $routeDefinition->getRouteAttributes() |
122 | + [ |
123 | '_controller' => $routeDefinition->getController(), |
124 | '_is_apie' => true, |
125 | ]; |
126 | $route = (new Route($path, $defaults, $requirements))->setMethods([$method->value]); |
127 | $routes->add( |
128 | 'apie._global.' . $routeDefinition->getOperationId(), |
129 | $route |
130 | ); |
131 | } |
132 | } |
133 | foreach ($this->boundedContextHashmap as $boundedContextId => $boundedContext) { |
134 | foreach ($this->routeProvider->getActionsForBoundedContext($boundedContext, $apieContext) as $routeDefinition) { |
135 | $routes->addResource(new ReflectionClassResource(new ReflectionClass($routeDefinition))); |
136 | /** @var HasRouteDefinition $routeDefinition */ |
137 | $prefix = $this->routePrefixProvider->getPossiblePrefixes($routeDefinition); |
138 | |
139 | $requirements = $prefix->getRouteRequirements(); |
140 | $url = $routeDefinition->getUrl(); |
141 | $placeholders = $url->getPlaceholders(); |
142 | if (in_array('properties', $placeholders)) { |
143 | $requirements['properties'] = '[a-zA-Z0-9]+(/[a-zA-Z0-9]+)*'; |
144 | } |
145 | $path = $prefix . $boundedContextId . '/' . ltrim($url, '/'); |
146 | $method = $routeDefinition->getMethod(); |
147 | $defaults = $routeDefinition->getRouteAttributes() |
148 | + [ |
149 | '_controller' => $routeDefinition->getController(), |
150 | '_is_apie' => true, |
151 | ]; |
152 | $route = (new Route($path, $defaults, $requirements))->setMethods([$method->value]); |
153 | $routes->add( |
154 | 'apie.' . $boundedContextId . '.' . $routeDefinition->getOperationId(), |
155 | $route |
156 | ); |
157 | } |
158 | } |
159 | |
160 | return $routes; |
161 | } |
162 | |
163 | /** |
164 | * @param mixed $resource |
165 | * @param mixed $type |
166 | */ |
167 | public function supports($resource, $type = null): bool |
168 | { |
169 | return 'apie' === $type; |
170 | } |
171 | } |