Comment utiliser le modèle de référentiel dans une application Laravel

October 01, 2021
Rédigé par
Oluyemi Olususi
Contributeur
Les opinions exprimées par les contributeurs de Twilio sont les leurs
Révisé par

Un référentiel peut être défini comme une couche d'abstraction entre le domaine et les couches de mappage des données, qui fournit une voie de médiation entre les deux, via une interface de type collection pour l'accès aux objets de domaine.

Les frameworks PHP modernes, tels que Laravel et Symfony, interagissent avec les bases de données via les mappings objet-relationnel (ORM) ; Symfony utilise Doctrine comme ORM par défaut et Laravel utilise Eloquent.

Tous deux adoptent des approches différentes en ce qui concerne le fonctionnement de l'interaction avec les bases de données. Avec Eloquent, des modèles sont générés pour chaque table de base de données, formant ainsi la base de l'interaction. Doctrine, en revanche, utilise le modèle de référentiel dans lequel chaque entité dispose d'un référentiel correspondant contenant des fonctions d'aide pour interagir avec la base de données. Même si Laravel ne propose pas cette fonctionnalité par défaut, il est possible d'utiliser le modèle de référentiel dans les projets Laravel.

L'un des principaux avantages du modèle de référentiel est qu'il nous permet d'utiliser le principe d'inversion des dépendances (du code pour les abstractions, et non des concrétions). Cela rend notre code plus robuste aux changements, par exemple s'il est décidé ultérieurement de passer à une source de données qui n'est pas prise en charge par Eloquent.

Cela permet également d'organiser le code et d'éviter la duplication, car la logique liée à la base de données est conservée au même endroit. Bien que cet avantage ne soit pas immédiatement visible dans les petits projets, il le devient davantage dans les projets à grande échelle qui perdurent pendant de nombreuses années.

Dans cet article, je vais vous montrer comment implémenter le modèle de référentiel dans vos applications Laravel. Pour ce faire, nous allons créer une API pour gérer les commandes reçues des clients d'une entreprise.

Prérequis

Démarrez

Créez un nouveau projet Laravel et un cd dans le répertoire à l'aide des commandes suivantes.

laravel new order_api
cd order_api

Configurez la base de données

Pour ce tutoriel, nous utiliserons MySQL comme base de données. Pour ce faire, dans le fichier .env, mettez à jour les paramètres liés à la base de données comme indiqué ci-dessous.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=order_api
DB_USERNAME=<YOUR_DATABASE_USERNAME>
DB_PASSWORD=<YOUR_DATABASE_PASSWORD>

Enfin, à l'aide de votre application de gestion de base de données préférée, créez une nouvelle base de données appelée order_api.

Générez les données initiales pour la base de données

Nous sommes en train de créer une application de gestion des commandes. Nous allons donc créer le modèle correspondant en exécutant la commande suivante.

php artisan make:model Order -a

L'argument -a indique à Artisan que nous voulons créer un fichier de migration , un seeder, une usine et un contrôleur pour le modèle Order (Commande) .

La commande ci-dessus crée cinq nouveaux fichiers :

  • Un contrôleur dans app/HTTP/Controllers/OrderController.php
  • Une base de données usine dans database/factories/orderFactory.php
  • Un fichier de migration dans database/migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php
  • Un modèle situé dans app/Models/Order.php
  • Un fichier seeder dans database/seeders/OrderSeeder.php 

Dans database/migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php, mettez à jour la fonction up pour qu'elle corresponde aux éléments suivants.

public function up()
{
    Schema::create('orders', function (Blueprint $table) {
        $table->id();
        $table->text('details');
        $table->string('client');
        $table->boolean('is_fulfilled')->default(false);
        $table->timestamps();
    });
 }

Comme spécifié dans le fichier de migration, la table order comporte les colonnes suivantes :

  1. Un ID. Il s'agit de la clé principale de la table.
  2. Les détails de la commande.
  3. Le nom du client qui a passé la commande.
  4. Le fait que la commande ait été exécutée ou non.
  5. Dates de création et de mise à jour de la commande, created_at et updated_at, fournies par la fonction timestamps.

Ensuite, mettons à jour OrderFactory afin qu'il puisse générer une commande fictive pour alimenter la base de données. Dans database/factories/OrderFactory.php, mettez à jour la fonction definition pour qu'elle corresponde aux éléments suivants.

public function definition() 
{
    return [
        'details'       => $this->faker->sentences(4, true),
        'client'         => $this->faker->name(),
        'is_fulfilled' => $this->faker->boolean(),
    ];
}

Ensuite, ouvrez database/seeders/OrderSeeder.php et mettez à jour la fonction run pour qu'elle corresponde aux éléments suivants.

public function run() 
{
    Order::factory()->times(50)->create();
}

Cela permet d'utiliser OrderFactory pour créer 50 commandes dans la base de données.

Don't forget to add this import:

use AppModelsOrder;

Dans src/database/seeders/DatabaseSeeder.php, ajoutez les éléments suivants à la fonction run.

$this->call(
    [
        OrderSeeder::class
    ]
);

Cela permet d'exécuter le QuoteSeeder lorsque la commande db:seed d'Artisan est exécutée.

Enfin, exécutez vos migrations et alimentez la base de données à l'aide de la commande suivante.

php artisan migrate --seed

Si vous ouvrez la table orders (Commandes), vous verrez les nouvelles commandes.

Liste des commandes

Créez le référentiel

Avant de créer un référentiel pour le modèle Order, définissons une interface afin de spécifier toutes les méthodes que le référentiel doit déclarer. Au lieu de s'appuyer directement sur la classe du référentiel, notre contrôleur (et tout composant de commande que nous pourrions créer à l'avenir) dépendra de l'interface.

Cela rendra notre code flexible. Ainsi, s'il s'avère nécessaire d'effectuer une modification ultérieurement, le contrôleur restera inchangé. Par exemple, si nous décidons d'externaliser la gestion des commandes vers une application tierce, nous pourrons créer un nouveau module conforme à la signature de OrderRepositoryInterface et échanger les déclarations de liaison pour que notre contrôleur fonctionne exactement comme prévu, sans toucher à une seule ligne de code dans le contrôleur.

Dans le répertoire app, créez un nouveau dossier appelé Interfaces. Ensuite, dans Interfaces, créez un nouveau fichier appelé OrderRepositoryInterface.php et ajoutez-y le code suivant.

<?php

namespace App\Interfaces;

interface OrderRepositoryInterface 
{
    public function getAllOrders();
    public function getOrderById($orderId);
    public function deleteOrder($orderId);
    public function createOrder(array $orderDetails);
    public function updateOrder($orderId, array $newDetails);
    public function getFulfilledOrders();
}

Ensuite, dans le dossier app, créez un nouveau dossier appelé Référentiels. Dans ce dossier, créez un nouveau fichier appelé OrderRepository.php et ajoutez-y le code suivant.

<?php

namespace App\Repositories;

use App\Interfaces\OrderRepositoryInterface;
use App\Models\Order;

class OrderRepository implements OrderRepositoryInterface 
{
    public function getAllOrders() 
    {
        return Order::all();
    }

    public function getOrderById($orderId) 
    {
        return Order::findOrFail($orderId);
    }

    public function deleteOrder($orderId) 
    {
        Order::destroy($orderId);
    }

    public function createOrder(array $orderDetails) 
    {
        return Order::create($orderDetails);
    }

    public function updateOrder($orderId, array $newDetails) 
    {
        return Order::whereId($orderId)->update($newDetails);
    }

    public function getFulfilledOrders() 
    {
        return Order::where('is_fulfilled', true);
    }
}

Outre la flexibilité offerte par l'interface, l'encapsulation des requêtes de cette manière présente l'avantage supplémentaire de ne pas devoir dupliquer les requêtes dans l'application.

Si nous décidons à l'avenir de ne récupérer que les commandes non traitées dans la fonction getAllOrders(), nous n'aurons qu'à effectuer une modification à un seul endroit au lieu de rechercher tous les endroits où Order::all() est déclaré, et de risquer d'en manquer certains.

Créez des contrôleurs

Une fois notre référentiel en place, ajoutons du code à notre contrôleur. Ouvrez app/Http/Controllers/OrderController.php et mettez à jour le code pour qu'il corresponde aux éléments suivants.

<?php

namespace App\Http\Controllers;

use App\Interfaces\OrderRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class OrderController extends Controller 
{
    private OrderRepositoryInterface $orderRepository;

    public function __construct(OrderRepositoryInterface $orderRepository) 
    {
        $this->orderRepository = $orderRepository;
    }

    public function index(): JsonResponse 
    {
        return response()->json([
            'data' => $this->orderRepository->getAllOrders()
        ]);
    }

    public function store(Request $request): JsonResponse 
    {
        $orderDetails = $request->only([
            'client',
            'details'
        ]);

        return response()->json(
            [
                'data' => $this->orderRepository->createOrder($orderDetails)
            ],
            Response::HTTP_CREATED
        );
    }

    public function show(Request $request): JsonResponse 
    {
        $orderId = $request->route('id');

        return response()->json([
            'data' => $this->orderRepository->getOrderById($orderId)
        ]);
    }

    public function update(Request $request): JsonResponse 
    {
        $orderId = $request->route('id');
        $orderDetails = $request->only([
            'client',
            'details'
        ]);

        return response()->json([
            'data' => $this->orderRepository->updateOrder($orderId, $orderDetails)
        ]);
    }

    public function destroy(Request $request): JsonResponse 
    {
        $orderId = $request->route('id');
        $this->orderRepository->deleteOrder($orderId);

        return response()->json(null, Response::HTTP_NO_CONTENT);
    }
}

Le code injecte une instance OrderRepositoryInterface via le constructeur et utilise les méthodes de l'objet concerné dans chaque méthode de contrôleur.

En premier lieu, dans la méthode index(), il appelle la méthode getAllOrders() définie dans orderRepository pour récupérer la liste des commandes et renvoie une réponse au format JSON.

Ensuite, la méthode store() appelle la méthode createOrder()  à partir de orderRepository pour créer une nouvelle commande. Cette opération prend les détails de la commande à créer sous forme de tableau et renvoie ensuite une réponse positive.

Dans la méthode show() du contrôleur, elle récupère l'Id unique de la commande à partir de l'itinéraire et le transmet à getOrderById() en tant que paramètre. Cela permet d'extraire les détails de la commande avec un ID correspondant à partir de la base de données et de renvoyer une réponse au format JSON.

Ensuite, pour mettre à jour les détails d'une commande déjà créée, il appelle la méthode updateOrder() à partir du référentiel. Deux paramètres sont nécessaires : l'ID unique de la commande et les détails qui doivent être mis à jour.

Enfin, la méthode destroy() récupère l'ID unique d'une commande particulière à partir de l'itinéraire et appelle la méthode deleteOrder() du référentiel pour la supprimer.

Ajoutez des itinéraires

Pour mapper chaque méthode définie dans le contrôleur avec des itinéraires spécifiques, ajoutez le code suivant à routes/api.php.

Route::get('orders', [OrderController::class, 'index']);
Route::get('orders/{id}', [OrderController::class, 'show']);
Route::post('orders', [OrderController::class, 'store']);
Route::put('orders/{id}', [OrderController::class, 'update']);
Route::delete('orders/{id}', [OrderController::class, 'delete']);

Remember to include the import statement for the OrderController.

use App\Http\Controllers\OrderController;

Liez l'interface et l'implémentation

La dernière chose à faire est de lier OrderRepository à OrderRepositoryInterface dans le conteneur de services de Laravel ; nous le faisons via un fournisseur de services. Créez-en un à l'aide de la commande suivante.

php artisan make:provider RepositoryServiceProvider

Ouvrez app/Providers/RepositoryServiceProvider.php et mettez à jour la fonction register pour qu'elle corresponde aux éléments suivants.

public function register() 
{
    $this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
 }

Remember to include the import statement for OrderRepository and OrderRepositoryInterface.

use App\Interfaces\OrderRepositoryInterface;
use App\Repositories\OrderRepository;

Enfin, ajoutez le nouveau fournisseur de services au tableau providers dans config/app.php.

'providers' => [
    // ...other declared providers
    App\Providers\RepositoryServiceProvider::class,
];

Tester l'application

Exécutez l'application à l'aide de la commande suivante.

php artisan serve

Par défaut, l'application servie sera disponible à l'adresse http://127.0.0.1:8000/. À l'aide de Postman ou de cUrl, nous pourrons envoyer des demandes à notre nouvelle API.

Exécutez la commande suivante pour tester l'endpoint /api/orders à l'aide de cURL :

curl --silent http://localhost:8000/api/orders | jq

The response was formatted to JSON using jq.

Vous verrez sur votre terminal une sortie JSON similaire à l'exemple ci-dessous, qui a été tronqué pour faciliter la lisibilité.

{
  "data": [
    {
      "id": 1,
      "details": "Sit ullam cupiditate dolorem in. Magnam suscipit eaque occaecati facilis amet illum. Dolor perspiciatis velit laboriosam. Enim fugiat excepturi qui natus incidunt dolorem debitis ut.",
      "client": "Cydney Conn V",
      "is_fulfilled": 0,
      "created_at": "2021-09-09T09:18:28.000000Z",
      "updated_at": "2021-09-09T09:18:28.000000Z"
    },
    {
      "id": 2,
      "details": "Eum iste eum molestiae est. Voluptatibus veritatis earum commodi. Quod et laboriosam ratione dolor adipisci. Nam et debitis nobis ea sit.",
      "client": "Willow Herzog",
      "is_fulfilled": 1,
      "created_at": "2021-09-09T09:18:28.000000Z",
      "updated_at": "2021-09-09T09:18:28.000000Z"
    },
    {
      "id": 3,
      "details": "At maxime architecto repellat quidem id. Saepe provident quo eos officiis et tenetur. Et expedita maxime atque. Et consequuntur sequi aperiam possimus odio est ab.",
      "client": "Mr. Peyton Nolan DVM",
      "is_fulfilled": 1,
      "created_at": "2021-09-09T09:18:28.000000Z",
      "updated_at": "2021-09-09T09:18:28.000000Z"
    }
  ]
}

Voici comment utiliser le modèle de référentiel dans une application Laravel

Dans cet article, nous avons découvert le modèle de référentiel et appris comment l'utiliser dans une application Laravel. Nous avons également vu certains des avantages qu'il présente pour un projet à grande échelle, l'un d'entre eux étant un code faiblement couplé où nous codons en abstractions, et non en implémentations concrètes.

Je terminerai toutefois par une mise en garde. Pour les petits projets, cette approche donnera l'impression d'un travail considérable et d'un code standard pour des résultats qui peuvent ne pas être immédiatement visibles. Il est donc important d'évaluer correctement l'ampleur du projet avant d'adopter cette approche.

L'intégralité du code de ce tutoriel est disponible sur GitHub. N'hésitez pas à approfondir votre exploration. J'ai hâte de voir ce que vous allez construire !

Oluyemi est un passionné de technologie, spécialisé dans le domaine de l'ingénierie des télécommunications. Son intérêt marqué pour la résolution des problèmes quotidiens rencontrés par les utilisateurs l'a incité à se lancer dans la programmation. Depuis, il met ses compétences en matière de résolution de problèmes au service de la création de logiciels pour le Web et les appareils mobiles.

Ingénieur logiciel full stack et fervent défenseur du partage des connaissances, Oluyemi a publié de nombreux contenus et articles techniques sur plusieurs blogs sur Internet. Féru de technologie, il consacre une partie de son temps libre à tester de nouveaux frameworks et langages de programmation.