This integration is nicely done by Lubiana. Thanks a lot for contributing!
You can find the source code here github.com/lubiana/viewi-slim-demo.
This example shows you how to build a basic slimphp application that handles all HTTP requests, and dispatches the calls through routes to your Viewi application. Viewi then renders the html response and returns it back to slim to be emitted to the web browser.
For server side rendering Viewi application will catch api calls and directly process them via your slim application.
Requirements: slim/slim, slim/psr7, viewi/viewi.
Steps to install:
Install Slim
Install Slim PSR7
Install Viewi
Create a demo app
Clean up your public/index.php from Viewi standalone code and require our SlimViewi app:
<?php require_once __DIR__ . '/../vendor/autoload.php'; (require __DIR__ . '/../src/SlimViewi.php')->run();
Now you are ready to create an integration. First, you will need a new Response class for handling direct url invocations and getting raw data without modifications.
By default, Response object keeps data as a string (html or json encoded). But you need to pass an original data, for example, if your API returns a BlogPostModel, you do not want to receive a json object instead of a fully typed instance of BlogPostModel class.
Like here, your callback expects it to be PostModel:
$http->get('/api/posts/5')->then(function (PostModel $data) { $this->post = $data; }, function ($error) { echo $error; });
For that your new Response should preserve the original data returned from the API call. Important thing: fully typed instances are only supported on server side during SSR. Javascript doesn't support types.
<?php declare(strict_types=1); namespace App\Adapters; use Slim\Psr7\Response; final class RawJsonResponse extends Response { private $rawData = null; /** * @param mixed $data * @return $this */ public function setData($data = []) { $this->rawData = $data; $this->body->write(json_encode($data)); return $this; } /** * @return mixed */ public function getRawData() { return $this->rawData; } public function withJsonHeader(): self { return $this->withAddedHeader('Content-Type', 'application/json'); } }
To make your work in request handlers easier let's create a ResponseFactory which will be used by slim to create your Response objects. Later you will register this factory in slim so that all request handlers get your RawJsonResponse by default:
<?php declare(strict_types=1); namespace App\Adapters; use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; final class RawJsonResponseFactory implements ResponseFactoryInterface { /** * {@inheritdoc} */ public function createResponse( int $code = StatusCodeInterface::STATUS_OK, string $reasonPhrase = '' ): ResponseInterface { $res = new RawJsonResponse($code); if ($reasonPhrase !== '') { $res = $res->withStatus($code, $reasonPhrase); } return $res; } }
Now you can inject a RawJsonResponse in your API handlers, like this:
<?php declare(strict_types=1); namespace App\Action; use App\Adapters\RawJsonResponse; use Components\Models\PostModel; use Psr\Http\Message\RequestInterface; final class ApiAction { public function __invoke(RequestInterface $request, RawJsonResponse $response, $args): RawJsonResponse { $postModel = new PostModel(); $postModel->Id = (int)$args['id']; $postModel->Name = 'Slim ft. Viewi'; $postModel->Version = 1; return $response ->setData($postModel) ->withJsonHeader(); } }
Now it's time to inject Viewi component into Slim request processing flow. You will need a wrapper for Viewi components:
This wrapper is an action handler for Slim, that invokes Viewi\App with the component name and returns the content.
<?php declare(strict_types=1); namespace App\Adapters; use Slim\Psr7\Request; use Slim\Psr7\Response; use Viewi\App; use function is_string; final class ViewiSlimComponent { private string $component; public function __construct(string $component) { $this->component = $component; } public function __invoke(Request $request, Response $response, $args): Response { $vResponse = App::run($this->component, $args); if (is_string($vResponse)) { // html $body = $response->getBody(); $body->write($vResponse); return $response ->withBody($body); } return $response; } }
After this, you are ready to create an adapter for Viewi:
<?php declare(strict_types=1); namespace App\Adapters; use Slim\App; use Slim\Psr7\Factory\ServerRequestFactory; use Viewi\Routing\Route; use Viewi\Routing\RouteAdapterBase; final class ViewiSlimAdapter extends RouteAdapterBase { private App $app; public function __construct(App $app) { $this->app = $app; } public function register($method, $url, $component, $defaults): void { $this->app->$method($url, new ViewiSlimComponent($component)); } public function handle($method, $url, $params = null) { $method = strtoupper($method); $request = (new ServerRequestFactory())->createServerRequest($method, $url, $params ?? []); $response = $this->app->handle($request); if ($response instanceof RawJsonResponse) { return $response->getRawData(); } return json_decode($response->getContent()); } }
What it does:
And now, the final step. You need to register an adapter and use your Response factory, include your Viewi app, etc.
<?php declare(strict_types=1); use App\Action\ApiAction; use App\Adapters\RawJsonResponseFactory; use App\Adapters\ViewiSlimAdapter; use Slim\Factory\AppFactory; use Viewi\Routing\Route; $app = AppFactory::create( new RawJsonResponseFactory() ); $app->get('/api/posts/{id}', ApiAction::class); $adapter = new ViewiSlimAdapter($app); Route::setAdapter($adapter); require __DIR__ . '/../src/ViewiApp/viewi.php'; return $app;
And that's it. Thank you and feel free to reach out for help if you need any.