RouteListFactory::addRoutes()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace kalanis\Restful\Application;
4
5
6
use kalanis\Restful\Exceptions\InvalidStateException;
7
use kalanis\Restful\Utils\Strings;
8
use Nette\Application\UI;
9
use Nette\Loaders\RobotLoader;
10
use ReflectionMethod;
11
use function str_contains;
12
13
14
/**
15
 * RouteListFactory
16
 * @package kalanis\Restful\Application\Routes
17
 */
18
class RouteListFactory implements IRouteListFactory
19
{
20
21
    private RobotLoader $loader;
22
23
    private string $module = '';
24
25
    private string $prefix = '';
26
27
    /**
28
     * @param string $presentersRoot from where to find presenters
29
     * @param bool $autoRebuild enable automatic rebuild of robot loader
30
     * @param string $cacheDirectory directory where to cache
31
     */
32
    public function __construct(
33
        string                           $presentersRoot,
34
        bool                             $autoRebuild,
35
        string                           $cacheDirectory,
36
        private readonly RouteAnnotation $routeAnnotation,
37
    )
38
    {
39
        $loader = new RobotLoader();
40
        $loader->addDirectory($presentersRoot);
41
        $loader->setTempDirectory($cacheDirectory);
42
        $loader->register();
43
        $loader->setAutoRefresh($autoRebuild);
44
        $loader->tryLoad(IResourcePresenter::class);
45
46
        $this->loader = $loader;
47
    }
48
49
    /**
50
     * Set default module of created routes
51
     * @param string $module
52
     * @return $this
53
     */
54
    public function setModule(string $module): self
55
    {
56
        $this->module = $module;
57
        return $this;
58
    }
59
60
    /**
61
     * Set default routes URL mask prefix
62
     * @param string $prefix
63
     * @return $this
64
     */
65
    public function setPrefix(string $prefix): self
66
    {
67
        $this->prefix = $prefix;
68
        return $this;
69
    }
70
71
    /**
72
     * Create route list
73
     * @param string|null $module
74
     * @return Routes\ResourceRouteList
75
     */
76
    public final function create(?string $module = null): Routes\ResourceRouteList
77
    {
78
        $routeList = new Routes\ResourceRouteList($module ?: $this->module);
79
        foreach ($this->loader->getIndexedClasses() as $class => $file) {
80
            /** @var class-string<object> $class */
81
            try {
82
                self::getClassReflection($class);
83
            } catch (\ReflectionException) {
84
                continue;
85
            }
86
87
            $methods = $this->getClassMethods($class);
88
            $routeData = $this->parseClassRoutes($methods);
89
            $this->addRoutes($routeList, $routeData, $class);
90
        }
91
        return $routeList;
92
    }
93
94
    /**
95
     * Get class reflection
96
     * @param class-string<object> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<object> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<object>.
Loading history...
97
     * @throws \ReflectionException
98
     * @return UI\ComponentReflection
99
     */
100
    private static function getClassReflection(string $className): UI\ComponentReflection
101
    {
102
        return new UI\ComponentReflection($className);
103
    }
104
105
    /**
106
     * Get class methods
107
     * @param class-string<object> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<object> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<object>.
Loading history...
108
     * @throws InvalidStateException
109
     * @return ReflectionMethod[]
110
     */
111
    protected function getClassMethods(string $className): array
112
    {
113
        return self::getClassReflection($className)->getMethods();
114
    }
115
116
    /**
117
     * Parse route annotations on given object methods
118
     * @param ReflectionMethod[] $methods
119
     * @return array<string, array<string, string>> $data[URL mask][request method] = action name e.g. $data['api/v1/articles']['GET'] = 'read'
120
     */
121
    protected function parseClassRoutes(array $methods): array
122
    {
123
        $routeData = [];
124
        foreach ($methods as $method) {
125
            // Parse annotations only on action methods
126
            if (!str_contains($method->getName(), 'action'))
127
                continue;
128
129
            $annotations = $this->routeAnnotation->parse($method);
130
            foreach ($annotations as $requestMethod => $mask) {
131
                $action = str_replace('action', '', $method->getName());
132
                $action = Strings::firstLower($action);
133
134
                $pattern = $this->prefix ?
135
                    $this->prefix . '/' . $mask :
136
                    $mask;
137
138
                $routeData[strval($pattern)][strval($requestMethod)] = $action;
139
            }
140
        }
141
        return $routeData;
142
    }
143
144
    /**
145
     * Add class routes to route list
146
     * @param Routes\ResourceRouteList $routeList
147
     * @param array<string, array<string, string>> $routeData
148
     * @param class-string<object> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<object> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<object>.
Loading history...
149
     * @return Routes\ResourceRouteList
150
     */
151
    protected function addRoutes(Routes\ResourceRouteList $routeList, array $routeData, string $className): Routes\ResourceRouteList
152
    {
153
        $presenter = str_replace('Presenter', '', self::getClassReflection($className)->getShortName());
154
        foreach ($routeData as $mask => $dictionary) {
155
            $routeList[] = new Routes\ResourceRoute($mask, ['presenter' => $presenter, 'action' => $dictionary], IResourceRouter::RESTFUL);
156
        }
157
        return $routeList;
158
    }
159
}
160