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