This site is built with Viewi itself. It is experimental and still in development. If you see any bugs please do not hesitate and open an issue or DM me on Twitter.

Slim Integration

Slim

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.

Basic concept and application flow

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.

Installation and setup

Requirements: slim/slim, slim/psr7, viewi/viewi.

Steps to install:

Install Slim

composer require slim/slim:"4.*"

Install Slim PSR7

composer require slim/psr7

Install Viewi

composer require viewi/viewi

Create a demo app

vendor/bin/viewi new -e

Implementation

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:

  • It takes Slim\App as a constructor argument.
  • register($method, $url, $component, $defaults): void delegates routes information from Viewi to your Slim application.
  • handle($method, $url, $params = null) is being invoked during server side rendering when the component uses HttpClient to get the data from the server.
    It creates a virtual (fake) request and runs it through your Slim application as it would be any regular request from a browser.
    It's here where we need our original data without modifications:
    return $response->getRawData();

And now, the final step. You need to register an adapter and use your Response factory, include your Viewi app, etc.

  • Register your RawJsonResponseFactory.
  • Register your API handlers.
  • Instantiate and adapter and set to Viewi Route.
  • Include your Viewi routes with components.
<?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.