OpenAPI Generator - use Symfony Bundle in your Project

Generate Symfony Bundle from OpenAPI Spec and use it in your Symfony 4 Projects.

Our Process - generate OpenApiBundle from existing Spec and integrate it into our Project

First, we set up a new Symfony 4 Project with the Symfony CLI, if you don't already have this on your machine go here to use the installer to make it available on the CLI.

symfony new petstore-symfony --version=4.4 --full

We need a few additional packages and install them via Composer.

composer require ext-curl ext-json ext-mbstring jms/serializer-bundle:^2.0

We create a new folder in the root of the Symfony Project and add there our config for generating/regenerating the OpenApiBundle at any time, so we can easily keep it in sync with the changes we make to the OpenAPI Spec.

mkdir openapi

In this folder, we will add two files config.json and openapi-generator.php

In config.json we can set the additional-properties, which you can review here. We only use the property invokerPackage to change the Namespace to App\OpenApiBundle.

// config.json
{
"invokerPackage": "App\\OpenApiBundle"
}

Install the openapi-generator-cli via npm command, so we can easily generate code from the command line.

You need to have Node.js installed on your machine to have npm available via the command line.

npm i @openapitools/openapi-generator-cli

In openapi-generator.php we will add some logic for generating the OpenApiBundle from the OpenAPI Spec, in this example, we will use the pet store spec.

<?php// OpenAPI Specification
$urlToOpenApiYaml = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml";
// the generated code will be added to src/OpenApiBundle
$outputPath = "src/OpenApiBundle";
// npm command to generate code from the spec
$output = shell_exec("openapi-generator-cli generate -g php-symfony -i $urlToOpenApiYaml -o $outputPath -c openapi/config.json");
echo "$output";
// remove some generated code to concentrate on the main parts
shell_exec("rm -rf $outputPath/Tests");
shell_exec("rm -rf $outputPath/src");

shell_exec("rm $outputPath/.coveralls.yml");
shell_exec("rm $outputPath/.gitignore");
shell_exec("rm $outputPath/.openapi-generator-ignore");
shell_exec("rm $outputPath/.php_cs.dist");
shell_exec("rm $outputPath/.travis.yml");
shell_exec("rm $outputPath/autoload.php");
shell_exec("rm $outputPath/composer.json");
shell_exec("rm $outputPath/git_push.sh");
shell_exec("rm $outputPath/phpunit.xml.dist");
shell_exec("rm $outputPath/pom.xml");

Add a new command in the composer.json in the scripts section.

"scripts": {
...
"generate-openapi-bundle": "php openapi/openapi-generator.php"
},

Now we can run the code generation easily via:

composer run generate-openapi-bundle

You should see some successful log info on the command line that lists the generated files.

Now you have a new folder under src/ the OpenApiBundle. If you worked before with Symfony you know most of the folders. Here is a short explanation of what we have:

Now we do some preparation before implementing our services.

In config/bundles.php we add our generated Bundle to the list, to load it into our project.

return [
...
App\OpenApiBundle\OpenAPIServerBundle::class => ['all' => true],
];

In config/routes.yaml we include the routes from your generated bundle.

OpenAPIBundle:
resource: "@OpenAPIServerBundle/Resources/config/routing.yml"
prefix: /

We delete the src/Contoller folder and remove the following part from config/routes/annotations.yaml

controllers:
resource: ../../src/Controller/
type: annotation

And we also remove this part from config/services.yaml

App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']

For this example I will use a very simple folder structure, you can change it easily to your needs.

Let's start to implement our API. We add a new file into the folder src/Api/

We extend the Controller from our generated Bundle so we have some logic out of the box and implement the Interface PetsApiInterface that verifies that we implement all needed functions with the correct parameters.

// src/Api/PetsApi.php
<?php

namespace App\Api;

use App\OpenApiBundle\Api\PetsApiInterface;
use App\OpenApiBundle\Controller\Controller;

class PetsApi extends Controller implements PetsApiInterface
{

public function createPets(&$responseCode, array &$responseHeaders)
{
// TODO: Implement createPets() method.
}

public function listPets($limit = null, &$responseCode, array &$responseHeaders)
{
// TODO: Implement listPets() method.
}

public function showPetById($petId, &$responseCode, array &$responseHeaders)
{
// TODO: Implement showPetById() method.
}
}

Before we move on we will configure the connection to the database. Please replace in the .env file the Parameter DATABASE_URL with your connection details. I will use the name petstore for the database.

You find more about the Database and the Doctrine ORM here.

// replace with your config
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"

We will generate the database from the command line with:

php bin/console doctrine:database:create

We generate a new entity with the same fields [name, tag] as src/OpenApiBundle/Model/Pet.php and name it PetEntity

php bin/console make:entity

With the next command, we will sync our new Entity with our Database.

php bin/console doctrine:migrations:migrate

Now, we start to implement our endpoints.

To our PetEntityRepository file, we will add three methods to create a pet, list all pets and find one pet by id.

// src/Repository/PetEntityRepository.php
<?php

namespace App\Repository;

use App\Entity\PetEntity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class PetEntityRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, PetEntity::class);
}

public function create()
{
$entityManager = $this->getEntityManager();
$pet = new PetEntity();
$pet->setName('Buddy');
$pet->setTag('Dog');

$entityManager->persist($pet);
$entityManager->flush();

return $pet;
}

public function list($limit)
{
return $this->createQueryBuilder('p')
->orderBy('p.name', 'ASC')
->setMaxResults($limit)
->getQuery()
->getResult();
}

public function findById($id)
{
return $this->find($id);
}
}

Fill the TODOs in PetsApi functions with calls to our repository.

// src/Api/PetsApi.php
<?php

namespace App\Api;

use App\Entity\PetEntity;
use App\OpenApiBundle\Api\PetsApiInterface;
use App\OpenApiBundle\Controller\Controller;

class PetsApi extends Controller implements PetsApiInterface
{

public function createPets(&$responseCode, array &$responseHeaders)
{
return $this->getDoctrine()
->getRepository(PetEntity::class)
->create();
}

public function listPets($limit = null, &$responseCode, array &$responseHeaders)
{
return $this->getDoctrine()
->getRepository(PetEntity::class)
->list($limit);
}

public function showPetById($petId, &$responseCode, array &$responseHeaders)
{
return $this->getDoctrine()
->getRepository(PetEntity::class)
->findById($petId);
}
}

Now we need to tell Symfony which service should be used for our pet's endpoints config/services.yaml.

petstore.api.client:
class: App\Api\PetsApi
tags:
- { name: "open_api_server.api", api: "pets" }

When you implement your own services and want to know which tags you need to use you can check the automatically generated docs in this folder src/OpenApiBundle/Resources/docs/Api/…

We can start a local server with the following command.

symfony server:start

We can check the available routes with:

php bin/console debug:router

Let's send a few POST requests to the following URL to generate some dummy entries in the DB, via Postman our your preferred environment. We can pass the optional query parameter limit to get a chunk of data.

http://127.0.0.1:8000/pets

Afterward, we can get a list of all generated pets.

http://127.0.0.1:8000/pets
Postman

We can also get a single entry via the following URL.

http://127.0.0.1:8000/pets/1

If you want to dig deeper into how your OpenAPI Spec is related to the generated code, here are a few hints.

At the time of writing the OpenAPI Generator for Symfony 5 is in development, I will write an updated article a soon as it is available.

You find the final solution to this article here.

I hope you enjoyed this article. Your questions and feedback are very welcome.

--

--

--

Loving web development and learning something new. Always curious about new tools and ideas.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

When to use Angular’s Content Projection

A Breakdown of JavaScript Closures in Less Than 10 Minutes

React-Router: Working with server routes in the front-end

https://youtu.be/p79eo0ZVUws

JavaScript Journey- Day 4

React Crash course

20 Reasons to do Angular In Nx

Redash Dashboard Tutorial with Cube.js

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Patric

Patric

Loving web development and learning something new. Always curious about new tools and ideas.

More from Medium

How I designed and built Lumeno’s recruitment search engine

PHP 7.4 vs PHP 8.1 Magento 2 an Adobe Commerce Performance

Deploy a Dockerized Laravel Application to AWS ECS with EC2 Instance Launch type using GitHub…

Serializing data in PHP II: A simple primer on database interactions