Proxy Pattern
Type : Structurel
Difficulté : 6/10
Définition succincte : Le Proxy Pattern est un design pattern structurel qui agit comme un intermédiaire ou substitut pour contrôler l'accès à un autre objet. Il permet d'ajouter des fonctionnalités supplémentaires comme la mise en cache, la journalisation, ou la gestion des permissions, sans modifier l'objet d'origine. Le proxy intercepte les appels faits à l'objet réel et y ajoute une logique supplémentaire avant de déléguer la tâche à l'objet réel.
Contexte spécifique à Laravel : En Laravel, le Proxy Pattern est souvent utilisé pour intercepter des appels à des services critiques, ajouter des fonctionnalités telles que la mise en cache des résultats ou la journalisation des appels, et déléguer ensuite les tâches à l'objet réel. Un bon exemple serait l'interception des appels à une API externe pour ajouter de la journalisation ou du contrôle de cache, ou encore la gestion des accès.
Objectif du Proxy Pattern
Le Proxy Pattern a pour but de contrôler l'accès à un objet, en permettant d'ajouter des comportements comme le contrôle de cache, la gestion des accès, ou la journalisation avant d'appeler la méthode réelle. Cela permet de maintenir une séparation des préoccupations et de découpler la logique additionnelle (comme la sécurité ou la gestion des performances) de l'objet d'origine.
Structure du Proxy Pattern
- Subject : Interface commune implémentée par l'objet réel et le proxy.
- Proxy : Classe qui intercepte les appels à l'objet réel pour ajouter une logique supplémentaire (comme la journalisation, la mise en cache, etc.).
- RealSubject : L'objet réel qui exécute la logique principale.
1. Exemple d'utilisation dans Laravel
Dans Laravel, les facades sont un excellent exemple du Proxy Pattern. Les facades fournissent un accès statique à des services du Service Container, tout en interceptant les appels pour ajouter des comportements supplémentaires. Un exemple courant est l'utilisation de la méthode spy() de Mockery, qui permet d'intercepter les appels de méthodes sur une façade pour des tests.
a) Utilisation normale de Log
Imaginons que nous utilisions la façade Log pour journaliser des événements dans notre application.
// app/Http/Controllers/LoggingController.php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Log;
class LoggingController extends Controller
{
public function index()
{
// Journalisation d'un message
Log::info('User accessed the dashboard');
return response()->json(['message' => 'Logged!']);
}
}
b) Ajout du proxy avec spy() pour des tests
Nous allons maintenant utiliser la méthode spy() pour intercepter les appels faits à Log::info(). Cela permet d'agir comme un proxy, capturant et observant les appels sans modifier directement la logique du service.
// tests/Feature/LoggingTest.php
namespace Tests\Feature;
use Illuminate\Support\Facades\Log;
use Tests\TestCase;
class LoggingTest extends TestCase
{
public function test_logging_is_spied()
{
// Créer un proxy pour la façade Log
Log::spy();
// Simuler un appel au contrôleur
$response = $this->get('/logging');
// Vérifier que l'appel à Log::info a été intercepté
Log::shouldHaveReceived('info')
->once()
->with('User accessed the dashboard');
$response->assertStatus(200);
}
}
Dans cet exemple, la méthode spy() agit comme un proxy, intercepte l'appel à Log::info() et permet de vérifier que la journalisation a bien eu lieu sans toucher à l'objet Log lui-même.
Voici l'exemple réécrit avec un style plus concis, tout en conservant les parties de code intactes :
2. Implémentation complète dans Laravel
Imaginons que tu utilises une bibliothèque qui fait des appels vers une API propriétaire pour récupérer des données. Ces appels peuvent être coûteux, et tu veux ajouter une couche de mise en cache pour éviter de répéter les requêtes inutiles. Nous allons créer un Proxy pour encapsuler ce service et y ajouter cette logique de cache.
a) Service d'accès à l'API propriétaire
Voici un service basique qui fait un appel direct à une API externe pour récupérer des données.
// app/Services/ExternalApiService.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class ExternalApiService
{
public function fetchData(string $endpoint)
{
return Http::get($endpoint)->json();
}
}
b) Ajout d'un proxy avec Cache::remember
Le proxy interceptera les appels et mettra en cache les résultats pendant 10 minutes pour éviter les appels redondants.
// app/Services/ExternalApiServiceProxy.php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class ExternalApiServiceProxy
{
protected $realService;
public function __construct($realService)
{
$this->realService = $realService;
}
public function fetchData(string $endpoint)
{
return Cache::remember("api_data_{$endpoint}", 600, function () use ($endpoint) {
Log::info("Fetching data from API (not cached): $endpoint");
return $this->realService->fetchData($endpoint);
});
}
}
c) Utiliser le proxy dans une commande artisan
Nous créons une commande pour récupérer les données via le proxy, en affichant les résultats dans la console.
// app/Console/Commands/FetchApiData.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\ExternalApiService;
use App\Services\ExternalApiServiceProxy;
class FetchApiData extends Command
{
protected $signature = 'api:fetch {endpoint}';
protected $description = 'Fetch data from an external API with caching';
public function handle()
{
$realService = new ExternalApiService();
$proxyService = new ExternalApiServiceProxy($realService);
$endpoint = $this->argument('endpoint');
// Appel via le proxy
$data = $proxyService->fetchData($endpoint);
Log::info("Data fetched from cache for endpoint: $endpoint");
$this->info('Données récupérées :');
$this->line(json_encode($data, JSON_PRETTY_PRINT));
}
}
d) Exécution de la commande avec journalisation
Exécute cette commande pour observer les journaux. Lors du premier appel, tu devrais voir que les données sont récupérées via l'API. Lors des appels suivants, elles seront récupérées depuis le cache.
php artisan api:fetch https://api.example.com/data
Journaux générés
Dans les journaux Laravel (storage/logs/laravel.log), voici ce que tu devrais voir :
- Premier appel (API) :
[INFO] Fetching data from API (not cached): https://api.example.com/data
[INFO] Data fetched from cache for endpoint: https://api.example.com/data
- Appels suivants (cache) :
[INFO] Data fetched from cache for endpoint: https://api.example.com/data
Avec cette configuration, le premier appel passera par la bibliothèque API et sera journalisé comme "not cached", tandis que les appels suivants seront servis par le cache.
Avantages du Proxy Pattern
Flexibilité : Le Proxy Pattern permet d'ajouter dynamiquement des fonctionnalités supplémentaires (comme la journalisation ou la mise en cache) à un service sans toucher à l'objet réel.
Contrôle d'accès : Il est souvent utilisé pour contrôler l'accès à un objet, comme ajouter
des permissions ou de la validation avant d'exécuter une opération.
Séparation des préoccupations : Ce pattern permet de séparer la logique additionnelle (journalisation, cache, sécurité) de la logique principale de l'objet.
Inconvénients du Proxy Pattern
- Complexité accrue : Le proxy peut rendre le code plus difficile à suivre, car l'objet réel est encapsulé et la logique supplémentaire peut rendre le flux d'exécution moins évident.
- Performance : Bien que le proxy puisse ajouter des optimisations comme le cache, l'ajout d'une couche supplémentaire d'interception peut affecter les performances si mal implémenté.
Conclusion
Le Proxy Pattern est un outil puissant pour intercepter et modifier le comportement d'objets sans avoir à changer leur structure interne. En Laravel, il est souvent utilisé dans des cas comme la journalisation des appels, le contrôle de cache, ou la gestion des permissions. Ce pattern est particulièrement utile lorsque tu veux ajouter des fonctionnalités transversales à un service tout en maintenant une séparation claire des responsabilités.