]> git.mxchange.org Git - friendica.git/blob - src/App/Router.php
a4a4a852a044748768fbe6aa7f5eb10c1b9aab3b
[friendica.git] / src / App / Router.php
1 <?php
2
3 namespace Friendica\App;
4
5
6 use FastRoute\DataGenerator\GroupCountBased;
7 use FastRoute\Dispatcher;
8 use FastRoute\RouteCollector;
9 use FastRoute\RouteParser\Std;
10 use Friendica\Core\Hook;
11 use Friendica\Core\L10n;
12 use Friendica\Network\HTTPException;
13
14 /**
15  * Wrapper for FastRoute\Router
16  *
17  * This wrapper only makes use of a subset of the router features, mainly parses a route rule to return the relevant
18  * module class.
19  *
20  * Actual routes are defined in App->collectRoutes.
21  *
22  * @package Friendica\App
23  */
24 class Router
25 {
26         const POST = 'POST';
27         const GET  = 'GET';
28
29         const ALLOWED_METHODS = [
30                 self::POST,
31                 self::GET,
32         ];
33
34         /** @var RouteCollector */
35         protected $routeCollector;
36
37         /**
38          * @var string The HTTP method
39          */
40         private $httpMethod;
41
42         /**
43          * @var array Module parameters
44          */
45         private $parameters = [];
46
47         /**
48          * @param array $server The $_SERVER variable
49          * @param RouteCollector|null $routeCollector Optional the loaded Route collector
50          */
51         public function __construct(array $server, RouteCollector $routeCollector = null)
52         {
53                 $httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
54                 $this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
55
56                 $this->routeCollector = isset($routeCollector) ?
57                         $routeCollector :
58                         new RouteCollector(new Std(), new GroupCountBased());
59         }
60
61         /**
62          * @param array $routes The routes to add to the Router
63          *
64          * @return self The router instance with the loaded routes
65          *
66          * @throws HTTPException\InternalServerErrorException In case of invalid configs
67          */
68         public function addRoutes(array $routes)
69         {
70                 $routeCollector = (isset($this->routeCollector) ?
71                         $this->routeCollector :
72                         new RouteCollector(new Std(), new GroupCountBased()));
73
74                 foreach ($routes as $route => $config) {
75                         if ($this->isGroup($config)) {
76                                 $this->addGroup($route, $config, $routeCollector);
77                         } elseif ($this->isRoute($config)) {
78                                 $routeCollector->addRoute($config[1], $route, $config[0]);
79                         } else {
80                                 throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
81                         }
82                 }
83
84                 $this->routeCollector = $routeCollector;
85
86                 return $this;
87         }
88
89         /**
90          * Adds a group of routes to a given group
91          *
92          * @param string         $groupRoute     The route of the group
93          * @param array          $routes         The routes of the group
94          * @param RouteCollector $routeCollector The route collector to add this group
95          */
96         private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector)
97         {
98                 $routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) {
99                         foreach ($routes as $route => $config) {
100                                 if ($this->isGroup($config)) {
101                                         $this->addGroup($route, $config, $routeCollector);
102                                 } elseif ($this->isRoute($config)) {
103                                         $routeCollector->addRoute($config[1], $route, $config[0]);
104                                 }else {
105                                         throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
106                                 }
107                         }
108                 });
109         }
110
111         /**
112          * Returns true in case the config is a group config
113          *
114          * @param array $config
115          *
116          * @return bool
117          */
118         private function isGroup(array $config)
119         {
120                 return
121                         is_array($config) &&
122                         is_string(array_keys($config)[0]) &&
123                         // This entry should NOT be a BaseModule
124                         (substr(array_keys($config)[0], 0, strlen('Friendica\Module')) !== 'Friendica\Module') &&
125                         // The second argument is an array (another routes)
126                         is_array(array_values($config)[0]);
127         }
128
129         /**
130          * Returns true in case the config is a route config
131          *
132          * @param array $config
133          *
134          * @return bool
135          */
136         private function isRoute(array $config)
137         {
138                 return
139                         // The config array should at least have one entry
140                         !empty($config[0]) &&
141                         // This entry should be a BaseModule
142                         (substr($config[0], 0, strlen('Friendica\Module')) === 'Friendica\Module') &&
143                         // Either there is no other argument
144                         (empty($config[1]) ||
145                          // Or the second argument is an array (HTTP-Methods)
146                          is_array($config[1]));
147         }
148
149         /**
150          * The current route collector
151          *
152          * @return RouteCollector|null
153          */
154         public function getRouteCollector()
155         {
156                 return $this->routeCollector;
157         }
158
159         /**
160          * Returns the relevant module class name for the given page URI or NULL if no route rule matched.
161          *
162          * @param string $cmd The path component of the request URL without the query string
163          *
164          * @return string A Friendica\BaseModule-extending class name if a route rule matched
165          *
166          * @throws HTTPException\InternalServerErrorException
167          * @throws HTTPException\MethodNotAllowedException    If a rule matched but the method didn't
168          * @throws HTTPException\NotFoundException            If no rule matched
169          */
170         public function getModuleClass($cmd)
171         {
172                 // Add routes from addons
173                 Hook::callAll('route_collection', $this->routeCollector);
174
175                 $cmd = '/' . ltrim($cmd, '/');
176
177                 $dispatcher = new \FastRoute\Dispatcher\GroupCountBased($this->routeCollector->getData());
178
179                 $moduleClass = null;
180                 $this->parameters = [];
181
182                 $routeInfo  = $dispatcher->dispatch($this->httpMethod, $cmd);
183                 if ($routeInfo[0] === Dispatcher::FOUND) {
184                         $moduleClass = $routeInfo[1];
185                         $this->parameters = $routeInfo[2];
186                 } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
187                         throw new HTTPException\MethodNotAllowedException(L10n::t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
188                 } else {
189                         throw new HTTPException\NotFoundException(L10n::t('Page not found.'));
190                 }
191
192                 return $moduleClass;
193         }
194
195         /**
196          * Returns the module parameters.
197          *
198          * @return array parameters
199          */
200         public function getModuleParameters()
201         {
202                 return $this->parameters;
203         }
204 }