From aade3c7d9bcb1e418ee567c9374d829ca71b18fc Mon Sep 17 00:00:00 2001
From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com>
Date: Wed, 10 Apr 2024 06:01:05 +0200
Subject: [PATCH] feat: use context to fetch current request

---
 README.md                                     |   9 +-
 .../features/database/doctrine/entities.md    |  13 +-
 .../database/doctrine/entity-managers.md      |  30 +----
 docs/pages/docs/features/http/middleware.md   |   2 +-
 .../observable-task-table/index.md            |   2 +-
 .../features/security/authentication/index.md |   2 +-
 .../templating/twig/rendering-templates.md    |   2 +-
 docs/pages/index.md                           | 115 +++++-------------
 docs/pages/tutorials/hello-world/index.md     |   2 +-
 .../index.md                                  |   2 +-
 .../session-based-authentication/index.md     |   6 +-
 src/HttpResponderAggregate.php                |   5 +
 src/InternalRedirect.php                      |  13 +-
 src/OAuth2AuthorizationCodeFlowController.php |   6 +-
 src/SwooleCoroutineHelper.php                 |  13 ++
 src/TwigTemplate.php                          |  13 +-
 16 files changed, 94 insertions(+), 141 deletions(-)

diff --git a/README.md b/README.md
index 6b323b41..d58c7661 100644
--- a/README.md
+++ b/README.md
@@ -89,15 +89,10 @@ advantage of the asynchronous environment.
 #[RespondsToHttp(
     method: RequestMethod::GET,
     pattern: '/',
-    routeSymbol: HttpRouteSymbol::Homepage,
 )]
-#[Singleton(collection: SingletonCollection::HttpResponder)]
-readonly class Homepage implements HttpResponderInterface
+function Homepage(ServerRequestInterface $request, ResponseInterface $response): TwigTemplate
 {
-    public function respond(ServerRequestInterface $request, ResponseInterface $response): TwigTemplate
-    {
-        return new TwigTemplate($request, $response, 'website/homepage.twig');
-    }
+    return new TwigTemplate('website/homepage.twig');
 }
 ```
 
diff --git a/docs/pages/docs/features/database/doctrine/entities.md b/docs/pages/docs/features/database/doctrine/entities.md
index ee6c758c..c07dbdd5 100644
--- a/docs/pages/docs/features/database/doctrine/entities.md
+++ b/docs/pages/docs/features/database/doctrine/entities.md
@@ -67,8 +67,6 @@ use Psr\Http\Message\ServerRequestInterface;
 final readonly class BlogPostShow extends HttpController
 {
     public function createResponse(
-        ServerRequestInterface $request,
-        ResponseInterface $response,
         #[DoctrineEntityRouteParameter(
             from: 'blog_post_slug', 
             intent: CrudAction::Read,
@@ -76,14 +74,9 @@ final readonly class BlogPostShow extends HttpController
         )]
         BlogPost $blogPost,
     ): HttpInterceptableInterface {
-        return new TwigTemplate(
-            $request, 
-            $response, 
-            'turbo/website/blog_post.twig', 
-            [
-                'blog_post' => $blogPost,
-            ],
-        );
+        return new TwigTemplate('turbo/website/blog_post.twig',  [
+            'blog_post' => $blogPost,
+        ]);
     }
 }
 ```
diff --git a/docs/pages/docs/features/database/doctrine/entity-managers.md b/docs/pages/docs/features/database/doctrine/entity-managers.md
index f25c5c15..240acd6d 100644
--- a/docs/pages/docs/features/database/doctrine/entity-managers.md
+++ b/docs/pages/docs/features/database/doctrine/entity-managers.md
@@ -114,14 +114,9 @@ final readonly class Blog extends HttpResponder
 
         $blogPostsRepository = $entityManager->getRepository(BlogPost::class);
 
-        return new TwigTemplate(
-            $request,
-            $response,
-            'turbo/website/blog.twig', 
-            [
-                'blog_posts' => $blogPostsRepository->findAll(),
-            ]
-        );
+        return new TwigTemplate('turbo/website/blog.twig', [
+            'blog_posts' => $blogPostsRepository->findAll(),
+        ]);
     }
 }
 ```
@@ -143,7 +138,6 @@ namespace App\HttpResponder;
 
 use App\DoctrineEntity\BlogPost;
 use App\HttpRouteSymbol;
-use Distantmagic\Resonance\Attribute\DoctrineEntityManager;
 use Distantmagic\Resonance\Attribute\DoctrineEntityRepository;
 use Distantmagic\Resonance\Attribute\RespondsToHttp;
 use Distantmagic\Resonance\Attribute\Singleton;
@@ -153,10 +147,7 @@ use Distantmagic\Resonance\HttpResponder\HttpController;
 use Distantmagic\Resonance\RequestMethod;
 use Distantmagic\Resonance\SingletonCollection;
 use Distantmagic\Resonance\TwigTemplate;
-use Doctrine\ORM\EntityManager;
 use Doctrine\ORM\EntityRepository;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
 
 #[RespondsToHttp(
     method: RequestMethod::GET,
@@ -167,21 +158,12 @@ use Psr\Http\Message\ServerRequestInterface;
 final readonly class Blog extends HttpController
 {
     public function createResponse(
-        ServerRequestInterface $request,
-        ResponseInterface $response,
-        #[DoctrineEntityManager]
-        EntityManager $entityManager,
         #[DoctrineEntityRepository(BlogPost::class)]
         EntityRepository $blogPosts,
     ): HttpInterceptableInterface {
-        return new TwigTemplate(
-            $request,
-            $response,
-            'website/blog.twig', 
-            [
-                'blog_posts' => $blogPosts->findAll(),
-            ]
-        );
+        return new TwigTemplate('website/blog.twig', [
+            'blog_posts' => $blogPosts->findAll(),
+        ]);
     }
 }
 ```
diff --git a/docs/pages/docs/features/http/middleware.md b/docs/pages/docs/features/http/middleware.md
index 9e96d191..c5ccb27f 100644
--- a/docs/pages/docs/features/http/middleware.md
+++ b/docs/pages/docs/features/http/middleware.md
@@ -100,7 +100,7 @@ final readonly class BlogPostDestroy extends HttpController
     ): HttpInterceptableInterface {
         // ...
 
-        return new InternalRedirect($request, $response, HttpRouteSymbol::Blog);
+        return new InternalRedirect(HttpRouteSymbol::Blog);
     }
 }
 
diff --git a/docs/pages/docs/features/observability/observable-task-table/index.md b/docs/pages/docs/features/observability/observable-task-table/index.md
index 181e0f6b..b97e911d 100644
--- a/docs/pages/docs/features/observability/observable-task-table/index.md
+++ b/docs/pages/docs/features/observability/observable-task-table/index.md
@@ -91,7 +91,7 @@ final readonly class ObservableTasksDashboard extends HttpResponder
 
     public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface
     {
-        return new TwigTemplate($request, $response, 'observable_task_table.twig',[
+        return new TwigTemplate('observable_task_table.twig',[
             'observableTaskTable' => $this->observableTaskTable,
         ]);
     }
diff --git a/docs/pages/docs/features/security/authentication/index.md b/docs/pages/docs/features/security/authentication/index.md
index 03382184..c1d586fe 100644
--- a/docs/pages/docs/features/security/authentication/index.md
+++ b/docs/pages/docs/features/security/authentication/index.md
@@ -65,7 +65,7 @@ final readonly class LoginValidation extends HttpController
             $user,
         );
 
-        return new InternalRedirect($request, $response, HttpRouteSymbol::Homepage);
+        return new InternalRedirect(HttpRouteSymbol::Homepage);
     }
 }
 ```
diff --git a/docs/pages/docs/features/templating/twig/rendering-templates.md b/docs/pages/docs/features/templating/twig/rendering-templates.md
index f8c0958a..ac0bd6e3 100644
--- a/docs/pages/docs/features/templating/twig/rendering-templates.md
+++ b/docs/pages/docs/features/templating/twig/rendering-templates.md
@@ -40,7 +40,7 @@ final readonly class Twig extends HttpResponder
 {
     public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface
     {
-        return new TwigTemplate($request, $response, 'test.twig');
+        return new TwigTemplate('test.twig');
     }
 }
 ```
diff --git a/docs/pages/index.md b/docs/pages/index.md
index 78752c0d..a3923f9d 100644
--- a/docs/pages/index.md
+++ b/docs/pages/index.md
@@ -36,20 +36,20 @@ description: >
         <ul class="homepage__examples">
             <li class="formatted-content homepage__example">
                 <h2 class="homepage__example__title">
-                    Chat with Open-Source LLMs
+                    Simple Things Remain Simple
                 </h2>
                 <div class="homepage__example__description">
                     <p>
-                        Create prompt controllers to directly answer user's 
-                        prompts.
+                        Writing HTTP controllers is similar to how it's done in 
+                        the synchronous code.
                     </p>
                     <p>
-                        LLM takes care of determining user's intention, you
-                        can focus on taking an appropriate action.
+                        Controllers have new exciting features that take 
+                        advantage of the asynchronous environment.
                     </p>
                     <a 
                         class="homepage__cta homepage__cta--example"
-                        href="/docs/features/ai/"
+                        href="/docs/features/http/controllers.html"
                     >
                         Learn More
                     </a>
@@ -58,40 +58,27 @@ description: >
                         class="language-php"
                         data-controller="hljs"
                         data-hljs-language-value="php"
-                    >#[RespondsToPromptSubject(
-    action: 'adopt',
-    subject: 'cat',
+                    >#[RespondsToHttp(
+    method: RequestMethod::GET,
+    pattern: '/',
 )]
-#[Singleton(collection: SingletonCollection::PromptSubjectResponder)]
-readonly class CatAdopt implements PromptSubjectResponderInterface
+function Homepage(ServerRequestInterface $request, ResponseInterface $response): TwigTemplate
 {
-    public function respondToPromptSubject(PromptSubjectRequest $request, PromptSubjectResponse $response): void
-    {
-        // Pipes message through WebSocket... 
-
-        $response->write("Here you go:\n\n");
-        $response->write("   |\_._/|\n");
-        $response->write("   | o o |\n");
-        $response->write("   (  T  )\n");
-        $response->write("  .^`-^-`^.\n");
-        $response->write("  `.  ;  .`\n");
-        $response->write("  | | | | |\n");
-        $response->write(" ((_((|))_))\n");
-        $response->end();
-    }
+    return new TwigTemplate('website/homepage.twig');
 }</code></pre>
             </li>
             <li class="formatted-content homepage__example">
                 <h2 class="homepage__example__title">
-                    Artificial Intelligence
+                    Chat with Open-Source LLMs
                 </h2>
                 <div class="homepage__example__description">
                     <p>
-                        Integrate your application with self-hosted open-source 
-                        LLMs.
+                        Create prompt controllers to directly answer user's 
+                        prompts.
                     </p>
                     <p>
-                        Use your own Machine Learning models in production.
+                        LLM takes care of determining user's intention, you
+                        can focus on taking an appropriate action.
                     </p>
                     <a 
                         class="homepage__cta homepage__cta--example"
@@ -104,21 +91,26 @@ readonly class CatAdopt implements PromptSubjectResponderInterface
                         class="language-php"
                         data-controller="hljs"
                         data-hljs-language-value="php"
-                    >class LlamaCppGenerate 
+                    >#[RespondsToPromptSubject(
+    action: 'adopt',
+    subject: 'cat',
+)]
+#[Singleton(collection: SingletonCollection::PromptSubjectResponder)]
+readonly class CatAdopt implements PromptSubjectResponderInterface
 {
-    public function __construct(protected LlamaCppClientInterface $llamaCppClient) 
-    {
-    }
-
-    public function doSomething(): void
+    public function respondToPromptSubject(PromptSubjectRequest $request, PromptSubjectResponse $response): void
     {
-        $request = new LlamaCppCompletionRequest('How to make a cat happy?');
-
-        $completion = $this->llamaCppClient->generateCompletion($request);
+        // Pipes message through WebSocket... 
 
-        foreach ($completion as $token) {
-            // ...
-        }
+        $response->write("Here you go:\n\n");
+        $response->write("   |\_._/|\n");
+        $response->write("   | o o |\n");
+        $response->write("   (  T  )\n");
+        $response->write("  .^`-^-`^.\n");
+        $response->write("  `.  ;  .`\n");
+        $response->write("  | | | | |\n");
+        $response->write(" ((_((|))_))\n");
+        $response->end();
     }
 }</code></pre>
             </li>
@@ -165,47 +157,6 @@ final readonly class EchoResponder extends WebSocketJsonRPCResponder
             $rpcRequest->payload,
         ));
     }
-}</code></pre>
-            </li>
-            <li class="formatted-content homepage__example">
-                <h2 class="homepage__example__title">
-                    Simple Things Remain Simple
-                </h2>
-                <div class="homepage__example__description">
-                    <p>
-                        Writing HTTP controllers is similar to how it's done in 
-                        the synchronous code.
-                    </p>
-                    <p>
-                        Controllers have new exciting features that take 
-                        advantage of the asynchronous environment.
-                    </p>
-                    <a 
-                        class="homepage__cta homepage__cta--example"
-                        href="/docs/features/http/controllers.html"
-                    >
-                        Learn More
-                    </a>
-                </div>
-                <pre class="homepage__example__code fenced-code"><code 
-                        class="language-php"
-                        data-controller="hljs"
-                        data-hljs-language-value="php"
-                    >#[RespondsToHttp(
-    method: RequestMethod::GET,
-    pattern: '/',
-    routeSymbol: HttpRouteSymbol::Homepage,
-)]
-#[Singleton(collection: SingletonCollection::HttpResponder)]
-readonly class Homepage implements HttpResponderInterface
-{
-    public function respond(
-        ServerRequestInterface $request, 
-        ResponseInterface $response
-    ): TwigTemplate
-    {
-        return new TwigTemplate($request, $response, 'website/homepage.twig');
-    }
 }</code></pre>
             </li>
             <li class="formatted-content homepage__example">
diff --git a/docs/pages/tutorials/hello-world/index.md b/docs/pages/tutorials/hello-world/index.md
index ed468486..e62adaf6 100644
--- a/docs/pages/tutorials/hello-world/index.md
+++ b/docs/pages/tutorials/hello-world/index.md
@@ -115,7 +115,7 @@ final readonly class Homepage extends HttpResponder
 {
     public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface
     {
-        return new TwigTemplate($request, $response, 'homepage.twig');
+        return new TwigTemplate('homepage.twig');
     }
 }
 ```
diff --git a/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md b/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md
index 81406a4d..e5eebdf8 100644
--- a/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md
+++ b/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md
@@ -113,7 +113,7 @@ final readonly class LlmChat extends HttpResponder
 {
     public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface
     {
-        return new TwigTemplate($request, $response, 'turbo/llmchat/index.twig');
+        return new TwigTemplate('turbo/llmchat/index.twig');
     }
 }
 ```
diff --git a/docs/pages/tutorials/session-based-authentication/index.md b/docs/pages/tutorials/session-based-authentication/index.md
index e3a2832c..6002e5c1 100644
--- a/docs/pages/tutorials/session-based-authentication/index.md
+++ b/docs/pages/tutorials/session-based-authentication/index.md
@@ -255,7 +255,7 @@ final readonly class LoginForm extends HttpResponder
 {
     public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface
     {
-        return new TwigTemplate($request, $response, 'auth/login_form.twig');
+        return new TwigTemplate('auth/login_form.twig');
     }
 }
 ```
@@ -473,7 +473,7 @@ final readonly class LoginValidation extends HttpController
             $user->user,
         );
 
-        return new InternalRedirect($request, $response, HttpRouteSymbol::Homepage);
+        return new InternalRedirect(HttpRouteSymbol::Homepage);
     }
 }
 ```
@@ -512,7 +512,7 @@ final readonly class LogoutForm extends HttpResponder
 {
     public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface
     {
-        return new TwigTemplate($request, $response, 'auth/logout_form.twig');
+        return new TwigTemplate('auth/logout_form.twig');
     }
 }
 ```
diff --git a/src/HttpResponderAggregate.php b/src/HttpResponderAggregate.php
index acc1bb70..11f32d0a 100644
--- a/src/HttpResponderAggregate.php
+++ b/src/HttpResponderAggregate.php
@@ -64,6 +64,11 @@ readonly class HttpResponderAggregate implements RequestHandlerInterface
         $responder = $this->selectResponder($request);
 
         try {
+            $context = SwooleCoroutineHelper::mustGetContext();
+
+            $context['psr_http_request'] = $request;
+            $context['psr_http_response'] = $response;
+
             return $this->recursiveResponder->respondRecursive($request, $response, $responder);
         } catch (Throwable $throwable) {
             $this->eventDispatcher->dispatch(new UnhandledException($throwable));
diff --git a/src/InternalRedirect.php b/src/InternalRedirect.php
index 28eca380..013fd118 100644
--- a/src/InternalRedirect.php
+++ b/src/InternalRedirect.php
@@ -9,15 +9,22 @@ use Psr\Http\Message\ServerRequestInterface;
 
 readonly class InternalRedirect implements HttpInterceptableInterface
 {
+    private ServerRequestInterface $request;
+    private ResponseInterface $response;
+
     /**
      * @param array<string,string> $params
      */
     public function __construct(
-        private ServerRequestInterface $request,
-        private ResponseInterface $response,
         public HttpRouteSymbolInterface $routeSymbol,
         public array $params = [],
-    ) {}
+        ?ResponseInterface $response = null,
+    ) {
+        $context = SwooleCoroutineHelper::mustGetContext();
+
+        $this->request = $context['psr_http_request'];
+        $this->response = $response ?? $context['psr_http_response'];
+    }
 
     public function getResponse(): ResponseInterface
     {
diff --git a/src/OAuth2AuthorizationCodeFlowController.php b/src/OAuth2AuthorizationCodeFlowController.php
index 261a4fab..ead1fe62 100644
--- a/src/OAuth2AuthorizationCodeFlowController.php
+++ b/src/OAuth2AuthorizationCodeFlowController.php
@@ -95,7 +95,7 @@ readonly class OAuth2AuthorizationCodeFlowController implements OAuth2Authorizat
             ->getHttpRouteSymbolForEndpoint(OAuth2Endpoint::AuthenticatedPage)
         ;
 
-        return new InternalRedirect($request, $response, $routeSymbol);
+        return new InternalRedirect($routeSymbol);
     }
 
     public function redirectToClientScopeConsentPage(
@@ -107,7 +107,7 @@ readonly class OAuth2AuthorizationCodeFlowController implements OAuth2Authorizat
             ->getHttpRouteSymbolForEndpoint(OAuth2Endpoint::ClientScopeConsentForm)
         ;
 
-        return new InternalRedirect($request, $response, $routeSymbol);
+        return new InternalRedirect($routeSymbol);
     }
 
     public function redirectToLoginPage(
@@ -119,6 +119,6 @@ readonly class OAuth2AuthorizationCodeFlowController implements OAuth2Authorizat
             ->getHttpRouteSymbolForEndpoint(OAuth2Endpoint::LoginForm)
         ;
 
-        return new InternalRedirect($request, $response, $routeSymbol);
+        return new InternalRedirect($routeSymbol);
     }
 }
diff --git a/src/SwooleCoroutineHelper.php b/src/SwooleCoroutineHelper.php
index af825d52..1df04687 100644
--- a/src/SwooleCoroutineHelper.php
+++ b/src/SwooleCoroutineHelper.php
@@ -5,6 +5,8 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance;
 
 use RuntimeException;
+use Swoole\Coroutine;
+use Swoole\Coroutine\Context;
 use Throwable;
 
 use function Swoole\Coroutine\go;
@@ -12,6 +14,17 @@ use function Swoole\Coroutine\run;
 
 final readonly class SwooleCoroutineHelper
 {
+    public static function mustGetContext(): Context
+    {
+        $context = Coroutine::getContext();
+
+        if (is_null($context)) {
+            throw new RuntimeException('Unable to get coroutine context');
+        }
+
+        return $context;
+    }
+
     /**
      * @param callable() $callback
      */
diff --git a/src/TwigTemplate.php b/src/TwigTemplate.php
index dc69cf7b..748bf42b 100644
--- a/src/TwigTemplate.php
+++ b/src/TwigTemplate.php
@@ -9,15 +9,22 @@ use Psr\Http\Message\ServerRequestInterface;
 
 final readonly class TwigTemplate implements HttpInterceptableInterface
 {
+    private ServerRequestInterface $request;
+    private ResponseInterface $response;
+
     /**
      * @psalm-taint-source file $templatePath
      */
     public function __construct(
-        private ServerRequestInterface $request,
-        private ResponseInterface $response,
         private string $templatePath,
         private array $templateData = [],
-    ) {}
+        ?ResponseInterface $response = null,
+    ) {
+        $context = SwooleCoroutineHelper::mustGetContext();
+
+        $this->request = $context['psr_http_request'];
+        $this->response = $response ?? $context['psr_http_response'];
+    }
 
     public function getResponse(): ResponseInterface
     {
-- 
GitLab