OpenAPI Generator - use Symfony Bundle in your Project

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

5 min readSep 27, 2021

--

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

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.