Singleton Pattern
- Type : Créationnel
- Difficulté : 2/10
- Définition succincte : Le Singleton Pattern est un design pattern de création qui restreint l'instanciation d'une classe à une seule instance et fournit un point d'accès global à cette instance. Ce pattern est utile pour des services partagés ou des objets qui doivent avoir une seule et même instance tout au long du cycle de vie de l'application.
- Contexte spécifique à Laravel : En Laravel, ce pattern est utilisé pour des composants critiques tels que les Facades et les services enregistrés via les Service Providers, où une seule instance d'un service est partagée dans l'application.
Contexte
Dans un projet Laravel, il est souvent nécessaire d'avoir une instance unique d'un service, par exemple pour gérer une connexion à une API externe, ou encore pour gérer des services de journalisation. Utiliser le Singleton Pattern garantit qu'il n'y a qu'une seule instance du service, même si celui-ci est appelé plusieurs fois dans différentes parties de l'application.
1. Exemple d'utilisation dans Laravel
En Laravel, le Singleton Pattern est implémenté de manière native dans les Facades et les Service Providers. Par exemple, la Log Facade utilise une seule instance du service de log :
Log::info('This is a log message.');
Laravel utilise le Service Container pour garantir qu'une seule instance du logger est partagée dans toute l'application. Cela signifie que, même si tu utilises Log::info() plusieurs fois, c'est la même instance du service de log qui est utilisée.
2. Implémentation complète dans Laravel
Supposons que tu crées un service qui gère l'envoi d'e-mails et que tu souhaites t'assurer qu'il n'y ait qu'une seule instance de ce service dans toute l'application. Le Singleton Pattern permet de garantir que ce service ne sera instancié qu'une seule fois.
a) Définir un contract
Definis un contract pour notre service d'e-mails, afin d'établir une interface commune pour toute implémentation future.
// app/Contracts/EmailServiceInterface.php
namespace App\Contracts;
interface EmailServiceInterface
{
public function send($recipient, $subject, $body): void;
}
b) Creation du service
Voici l'implémentation d'un service d'envoi d'e-mails qui utilise le Singleton Pattern pour s'assurer qu'il n'y ait qu'une seule instance dans l'application.
// app/Services/EmailService.php
namespace App\Services;
use App\Contracts\EmailServiceInterface;
class EmailService implements EmailServiceInterface
{
private static ?EmailService $instance = null;
// Le constructeur est privé pour empêcher l'instanciation directe
private function __construct() {}
// Méthode pour obtenir l'unique instance du service
public static function getInstance(): EmailService
{
if (self::$instance === null) {
self::$instance = new EmailService();
}
return self::$instance;
}
// Implémentation de la méthode pour envoyer des e-mails
public function send($recipient, $subject, $body): void
{
// Simule l'envoi d'un e-mail (en réalité, tu utiliserais un service comme Mailgun ou SMTP)
echo "Sending email to $recipient with subject '$subject'. Body: $body" . PHP_EOL;
}
}
c) Utilisation du pattern
Nous allons maintenant utiliser ce service d'e-mails dans une commande CLI pour vérifier son comportement en tant que singleton.
// app/Console/Commands/TestEmailService.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\EmailService;
class TestEmailService extends Command
{
protected $signature = 'test:email-service';
protected $description = 'Test the EmailService Singleton';
public function handle()
{
// Obtenir deux instances du service d'e-mails
$emailService1 = EmailService::getInstance();
$emailService2 = EmailService::getInstance();
// Envoyer deux e-mails pour tester le service
$emailService1->send('user1@example.com', 'Subject 1', 'Body of email 1');
$emailService2->send('user2@example.com', 'Subject 2', 'Body of email 2');
// Vérifier si les deux instances sont identiques
if ($emailService1 === $emailService2) {
$this->info('EmailService is a Singleton. Both instances are the same.');
} else {
$this->error('EmailService is not a Singleton. Instances are different.');
}
}
}
d) Déclaration du service en tant que Singleton
Dans ton service provider, tu peux enregistrer ce service en tant que singleton pour t'assurer qu'une seule instance est créée et réutilisée :
// app/Providers/AppServiceProvider.php
use App\Services\EmailService;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
// Enregistre le service comme singleton
$this->app->singleton(EmailService::class, function ($app) {
return new EmailService();
});
}
public function boot()
{
//
}
}
3. Utilisation du Singleton dans l'application
Tu peux maintenant injecter le service EmailService dans ta commande directement, et Laravel va s'assurer qu'une seule instance du service sera partagée à travers toute l'application.
Simplification du service
// app/Services/EmailService.php
namespace App\Services;
use App\Contracts\EmailServiceInterface;
class EmailService implements EmailServiceInterface
{
// Implémentation de la méthode pour envoyer des e-mails
public function send($recipient, $subject, $body): void
{
// Simule l'envoi d'un e-mail (en réalité, tu utiliserais un service comme Mailgun ou SMTP)
echo "Sending email to $recipient with subject '$subject'. Body: $body" . PHP_EOL;
}
}
Utilisation du pattern
// app/Console/Commands/TestEmailService.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\EmailService;
class TestEmailService extends Command
{
protected $signature = 'test:email-service';
protected $description = 'Test the EmailService Singleton';
public function handle()
{
// Obtenir deux instances du service d'e-mails
$emailService1 = resolve(EmailService::class);
$emailService2 = resolve(EmailService::class);
// Envoyer deux e-mails pour tester le service
$emailService1->send('user1@example.com', 'Subject 1', 'Body of email 1');
$emailService2->send('user2@example.com', 'Subject 2', 'Body of email 2');
// Vérifier si les deux instances sont identiques
if ($emailService1 === $emailService2) {
$this->info('EmailService is a Singleton. Both instances are the same.');
} else {
$this->error('EmailService is not a Singleton. Instances are different.');
}
}
}
Exemple de résultat
En exécutant la commande CLI suivante :
php artisan test:email-service
Tu verras le résultat suivant dans la console :
Sending email to user1@example.com with subject 'Subject 1'. Body: Body of email 1
Sending email to user2@example.com with subject 'Subject 2'. Body: Body of email 2
EmailService is a Singleton. Both instances are the same.
À chaque appel de la méthode send, Laravel utilise la même instance de EmailService grâce au Singleton Pattern. Cela signifie que si le service utilise des ressources coûteuses ou des configurations spécifiques, ces dernières ne seront instanciées qu'une seule fois, optimisant ainsi les performances.
Avantages du Pattern
- Accès global : Le Singleton fournit un point d'accès global à une instance unique dans toute l'application.
- Optimisation des ressources : Il réduit l'utilisation de la mémoire en assurant qu'une seule instance est créée, même si elle est appelée plusieurs fois.
- Contrôle d'instanciation : Il garantit que certaines classes critiques ne sont instanciées qu'une seule fois.
Inconvénients
- Difficulté à tester : Les singletons peuvent rendre les tests unitaires plus compliqués, car ils maintiennent un état global qui peut affecter plusieurs tests.
- Potentiel d'abus : Un usage excessif peut conduire à un couplage fort, rendant le code moins flexible et plus difficile à maintenir.
Conclusion
Le Singleton Pattern est particulièrement utile pour des services critiques comme un service d'envoi d'e-mails, où une seule instance doit exister dans toute l'application pour éviter des doublons ou des conflits. En Laravel, ce pattern est souvent utilisé dans les Facades et les Service Providers, mais il peut être implémenté manuellement pour des cas spécifiques comme dans cet exemple.