3 namespace Friendica\App;
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;
14 * Wrapper for FastRoute\Router
16 * This wrapper only makes use of a subset of the router features, mainly parses a route rule to return the relevant
19 * Actual routes are defined in App->collectRoutes.
21 * @package Friendica\App
28 const ALLOWED_METHODS = [
33 /** @var RouteCollector */
34 protected $routeCollector;
37 * @var string The HTTP method
42 * @var array Module parameters
44 private $parameters = [];
47 * @param array $server The $_SERVER variable
48 * @param RouteCollector|null $routeCollector Optional the loaded Route collector
50 public function __construct(array $server, RouteCollector $routeCollector = null)
52 $httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
53 $this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
55 $this->routeCollector = isset($routeCollector) ?
57 new RouteCollector(new Std(), new GroupCountBased());
61 * @param array $routes The routes to add to the Router
63 * @return self The router instance with the loaded routes
65 * @throws HTTPException\InternalServerErrorException In case of invalid configs
67 public function loadRoutes(array $routes)
69 $routeCollector = (isset($this->routeCollector) ?
70 $this->routeCollector :
71 new RouteCollector(new Std(), new GroupCountBased()));
73 $this->addRoutes($routeCollector, $routes);
75 $this->routeCollector = $routeCollector;
80 private function addRoutes(RouteCollector $routeCollector, array $routes)
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]);
88 throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
94 * Adds a group of routes to a given group
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
100 private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector)
102 $routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) {
103 $this->addRoutes($routeCollector, $routes);
108 * Returns true in case the config is a group config
110 * @param array $config
114 private function isGroup(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]);
126 * Returns true in case the config is a route config
128 * @param array $config
132 private function isRoute(array $config)
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]));
146 * The current route collector
148 * @return RouteCollector|null
150 public function getRouteCollector()
152 return $this->routeCollector;
156 * Returns the relevant module class name for the given page URI or NULL if no route rule matched.
158 * @param string $cmd The path component of the request URL without the query string
160 * @return string A Friendica\BaseModule-extending class name if a route rule matched
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
166 public function getModuleClass($cmd)
168 // Add routes from addons
169 Hook::callAll('route_collection', $this->routeCollector);
171 $cmd = '/' . ltrim($cmd, '/');
173 $dispatcher = new Dispatcher\GroupCountBased($this->routeCollector->getData());
176 $this->parameters = [];
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])));
185 throw new HTTPException\NotFoundException(DI::l10n()->t('Page not found.'));
192 * Returns the module parameters.
194 * @return array parameters
196 public function getModuleParameters()
198 return $this->parameters;