Between two Symfony apps

Here is a demo of Jane interacting with two Symfony apps. A frontend and an API apps.

You can find the fully working example on this repository: janephp/demo-between-two-apps.

A common contract

To make this all work we need a common contract, something that will declare our common model and how our API works. For this we will use OpenAPI 3, here is how this file will looks like:

openapi: '3.0.2'
info:
  title: Between two apps
  description: Simple OpenAPI
  version: 1.0.0
servers:
  - url: 'http://api/'
paths:
  /beers:
    get:
      summary: Get beers
      operationId: getBeers
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Beer'
components:
  schemas:
    Beer:
      type: object
      properties:
        name:
          type: string
        brewer:
          type: string
        style:
          type: string
        color:
          type: string
        alcohol:
          type: integer

Thanks to this schema, we know which endpoint will contains our data, where our server is (I’m using http://api/ here because we are in docker environment and this is the service name) and how our data is structured.

API

First, in the API, we need an endpoint with a list of Beer models (like we described in our OpenAPI file). We will add a BeerController and make routing point path /beers to it. In this controller, we will list all beers and send them as JSON. Here is the controller code, decomposed to explain each steps:

namespace App\Controller;

use App\Entity\Beer as BeerEntity;
use App\Repository\BeerRepository;
use Jane\Component\AutoMapper\AutoMapperInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Generated\Model\Beer as BeerModel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class BeerController extends AbstractController
{
    public function list(BeerRepository $beerRepository, AutoMapperInterface $autoMapper, NormalizerInterface $normalizer)
    {
        // Fetch all beers from database
        $beers = $beerRepository->findAll();

        // Will map all our beers from the entity `App\Entity\Beer` to the model `Generated\Model\Beer`
        // For each entity, we use the AutoMapper to make this conversion
        $beerModels = \array_map(function (BeerEntity $beer) use ($autoMapper) {
            return $autoMapper->map($beer, BeerModel::class);
        }, $beers);

        // Return a response with `application/json` content-type from a list of `Generated\Model\Beer` models
        // We use the normalizer to transform this list to an array of data
        return new JsonResponse($normalizer->normalize($beerModels));
    }
}

This is only our endpoint, we also have some configuration to generate Jane models (see project/api/config/jane/), entity, repository … You can see everything in project/api/.

Frontend

Then in our frontend part, we will recover data from the API and show them thanks a quick twig template. Here is the home controller code, even if he’s really small, I would like to describe some stuff:

namespace App\Controller;

use Generated\Client;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class HomeController extends AbstractController
{
    // Here we will inject the Jane Client, this will allow us to recover beers from the API!
    public function index(Client $client)
    {
        // We will render our home template with the beers from the API
        // Thanks to the OpenAPI scheme, Jane knows where is the server `http://api/` and the path to use, so we only
        // have to call related operation (defined by `operationId` in OpenAPI)
        // Jane will call the endpoint and return a list of `Generated\Model\Beer` models
        return $this->render('home.html.twig', [
            'beers' => $client->getBeers()
        ]);
    }
}

This will gives us all our data and render them, but we miss a thing! How this client was injected there? So here is the project/front/config/packages/jane.yaml file, that contains all Jane related configuration:

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  # This is the usual Normalizer service, it's used to get all Jane generated normalizers
  Generated\Normalizer\JaneObjectNormalizer: ~

  # And here we create a service for the Jane Client based on Client factory
  Generated\Client:
    factory: ['Generated\Client', 'create']
    lazy: true

I only described you our home controller and specific Jane configuration, we also have all usual Symfony configuration and code that you can see in project/front/.