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