
Adaptateur en PHP
L’Adaptateur est un patron de conception structurel qui permet à des objets incompatibles de collaborer.
L’adaptateur fait office d’emballeur entre les deux objets. Il récupère les appels à un objet et les met dans un format et une interface reconnaissables par le second objet.
Complexité :
Popularité :
Exemples d’utilisation : L’adaptateur est très répandu en PHP. On le retrouve souvent dans des systèmes basés sur du code hérité, dans lesquels l’adaptateur fait fonctionner du code hérité avec des classes modernes.
Identification : L’adaptateur peut être identifié grâce à son constructeur qui prend une instance d’un type abstrait différent ou d’une interface différente. Lorsque l’une des méthodes de l’adaptateur est appelée, il traduit les paramètres dans un format approprié et redirige l’appel vers une ou plusieurs méthodes de l’objet emballé.
Exemple conceptuel
Dans cet exemple, nous allons voir la structure de l’Adaptateur et répondre aux questions suivantes :
- Que contiennent les classes ?
- Quels rôles jouent-elles ?
- Comment les éléments du patron sont-ils reliés ?
Après avoir étudié la structure du patron, vous pourrez plus facilement comprendre l’exemple suivant qui est basé sur un cas d’utilisation réel en PHP.
index.php: Exemple conceptuel
<?php
namespace RefactoringGuru\Adapter\Conceptual;
/**
* The Target defines the domain-specific interface used by the client code.
*/
class Target
{
public function request(): string
{
return "Target: The default target's behavior.";
}
}
/**
* The Adaptee contains some useful behavior, but its interface is incompatible
* with the existing client code. The Adaptee needs some adaptation before the
* client code can use it.
*/
class Adaptee
{
public function specificRequest(): string
{
return ".eetpadA eht fo roivaheb laicepS";
}
}
/**
* The Adapter makes the Adaptee's interface compatible with the Target's
* interface.
*/
class Adapter extends Target
{
private $adaptee;
public function __construct(Adaptee $adaptee)
{
$this->adaptee = $adaptee;
}
public function request(): string
{
return "Adapter: (TRANSLATED) " . strrev($this->adaptee->specificRequest());
}
}
/**
* The client code supports all classes that follow the Target interface.
*/
function clientCode(Target $target)
{
echo $target->request();
}
echo "Client: I can work just fine with the Target objects:\n";
$target = new Target();
clientCode($target);
echo "\n\n";
$adaptee = new Adaptee();
echo "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";
echo "Adaptee: " . $adaptee->specificRequest();
echo "\n\n";
echo "Client: But I can work with it via the Adapter:\n";
$adapter = new Adapter($adaptee);
clientCode($adapter);
Output.txt: Résultat de l’exécution
Client: I can work just fine with the Target objects:
Target: The default target's behavior.
Client: The Adaptee class has a weird interface. See, I don't understand it:
Adaptee: .eetpadA eht fo roivaheb laicepS
Client: But I can work with it via the Adapter:
Adapter: (TRANSLATED) Special behavior of the Adaptee.
Analogie du monde réel
Le patron Adaptateur vous permet d’utiliser des classes externes ou héritées même si elles ne sont pas vraiment compatibles avec votre code. Par exemple, plutôt que de réécrire l’interface de notification de votre application pour pouvoir prendre en charge des services externes comme Slack, Facebook, SMS ou bien d’autres, vous pouvez créer un ensemble d’emballeurs qui vont envoyer les appels faits dans votre application vers une interface dans le format attendu par la classe externe.
index.php: Exemple du monde réel
<?php
namespace RefactoringGuru\Adapter\RealWorld;
/**
* The Target interface represents the interface that your application's classes
* already follow.
*/
interface Notification
{
public function send(string $title, string $message);
}
/**
* Here's an example of the existing class that follows the Target interface.
*
* The truth is that many real apps may not have this interface clearly defined.
* If you're in that boat, your best bet would be to extend the Adapter from one
* of your application's existing classes. If that's awkward (for instance,
* SlackNotification doesn't feel like a subclass of EmailNotification), then
* extracting an interface should be your first step.
*/
class EmailNotification implements Notification
{
private $adminEmail;
public function __construct(string $adminEmail)
{
$this->adminEmail = $adminEmail;
}
public function send(string $title, string $message): void
{
mail($this->adminEmail, $title, $message);
echo "Sent email with title '$title' to '{$this->adminEmail}' that says '$message'.";
}
}
/**
* The Adaptee is some useful class, incompatible with the Target interface. You
* can't just go in and change the code of the class to follow the Target
* interface, since the code might be provided by a 3rd-party library.
*/
class SlackApi
{
private $login;
private $apiKey;
public function __construct(string $login, string $apiKey)
{
$this->login = $login;
$this->apiKey = $apiKey;
}
public function logIn(): void
{
// Send authentication request to Slack web service.
echo "Logged in to a slack account '{$this->login}'.\n";
}
public function sendMessage(string $chatId, string $message): void
{
// Send message post request to Slack web service.
echo "Posted following message into the '$chatId' chat: '$message'.\n";
}
}
/**
* The Adapter is a class that links the Target interface and the Adaptee class.
* In this case, it allows the application to send notifications using Slack
* API.
*/
class SlackNotification implements Notification
{
private $slack;
private $chatId;
public function __construct(SlackApi $slack, string $chatId)
{
$this->slack = $slack;
$this->chatId = $chatId;
}
/**
* An Adapter is not only capable of adapting interfaces, but it can also
* convert incoming data to the format required by the Adaptee.
*/
public function send(string $title, string $message): void
{
$slackMessage = "#" . $title . "# " . strip_tags($message);
$this->slack->logIn();
$this->slack->sendMessage($this->chatId, $slackMessage);
}
}
/**
* The client code can work with any class that follows the Target interface.
*/
function clientCode(Notification $notification)
{
// ...
echo $notification->send("Website is down!",
"<strong style='color:red;font-size: 50px;'>Alert!</strong> " .
"Our website is not responding. Call admins and bring it up!");
// ...
}
echo "Client code is designed correctly and works with email notifications:\n";
$notification = new EmailNotification("developers@example.com");
clientCode($notification);
echo "\n\n";
echo "The same client code can work with other classes via adapter:\n";
$slackApi = new SlackApi("example.com", "XXXXXXXX");
$notification = new SlackNotification($slackApi, "Example.com Developers");
clientCode($notification);
Output.txt: Résultat de l’exécution
Client code is designed correctly and works with email notifications:
Sent email with title 'Website is down!' to 'developers@example.com' that says '<strong style='color:red;font-size: 50px;'>Alert!</strong> Our website is not responding. Call admins and bring it up!'.
The same client code can work with other classes via adapter:
Logged in to a slack account 'example.com'.
Posted following message into the 'Example.com Developers' chat: '#Website is down!# Alert! Our website is not responding. Call admins and bring it up!'.