diff --git a/README.md b/README.md index 6b323b411ea731bdb0d8b8f444333562df69b993..d58c7661c4b8e50e15f13738cba2aeb47ef41ee9 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 ee6c758c1141d292629018912912b8ecd78ca697..c07dbdd527f651a12dab617020b30f3a9b6d53b8 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 f25c5c154f5a88e375870f22ef3b7ed69e5846cb..240acd6dc9dd17e87a8caf86da63c52d3cf84901 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 9e96d19134a59774f9b9fc025d6bc4eac8a0a10c..c5ccb27ff304303a4b20c5470f3013ffde9879a8 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 181e0f6b8fc2bea92b096a90120d28b9177d3dd9..b97e911dccad87325d959b0db585a5be77efd870 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 03382184a89b848dec1c63db923b490ecc1c657d..c1d586fe138a9d825c949dc7d8f7e78d582b2c9a 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 f8c0958ae5171bd78260b79fb6a7ebd9edf47095..ac0bd6e3ccedb3e4e53b48130950f3d155b9bf7a 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 78752c0d718d60b1bac82970a0323702a49d1c37..a3923f9da160a7392cd6cf6095c6c1a3deda205f 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 ed4684866afc2c4c7c80b8be0bd13cb194cfbfb6..e62adaf6dad80b7e70d56e793338dacdf89c44aa 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 81406a4d8607ad23264e8fc432baafd235b05089..e5eebdf86b136f530393e7ea89aed596f78dfc0a 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 e3a2832c7c76300dbc3a1987e37ae08027ac2a9d..6002e5c1f3643a509ccc03d3ff936256793828d9 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 acc1bb70a41bc5a2d8dccefb57b9d46285239900..11f32d0ac8334e6d326df1471789ae1f90fd7b5e 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 28eca380c07281f0661f548ffd0d5b5f726d533b..013fd118ec3205456b51bfb3a4c907da6f4923ba 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 261a4fab920188cc697bed38daf10f6cb7ae0d57..ead1fe626da0064dd2f526e4472be6f09c624ae1 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 af825d529fe7e8b5e1cf1752bca2aae024ceb0a7..1df0468726be26b5aef969a67b118ce9325681aa 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 dc69cf7bb92aac2580e82f11ceb8b2d8f0770139..748bf42b34d2fe1f98d73b6e54d222d8ecb21cfa 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 {