Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.06% covered (warning)
89.06%
57 / 64
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApieRouteLoader
89.06% covered (warning)
89.06%
57 / 64
66.67% covered (warning)
66.67%
2 / 3
17.38
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
88.71% covered (warning)
88.71%
55 / 62
0.00% covered (danger)
0.00%
0 / 1
15.32
 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\HasRouteDefinition;
6use Apie\Common\Interfaces\RouteDefinitionProviderInterface;
7use Apie\Common\Lists\UrlPrefixList;
8use Apie\Common\RouteDefinitions\ActionHashmap;
9use Apie\Common\RouteDefinitions\PossibleRoutePrefixProvider;
10use Apie\Core\ApieLib;
11use Apie\Core\Attributes\Route as AttributesRoute;
12use Apie\Core\BoundedContext\BoundedContextHashmap;
13use Apie\Core\ContextBuilders\ContextBuilderFactory;
14use Apie\Core\Enums\RequestMethod;
15use Apie\Core\ValueObjects\UrlRouteDefinition;
16use Apie\RestApi\RouteDefinitions\RestApiRouteDefinitionProvider;
17use Psr\Log\LoggerInterface;
18use Psr\Log\NullLogger;
19use ReflectionClass;
20use Symfony\Component\Config\Loader\Loader;
21use Symfony\Component\Config\Resource\DirectoryResource;
22use Symfony\Component\Config\Resource\FileExistenceResource;
23use Symfony\Component\Config\Resource\GlobResource;
24use Symfony\Component\Config\Resource\ReflectionClassResource;
25use Symfony\Component\Routing\Route;
26use Symfony\Component\Routing\RouteCollection;
27
28/**
29 * Loads the Apie routing into the symfony loader system.
30 */
31final class ApieRouteLoader extends Loader
32{
33    private bool $loaded = false;
34
35    /**
36     * @param array<string, string> $scanBoundedContexts
37     */
38    public function __construct(
39        private readonly RouteDefinitionProviderInterface $routeProvider,
40        private readonly BoundedContextHashmap $boundedContextHashmap,
41        private readonly PossibleRoutePrefixProvider $routePrefixProvider,
42        private readonly ContextBuilderFactory $contextBuilder,
43        private readonly LoggerInterface $logger = new NullLogger(),
44        private readonly array $scanBoundedContexts = [],
45    ) {
46    }
47
48    /**
49     * @param mixed $resource
50     * @param mixed $type
51     */
52    public function load($resource, $type = null): RouteCollection
53    {
54        if (true === $this->loaded) {
55            throw new \RuntimeException('Do not add the "apie" loader twice');
56        }
57
58        $routes = new RouteCollection();
59        $classesForCaching = [
60            __CLASS__,
61            $this->routeProvider,
62            $this->boundedContextHashmap,
63            $this->routePrefixProvider,
64            RestApiRouteDefinitionProvider::class,
65            CmsRouteDefinitionProvider::class,
66            RouteDefinitionProviderInterface::class,
67            HasRouteDefinition::class,
68            UrlRouteDefinition::class,
69            RequestMethod::class,
70            UrlPrefixList::class,
71            ActionHashmap::class,
72            ApieLib::class,
73            AttributesRoute::class,
74        ];
75        if (!empty($this->scanBoundedContexts['search_path'])) {
76            if (!is_dir($this->scanBoundedContexts['search_path'])) {
77                if (!@mkdir($this->scanBoundedContexts['search_path'], recursive: true)) {
78                    $this->logger->error('I could not create path: "' . $this->scanBoundedContexts['search_path'] . '"');
79                }
80            }
81            if (is_dir($this->scanBoundedContexts['search_path'])) {
82                $routes->addResource(new GlobResource($this->scanBoundedContexts['search_path'], '*', true));
83            } else {
84                $routes->addResource(new FileExistenceResource($this->scanBoundedContexts['search_path']));
85            }
86        }
87        
88        foreach ($classesForCaching as $classForCaching) {
89            if (is_object($classForCaching) || class_exists($classForCaching)) {
90                $routes->addResource(new ReflectionClassResource(new ReflectionClass($classForCaching)));
91            }
92        }
93        $pathsHandled = [];
94        foreach ($this->boundedContextHashmap as $boundedContext) {
95            foreach ($boundedContext->resources as $resource) {
96                $routes->addResource(new ReflectionClassResource($resource));
97                $path = dirname($resource->getFileName());
98                if (!isset($pathsHandled[$path])) {
99                    $pathsHandled[$path] = true;
100                    $routes->addResource(new DirectoryResource($path));
101                }
102            }
103        }
104        $apieContext = $this->contextBuilder->createGeneralContext([
105            'route-gen' => true,
106        ]);
107        foreach ($this->boundedContextHashmap as $boundedContextId => $boundedContext) {
108            foreach ($this->routeProvider->getActionsForBoundedContext($boundedContext, $apieContext) as $routeDefinition) {
109                $routes->addResource(new ReflectionClassResource(new ReflectionClass($routeDefinition)));
110                /** @var HasRouteDefinition $routeDefinition */
111                $prefix = $this->routePrefixProvider->getPossiblePrefixes($routeDefinition);
112
113                $requirements = $prefix->getRouteRequirements();
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 = $prefix . $boundedContextId . '/' . 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.' . $boundedContextId . '.' . $routeDefinition->getOperationId(),
129                    $route
130                );
131            }
132        }
133        
134        return $routes;
135    }
136
137    /**
138     * @param mixed $resource
139     * @param mixed $type
140     */
141    public function supports($resource, $type = null): bool
142    {
143        return 'apie' === $type;
144    }
145}