Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\router;
18
 
19
use Slim\Interfaces\RouteInterface;
20
 
21
/**
22
 * A base Route Loader
23
 *
24
 * @package    core
25
 * @copyright  2024 Andrew Lyons <andrew@nicols.co.uk>
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
abstract class abstract_route_loader {
29
    /**
30
     * Get all routes in the namespace.
31
     *
32
     * @param string $namespace The namespace to get the routes for
33
     * @param callable $componentpathcallback A callback to get the component path for a class
34
     * @param null|callable $filtercallback A callback to use to filter routes before they are added
35
     * @return array[]
36
     */
37
    protected function get_all_routes_in_namespace(
38
        string $namespace,
39
        callable $componentpathcallback,
40
        ?callable $filtercallback = null,
41
    ): array {
42
        $routes = [];
43
 
44
        // Get all classes in the namespace.
45
        $classes = \core_component::get_component_classes_in_namespace(namespace: $namespace);
46
        foreach (array_keys($classes) as $classname) {
47
            $classinfo = new \ReflectionClass($classname);
48
            if ($filtercallback && !$filtercallback($classname)) {
49
                continue;
50
            }
51
            $component = \core_component::get_component_from_classname($classname);
52
            $componentpath = $componentpathcallback($component);
53
 
54
            // Add all public methods with a #[route] attribute in this class.
55
            array_push($routes, ...$this->get_all_routes_in_class(
56
                componentpath: $componentpath,
57
                classinfo: $classinfo,
58
            ));
59
        }
60
 
61
        return $routes;
62
    }
63
 
64
    /**
65
     * Get all routes in a class.
66
     *
67
     * @param string $componentpath The path to the component that the class belongs to
68
     * @param \ReflectionClass $classinfo The class to get the routes for
69
     * @return array[]
70
     */
71
    protected function get_all_routes_in_class(
72
        string $componentpath,
73
        \ReflectionClass $classinfo,
74
    ): array {
75
        // Filter out any methods which are public but do not have any route attached.
76
        return array_filter(
77
            array_map(
78
                fn ($methodinfo) => $this->get_route_data_for_method(
79
                    componentpath: $componentpath,
80
                    classinfo: $classinfo,
81
                    methodinfo: $methodinfo,
82
                ),
83
                $classinfo->getMethods(\ReflectionMethod::IS_PUBLIC),
84
            )
85
        );
86
    }
87
 
88
    /**
89
     * Get route data for a single method in a class.
90
     *
91
     * @param string $componentpath The path to the component that the class belongs to
92
     * @param \ReflectionClass $classinfo The class to get the route data for
93
     * @param \ReflectionMethod $methodinfo The method to get the route data for
94
     * @return null|array[]
95
     */
96
    protected function get_route_data_for_method(
97
        string $componentpath,
98
        \ReflectionClass $classinfo,
99
        \ReflectionMethod $methodinfo,
100
    ): ?array {
101
        $routeattribute = $this->get_route_attribute_for_method(
102
            $classinfo,
103
            $methodinfo,
104
        );
105
 
106
        if ($routeattribute === null) {
107
            // No route on this method.
108
            return null;
109
        }
110
 
111
        // Build the pattern for this route.
112
        $path = $routeattribute->get_path();
113
        $pattern = "/{$componentpath}{$path}";
114
 
115
        // Remove duplicate slashes.
116
        $pattern = preg_replace('@/+@', '/', $pattern);
117
 
118
        // Get the HTTP methods for this route.
119
        $httpmethods = $routeattribute->get_methods(['GET']);
120
 
121
        return [
122
            'methods' => $httpmethods,
123
            'pattern' => $pattern,
124
            'callable' => [$classinfo->getName(), $methodinfo->getName()],
125
        ];
126
    }
127
 
128
    /**
129
     * Get the route attribute for the specified method.
130
     *
131
     * Note: If a parent has a route, but the method does not, no route will be returned.
132
     *
133
     * @param \ReflectionClass $classinfo The class to get the route attribute for
134
     * @param \ReflectionMethod $methodinfo The method to get the route attribute for
135
     * @return null|route
136
     */
137
    protected function get_route_attribute_for_method(
138
        \ReflectionClass $classinfo,
139
        \ReflectionMethod $methodinfo,
140
    ): ?route {
141
        // Fetch the route attribute from the method.
142
        // Each method can only have a single route attribute.
143
        $routeattributes = $methodinfo->getAttributes(route::class, \ReflectionAttribute::IS_INSTANCEOF);
144
        if (empty($routeattributes)) {
145
            return null;
146
        }
147
 
148
        // Get the instance.
149
        $methodroute = $routeattributes[0]->newInstance();
150
 
151
        // Set the parent route if the class has one.
152
        $classattributes = $classinfo->getAttributes(route::class);
153
        if ($classattributes) {
154
            // The class has a #route attribute.
155
            $methodroute->set_parent($classattributes[0]->newInstance());
156
        }
157
 
158
        return $methodroute;
159
    }
160
 
161
    /**
162
     * Normalise the component for use as part of the path.
163
     *
164
     * If the component is a subsystem, the `core_` prefix will be removed.
165
     * If the component is 'core', it will be kept.
166
     * All other components will use their frankenstyle name.
167
     *
168
     * @param string $component
169
     * @return string
170
     */
171
    protected function normalise_component_path(
172
        string $component,
173
    ): string {
174
        return util::normalise_component_path($component);
175
    }
176
 
177
    /**
178
     * Set a route name for the specified callable.
179
     *
180
     * @param RouteInterface $slimroute
181
     * @param string|array|callable $callable
182
     * @return string|null The name of the route if it was set, otherwise null
183
     */
184
    protected function set_route_name_for_callable(
185
        RouteInterface $slimroute,
186
        string|array|callable $callable,
187
    ): ?string {
188
        if (is_string($callable)) {
189
            $slimroute->setName($callable);
190
            return $callable;
191
        }
192
 
193
        if (is_array($callable)) {
194
            $name = implode('::', $callable);
195
            $slimroute->setName($name);
196
            return $name;
197
        }
198
 
199
        // Unable to set a name. Return null.
200
        return null;
201
    }
202
}