Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.48% covered (success)
90.48%
76 / 84
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApieRouteLoader
90.48% covered (success)
90.48%
76 / 84
66.67% covered (warning)
66.67%
2 / 3
20.35
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
90.24% covered (success)
90.24%
74 / 82
0.00% covered (danger)
0.00%
0 / 1
18.30
 supports
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\ApieBundle\Routing;
3
4use Apie\Cms\RouteDefinitions\CmsRouteDefinitionProvider;
5use Apie\Common\Interfaces\GlobalRouteDefinitionProviderInterface;
6use Apie\Common\Interfaces\HasRouteDefinition;
7use Apie\Common\Interfaces\RouteDefinitionProviderInterface;
8use Apie\Common\Lists\UrlPrefixList;
9use Apie\Common\RouteDefinitions\ActionHashmap;
10use Apie\Common\RouteDefinitions\PossibleRoutePrefixProvider;
11use Apie\Core\ApieLib;
12use Apie\Core\Attributes\Route as AttributesRoute;
13use Apie\Core\BoundedContext\BoundedContextHashmap;
14use Apie\Core\ContextBuilders\ContextBuilderFactory;
15use Apie\Core\Enums\RequestMethod;
16use Apie\Core\ValueObjects\UrlRouteDefinition;
17use Apie\RestApi\RouteDefinitions\RestApiRouteDefinitionProvider;
18use Psr\Log\LoggerInterface;
19use Psr\Log\NullLogger;
20use ReflectionClass;
21use Symfony\Component\Config\Loader\Loader;
22use Symfony\Component\Config\Resource\DirectoryResource;
23use Symfony\Component\Config\Resource\FileExistenceResource;
24use Symfony\Component\Config\Resource\GlobResource;
25use Symfony\Component\Config\Resource\ReflectionClassResource;
26use Symfony\Component\Routing\Route;
27use Symfony\Component\Routing\RouteCollection;
28
29/**
30 * Loads the Apie routing into the symfony loader system.
31 */
32final 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}