Een REST API maken met Symfony 4

Het is tegenwoordig heel gebruikelijk om een REST API te gebruiken voor het beheren van data voor een web-applicatie of mobiele app. Dit artikel beschrijft hoe je een eenvoudige REST API kunt maken met behulp van Symfony

Voordat we kunnen beginnen hebben we een werkende Symfony omgeving nodig. Bij het schrijven van dit artikel is Symfony 5 beschikbaar maar omdat niet alle benodigde onderdelen van onze API daar klaar voor zijn gebruiken we Symfony 4. Om deze te installeren gebruiken we Composer:

composer create-project symfony/skeleton my_rest_api "4.4.*"

We gebruiken het kleinere Symfony skeleton project omdat dat normaal gesproken voldoende is voor microservices en API's.

Het plan

We gaan een REST API opzetten met behulp van FOSRest bundle. Deze bundle maakt het mogelijk om snel een Rest API te ontwikkelen. Met de REST API moet het mogelijk zijn om een eigen entity aan te maken, te bewerken, op te halen en te verwijderen. We gaan onze REST API testen met behulp van Postman.

Setup van de API omgeving

Laten we beginnen om, met behulp van Composer, een aantal bundles te installeren.

composer require jms/serializer-bundle 
composer require friendsofsymfony/rest-bundle 
composer require sensio/framework-extra-bundle 
composer require symfony/orm-pack 
composer require doctrine/doctrine-bundle
composer require symfony/apache-pack
  • De serializer-bundle wordt gebruikt om onze data de (de)serializen.
  • De rest-bundle bevat de verschillende tools die we nodig hebben voor onze API.
  • De framework-extra-bundle maakt het mogelijk om onze routes te voorzien van annotations wat het lezen van de code makkelijker maakt.
  • Het orm-pack is ter ondersteuning van de doctrine-bundle.
  • De doctrine-bundle zorgt ervoor dat we met Doctrine ORM onze data in de database kunnen opslaan.
  • De apache-pack zorgt ervoor dat we de routes die we later in onze controller maken ook echt te bezoeken zijn.

Tot slot, om ons leven als ontwikkelaar wat makkelijker te maken, installeren we ook nog de maker-bundle.

composer require symfony/maker-bundle --dev

Na de installatie van de bundles controleren we of we zonder fouten de cache kunnen legen en als dat zo is dan zijn we klaar om te beginnen.

bin/console cache:clear

Symfony configureren

De FOSRestBundle geeft de mogelijkheid onze API te configureren en data in verschillende formaten aan te bieden. In ons voorbeeld gaan we de data in json-formaat aanbieden voor alle routes die beginnen met een /.

In config/packages/fos_rest.yaml:

fos_rest: format_listener: 
    rules: 
        - { path: ^/, priorities: [ json ], fallback_format: json, prefer_extension: true }

Om een database te kunnen gebruiken moet die ook geconfigureerd worden. Deze configuratie wordt gedaan in .env:

###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:db_pass@127.0.0.1:3306/db_name
###< doctrine/doctrine-bundle ###

De supplier entity maken

We gaan het met onze REST API mogelijk maken om leveranciers (suppliers) te beheren. Om leveranciers in de database aan te maken, ophalen, bijwerken en uit de database te verwijderen hebben we de entity "supplier" nodig:

bin/console make:entity Supplier

Dit commando zorgt ervoor dat er in src/Entity een class wordt toegevoegd met de naam Supplier.php en geeft gelijk de mogelijkheid om velden voor de entity toe te voegen.

Voeg de volgende velden toe:

  • "name" van het type string
  • "vat" van het type string
  • "createdAt" van het type datetime 

Volg de instructies van de command-line voor het toevoegen van de velden. Als deze velden zijn toegevoegd werken we de database bij:

bin/console doctrine:schema:update -f

Kijken we naar de database dan zien we dat er een "supplier" tabel beschikbaar is.

In de tabel staat het veld "created_at" om bij te houden wanneer een leverancier is aangemaakt. Om te voorkomen dat we dat veld iedere keer zelf te moeten vullen laten we Doctrine dat doen met Doctrine Lifecycle Events.

Voeg aan de Supplier class @ORM\HasLifecycleCallbacks() toe. Dit zorgt ervoor dat lifecycle callbacks ondersteund worden.

/**
 * @ORM\Entity(repositoryClass="App\Repository\SupplierRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class Supplier
....

Bij het maken van de supplier class is daarin de functie setCreatedAt() aangemaakt. Pas deze aan zodat hij er als volgt uitziet:

/**
 * @ORM\PrePersist()
 *
 * @return $this
 */
public function setCreatedAt(): self {
  $this->createdAt = new \DateTime();
  return $this;
}

@ORM/PrePersist() zorgt ervoor dat deze functie wordt uitgevoerd voordat een nieuwe leverancier wordt toegevoegd aan de tabel.

De supplier controller maken

We gaan het endpoint van onze REST API instellen. Dat doen we in een controller en met behulp van annotations. Maak de controller met het volgende commando:

bin/console make:controller SupplierController

Hiermee wordt in src/Controller een bestand gemaakt met de naam SupplierController met daarin de route /supplier. In een browser kunnen we controleren dat dit adres daadwerkelijk beschikbaar is (dankzij het eerder geïnstalleerde apache-pack).

Onze SupplierController heeft een aantal aanpassingen nodig om met behulp van FOSRest entities te kunnen bewerken en met annotations om te kunnen gaan. Voeg het volgende use-statement toe aan de controller:

use FOS\RestBundle\Controller\Annotations as REST;

Zorg ervoor dat de controller de AbstractFOSRestController extend, in plaats van de standaard AbstractController:

class SupplierController extends AbstractFOSRestController
{

Pas de index functie aan zodat hij er als volgt uit ziet:

/**
 * Get a list of all suppliers.
 *
 * @param \App\Repository\SupplierRepository $supplierRepository
 *   The supplier repository.
 *
 * @return \Symfony\Component\HttpFoundation\Response
 *   A list of all suppliers.
 *
 * @REST\Route("/supplier", methods={"GET"})
 */
public function index(SupplierRepository $supplierRepository): Response
{
    $view = $this->view($supplierRepository->findAll());
    return $this->handleView($view);
}

We hebben een functie (index) om een lijst met leveranciers op te vragen. Nu hebben we nog functies nodig om een leverancier aan te maken (create), op te vragen (read), bij te werken (update), en te verwijderen (delete).

Wat op zal vallen is dat voor de index en create functies, en voor read, update en delete, dezelfde routes gebruikt gaan worden. Dit kan omdat we gebruik gaan maken van REST en voor iedere functie een ander soort request method zullen gebruiken.

  • een GET request naar /supplier geeft een lijst met allen leveranciers.
  • een POST request naar /supplier maakt een nieuwe leverancier.
  • een GET request naar /supplier/1 geeft de gegevens van de leverancier met ID 1.
  • een PUT request naar /supplier/1 werkt de gegevens van de leverancier met ID 1 bij.
  • een DELETE request naar /supplier/1 verwijderd de gegevens van de leverancier met ID 1. 

Voeg de onderstaande functies toe aan de SupplierController.

/**
 * Create a new supplier.
 *
 * @Rest\Route("/supplier", methods={"POST"})
 *
 * @param \Symfony\Component\HttpFoundation\Request $request
 *   A request with the new supplier data.
 *
 * @return \Symfony\Component\HttpFoundation\Response
 *   A new supplier.
 */
public function create(Request $request): Response
{
    $supplier = new Supplier();
    $supplier->setName($request->get('name'));
    $supplier->setVat($request->get('vat'));

    $manager = $this->getDoctrine()->getManager();
    $manager->persist($supplier);
    $manager->flush();

    $view = $this->view($supplier);
    return $this->handleView($view);
}

De code hierboven maakt een nieuw Supplier object en vult de "name" en de "vat" met de data die is aangeboden in het request. Nadat we de nieuwe leverancier hebben aangemaakt wordt deze aan de database toegevoegd (flush) door de EntityManager.

Eerder hebben we bij de entity met @ORM\PrePersist() ingesteld dat het veld "created_at" automatisch wordt gevuld, daarom doen we dat bij het aanmaken niet.

/**
 * Get a single supplier.
 *
 * @Rest\Route("/supplier/{id}", methods={"GET"})
 *
 * @param int $id
 *   The supplier id.
 * @param \App\Repository\SupplierRepository $supplierRepository
 *   The supplier repository.
 *
 * @return \Symfony\Component\HttpFoundation\Response
 *   A single supplier.
 */
public function read($id, SupplierRepository $supplierRepository): Response
{
    $supplier = $supplierRepository->find($id);
    $view = $this->view($supplier);
    return $this->handleView($view);
}
/**
 * Update a supplier.
 *
 * @Rest\Route("/supplier/{id}", methods={"PUT"})
 *
 * @param int $id
 *   The supplier id.
 * @param \App\Repository\SupplierRepository $supplierRepository
 *   The supplier repository.
 * @param \Symfony\Component\HttpFoundation\Request $request
 *   A request with the updated supplier data.
 *
 * @return \Symfony\Component\HttpFoundation\Response
 *   The updated supplier.
 */
public function update($id, SupplierRepository $supplierRepository, Request $request): Response
    {
    $supplier = $supplierRepository->find($id);
    $supplier->setName($request->get('name'));
    $supplier->setVat($request->get('vat'));

    $this->getDoctrine()->getManager()->flush();

    $view = $this->view($supplier);
    return $this->handleView($view);
}

Bewerken van een leverancier is nagenoeg hetzelfde is als het aanmaken. Alleen halen we eerst de bestaande leverancier op voordat we de gegevens aanpassen.

LET OP: persist() is hier niet meer nodig omdat de leverancier al in de database bekend is.

/**
 * Delete a supplier.
 *
 * @Rest\Route("/supplier/{id}", methods={"DELETE"})
 *
 * @param int $id
 *   The supplier id.
 * @param \App\Repository\SupplierRepository $supplierRepository
 *   The supplier repository.
 *
 * @return \Symfony\Component\HttpFoundation\JsonResponse
 *   Confirmation that the supplier was deleted.
 */
public function delete($id, SupplierRepository $supplierRepository): JsonResponse
{
    $supplier = $supplierRepository->find($id);
    $manager = $this->getDoctrine()->getManager();
    $manager->remove($supplier);
    $manager->flush();
    return $this->json([
      'message' => 'Supplier with ID ' . $id . ' has been deleted.'
    ]);
}

Hier valt op dat de "remove" functie de tegenhanger is van "persist". Persist zorgt ervoor dat Doctrine op de hoogte is van de nieuwe entity. Remove zorgt ervoor dat de entity wordt verwijderd. Echter, net als bij aanmaken en bewerken, gebeurd er niets totdat de "flush" functie is uitgevoerd.

De API testen met Postman

Postman is een handige tool om onze REST API endpoints te testen. Ga in Postman naar /supplier en we krijgen een lege array als response. Logisch, want onze database is nog leeg.

Voeg een leverancier toevoegen aan de tabel:

INSERT INTO supplier (name, vat, created_at) VALUES ('Top Supplies', '12345', NOW());

Ga nogmaals naar /supplier en bekijk het resultaat!

[
    {
        "id": 1,
        "name": "Top Supplies",
        "vat": "12345",
        "created_at": "2020-05-11T21:07:16+00:00"
    }
]

Om een nieuwe leverancier aan te maken gebruiken we hetzelfde pad. Alleen in plaats van de GET methode gebruiken we POST waardoor de create() functie in de SupplierController geïnitieerd zal worden.

Een POST request moet wel data bevatten. Zorg ervoor dat onderstaande data als raw JSON in de body van het request komt:

{
    "name": "Supplyaroo",
    "vat": "67890"
}

Doen we vervolgens weer een request naar /supplier met de GET methode dan is het resultaat een array met daarin beide leveranciers.

Op dezelfde manier kan de eerste leverancier ook bijgewerkt. Stuur een PUT request, met vergelijkbare data als het POST request, naar /supplier/1 en deze leverancier zal worden bijgewerkt.

Om een leverancier te verwijderen is geen data nodig. Stuur een DELETE request naar /supplier/1 en deze leverancier wordt verwijderd.

Klaar!

We hebben een werkende REST API gemaakt en hem getest met Postman.

Profile picture for user writeupp_sys_admin
Wilfred Waltman
PHP en Drupal ontwikkelaar

Ervaren PHP-ontwikkelaar met de focus op Drupal. Oprecht, to the point, klantgericht, analytisch, teamspeler en meewerkend voorman. Bewust van eigen kennis en nieuwsgierig naar nieuwe ontwikkelingen. Coach voor collega's en (sparring)partner voor de business.

Waar kunnen wij u mee helpen?

Wij helpen u graag met het beantwoorden van uw vraag.
Neem vrijblijvend contact met ons op om samen de mogelijkheden te verkennen.

Adres


Joke Smithoeve 48, 2743 JC Waddinxveen

Telefoon


06 - 22663583