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.

Symfony Integration

Symfony

In this section I will describe how to integrate Viewi with Symfony.

First, we will need a new Response class for handling direct url invocations and getting a raw data without modifications:

<?php

namespace App\Adapters;

use Symfony\Component\HttpFoundation\JsonResponse;

class RawJsonResponse extends JsonResponse
{
    private $rawData = null;
    /**
     * @param mixed $data    The response data
     * @param int   $status  The response status code
     * @param array $headers An array of response headers
     * @param bool  $json    If the data is already a JSON string
     */
    public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false)
    {
        $this->rawData = $data;
        parent::__construct($data, $status, $headers, $json);
    }

    public function setData($data = [])
    {
        $this->rawData = $data;
        parent::setData($data);
    }

    public function getRawData()
    {
        return $this->rawData;
    }
}

Then we need to adapt Viewi components to be served from Symfony. Let us create a ViewiSymfonyComponent which will be an entry point to Viewi components.

<?php

namespace App\Adapters;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Viewi\App;
use Viewi\WebComponents\Response as ViewiResponse;

class ViewiSymfonyComponent
{
    public function __invoke(Request $request): Response
    {
        $params = $request->attributes->all();
        $component = $params['__viewi_component'];

        $response = App::run($component, $params);
        if (is_string($response)) { // html
            return new Response(
                $response
            );
        } else if ($response instanceof ViewiResponse) {
            /** @var ViewiResponse $response */
            if ($response->Stringify) { // ViewiResponse with the object (should never happen, Symfony handles the API)
                return new JsonResponse(
                    $response->Content,
                    $response->StatusCode,
                    $response->Headers
                );
            }

            return new Response(
                $response->Content,
                $response->StatusCode,
                $response->Headers
            );
        } else { // json (should never happen, Symfony handles the API)
            return new JsonResponse($response);
        }
    }
}

Then we need an adapter.

Please, do not use Kernel dev in production.

<?php

namespace App\Adapters;

use App\Kernel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use Viewi\Routing\RouteAdapterBase;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Viewi\Routing\Route;
use Viewi\Routing\RouteItem;

class ViewiSymfonyAdapter extends RouteAdapterBase
{
    private int $index = 0; // unique names

    public function register($method, $url, $component, $defaults)
    {
        // skip
    }

    public function handle($method, $url, $params = null)
    {
        // !!do not use Kernel dev in production!!
        $kernel = new Kernel('dev', false);
        $request = Request::create($url, $method, $params ?? []);
        $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
        if($response instanceof RawJsonResponse)
        {
            return $response->getRawData();
        }
        return json_decode($response->getContent());
    }

    public function registerRoutes(RoutingConfigurator $routes)
    {
        $viewiRoutes = Route::getRoutes();
        /** @var RouteItem $route */
        foreach ($viewiRoutes as $viewiRoute) {
            $route = $routes->add($viewiRoute->component . (++$this->index), $viewiRoute->url)
                ->controller(ViewiSymfonyComponent::class)
                ->methods([$viewiRoute->method]);
            $defaults  = ['__viewi_component' => $viewiRoute->component] + ($viewiRoute->defaults ?? []);
            $route->defaults($defaults);
        }
    }
}

Now let us use it in our Symfony application. For that we need to modify Kernel. Register an adapter inside of boot method. And register routes inside of configureRoutes method.

<?php

namespace App;

use App\Adapters\ViewiSymfonyAdapter;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use Viewi\Routing\Route;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    private ViewiSymfonyAdapter $viewiAdapter;

    public function boot()
    {
        $this->viewiAdapter = new ViewiSymfonyAdapter();
        Route::setAdapter($this->viewiAdapter);
        include __DIR__ . '/ViewiApp/viewi.php';
        parent::boot();
    }

    protected function configureRoutes(RoutingConfigurator $routes): void
    {
        $extensions = '{php,yaml}';

        $routes->import('../config/{routes}/' . $this->environment . "/*.$extensions");
        $routes->import("../config/{routes}/*.$extensions");
        $routes->import("../config/{routes}.$extensions");
        
        $this->viewiAdapter->registerRoutes($routes);
    }
}

You can find the source code here github.com/ivanvoitovych/viewi-symfony-demo.