Implement a cache for the router
authorAlexander Ebert <ebert@woltlab.com>
Sun, 17 Mar 2024 17:05:11 +0000 (18:05 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Sun, 17 Mar 2024 17:05:11 +0000 (18:05 +0100)
wcfsetup/install/files/lib/action/ApiAction.class.php
wcfsetup/install/files/lib/system/cache/builder/ApiEndpointCacheBuilder.class.php [new file with mode: 0644]

index 3cb7348507af53eb05c1b1f5fb85dbdbf2c14bf5..a31a1e223b1a1adedde7b96772a6e742d2f9f741 100644 (file)
@@ -3,14 +3,15 @@
 namespace wcf\action;
 
 use CuyZ\Valinor\Mapper\MappingError;
+use FastRoute\ConfigureRoutes;
 use FastRoute\Dispatcher\Result\MethodNotAllowed;
 use FastRoute\Dispatcher\Result\NotMatched;
-use FastRoute\RouteCollector;
 use Laminas\Diactoros\Response\JsonResponse;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use wcf\http\attribute\AllowHttpMethod;
+use wcf\system\cache\builder\ApiEndpointCacheBuilder;
 use wcf\system\endpoint\event\ControllerCollecting;
 use wcf\system\endpoint\IController;
 use wcf\system\endpoint\RequestFailure;
@@ -20,7 +21,7 @@ use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
 use wcf\system\request\RouteHandler;
 
-use function FastRoute\simpleDispatcher;
+use function FastRoute\cachedDispatcher;
 
 /**
  * Resolves and forwards API requests to the responsible controllers, exposing
@@ -51,13 +52,11 @@ final class ApiAction implements RequestHandlerInterface
             return $this->toErrorResponse(RequestFailure::UnknownEndpoint, 'missing_endpoint');
         }
 
-        // TODO: This is currently very inefficient and should be cached in some
-        //       way, maybe even use a combined cache for both?
-        $event = new ControllerCollecting();
-        EventHandler::getInstance()->fire($event);
+        $dispatcher = cachedDispatcher(
+            static function (ConfigureRoutes $r) {
+                $event = new ControllerCollecting();
+                EventHandler::getInstance()->fire($event);
 
-        $dispatcher = simpleDispatcher(
-            static function (RouteCollector $r) use ($event) {
                 foreach ($event->getControllers() as $controller) {
                     $reflectionClass = new \ReflectionClass($controller);
                     $attribute = current($reflectionClass->getAttributes(RequestType::class, \ReflectionAttribute::IS_INSTANCEOF));
@@ -69,8 +68,8 @@ final class ApiAction implements RequestHandlerInterface
                 }
             },
             [
-                // TODO: debug only
-                'cacheDisabled' => true,
+                'cacheKey' => self::class,
+                'cacheDriver' => ApiEndpointCacheBuilder::getInstance(),
             ]
         );
 
diff --git a/wcfsetup/install/files/lib/system/cache/builder/ApiEndpointCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/ApiEndpointCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..2ebc631
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+namespace wcf\system\cache\builder;
+
+use FastRoute\Cache;
+
+/**
+ * Caches the list of available API endpoints.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class ApiEndpointCacheBuilder extends AbstractCacheBuilder implements Cache
+{
+    private \Closure $loader;
+
+    #[\Override]
+    public function get(string $key, callable $loader): array
+    {
+        $this->loader = \Closure::fromCallable($loader);
+
+        return $this->getData(['key' => $key]);
+    }
+
+    #[\Override]
+    public function rebuild(array $parameters)
+    {
+        if (!isset($this->loader)) {
+            throw new \RuntimeException('You may not access the API endpoint cache builder directly.');
+        }
+
+        $callable = $this->loader;
+
+        return $callable();
+    }
+}