Salut. Très souvent, lorsque vous travaillez avec du code ancien (et parfois non), ou que vous essayez d'utiliser une sorte de bibliothèque, vous rencontrez des restrictions d'extension. Souvent, il n'y aurait aucun problème si le code possédait des compétences architecturales. Il existe de nombreuses règles et modèles d'architecture qui facilitent en fin de compte l'extension du code, la refactorisation et la réutilisation. Dans cet article, je veux aborder certains d'entre eux dans des exemples.
Il y a longtemps, dans un projet lointain, un service est apparu qui envoie une lettre avec un nouveau mot de passe aux utilisateurs. Quelque chose comme ça:
<?php
class ReminderPasswordService
{
protected function sendToUser($user, $message)
{
$this->getMailer()->send([
'from' => 'admin@example.com',
'to' => $user['email'],
'message' => $message
]);
}
public function sendReminderPassword($user, $password)
{
$message = $this->prepareMessage($user, $password);
$this->sendToUser($user, $message);
}
protected function prepareMessage($user, $password)
{
$userName = $this->escapeHtml($user['first_name']);
$password = $this->escapeHtml($password);
$message = " {$userName}!
{$password}";
$message = $this->format($message);
$message = $this->addHeaderAndFooter($message);
return $message;
}
protected function format($message)
{
return nl2br($message);
}
protected function escapeHtml($string)
{
return htmlentities($string);
}
protected function addHeaderAndFooter($message)
{
$message = "<html><body>{$message}<br> , !</body>";
return $message;
}
protected function getMailer()
{
return new Mailer('user', 'password', 'smtp.example.com');
}
}
, .. , , , - . , , , , . - . plainText, HTML. ( , , ).
<?php
class ReminderPasswordCopyToManagerService extends ReminderPasswordService
{
protected function send($user, $message)
{
$this->getMailer()->send([
'from' => 'admin@example.com',
'to' => 'manager@example.com',
'message' => $message
]);
}
protected function prepareMessage($user, $password)
{
$userName = $this->escapeHtml($user['first_name']);
$message = " {$userName}!
****";
return $message;
}
protected function getMailer()
{
return new Mailer('user2', 'password2', 'smtp.corp.example.com');
}
}
, , . smtp API . Mailer , . , , ?
Dependency Injection ( , DI)
DI - , , - , .
, . , , - . , - , . . Unit . , - DI, . :
<?php
class ReminderPasswordService
{
/**
* @var Mailer
*/
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
// getMailer, protected $mailer
// ...
}
, getMailer():
<?php
class ReminderPasswordCopyToManagerService extends ReminderPasswordService
{
protected function send($to, $message)
{
$this->mailer->send([
'from' => 'admin@example.com',
'to' => 'manager@example.com',
'message' => $message
]);
}
protected function prepareMessage($user, $password)
{
$userName = $this->escapeHtml($user['first_name']);
$message = " {$userName}!
****";
return $message;
}
}
, , . , Mailer, ( , , ) . , , .
(Dependency Inversion Principle, DIP)
- , . - .
. , , , . : , .
<?php
interface MailerInterface
{
public function send($emailFrom, $emailTo, $message);
}
.. - - MailMessageInterface , .
<?php
interface MailMessageInterface
{
public function setFrom($from);
public function getFrom();
public function setTo($to);
public function getTo();
public function setMessage($message);
public function getMessage();
}
MailSenderInterface, ,
<?php
interface MailerInterface
{
public function send(MailMessageInterface $message);
}
- MailMessageInterface,
<?php
interface MailMessageFactoryInterface
{
public function create(): MailMessageInterface;
}
, ,
<?php
class ReminderPasswordService
{
/**
* @var MailerInterface
*/
protected $mailer;
/**
* @var MailMessageFactoryInterface
*/
protected $messageFactory;
public function __construct(MailerInterface $mailer, MailMessageFactoryInterface $messageFactory)
{
$this->mailer = $mailer;
$this->messageFactory = $messageFactory;
}
protected function send($user, $messageText)
{
$message = $this->messageFactory->create();
$message->setFrom('admin@example.com');
$message->setTo($user['email']);
$message->setMessage($messageText);
$this->mailer->send($message);
}
//
public function sendReminderPassword($user, $password)
{
$message = $this->prepareMessage($user, $password);
$this->sendToUser($user, $message);
}
protected function prepareMessage($user, $password)
{
$userName = $this->escapeHtml($user['first_name']);
$password = $this->escapeHtml($password);
$message = " {$userName}!
{$password}";
$message = $this->format($message);
$message = $this->addHeaderAndFooter($message);
return $message;
}
protected function format($message)
{
return nl2br($message);
}
protected function escapeHtml($string)
{
return htmlentities($string);
}
protected function addHeaderAndFooter($message)
{
$message = "<html><body>{$message}<br> , !</body>";
return $message;
}
}
, , . .
<?php
class ReminderPasswordCopyToManagerService extends ReminderPasswordService
{
protected function send($to, $messageText)
{
$message = $this->messageFactory->create();
$message->setFrom('admin@example.com');
$message->setTo('manager@example.com');
$message->setMessage($messageText);
$this->mailer->send($message);
}
protected function prepareMessage($user, $password)
{
$userName = $this->escapeHtml($user['first_name']);
$message = " {$userName}!
****";
return $message;
}
}
VS
- . - , .
:
1. , .
2. , protected/private
3. , - - .
, , - , , . 90% ( , , ), .
, . , API, -
<?php
class SomeAPIService implements SomeAPIServiceInterface
{
public function getSomeData($someParam)
{
$someData = [];
// ...
return $someData;
}
}
, , . :
<?php
class SomeApiServiceCached extends SomeAPIService
{
public function getSomeData($someParam)
{
$cachedData = $this->getCachedData($someParam);
if ($cachedData === null) {
$cachedData = parent::getSomeData($someParam);
$this->saveToCache($someParam, $cachedData);
}
return $cachedData;
}
// ...
}
API , , DIP, .
<?php
class SomeApiServiceCached implements SomeAPIServiceInterface
{
private $someApiService;
public function __construct(SomeApiServiceInterface $someApiService)
{
$this->someApiService = $someApiService;
}
public function getSomeData($someParam)
{
$cachedData = $this->getCachedData($someParam);
if ($cachedData === null) {
$cachedData = $this->someApiService->getSomeData($someParam);
$this->saveToCache($someParam, $cachedData);
}
return $cachedData;
}
// ...
}
, , .
ReminderPasswordCopyToManagerService , " ". , - addHeaderAndFooter format, prepareMessage ( - (Open-Closed Principe), , ),
Général - corps du message, méthode escapeHtml .
Essayons de ramener le général dans des classes séparées.
<?php
class ReminderPasswordMessageTextBuilder
{
public function buildMessageText($userName, $password)
{
return " {$userName}!
{$password}";
}
}
class Escaper
{
public function escapeHtml($string)
{
return htmlentities($string);
}
}
Si nous examinons les différences, alors en général, les deux services ne diffèrent que par le texte du message, ainsi que par les destinataires. Réécrivons les deux services pour qu'ils soient indépendants l'un de l'autre et ne contiennent que des différences.
<?php
class ReminderPasswordService
{
// ,
private $mailer;
private $messageFactory;
private $escaper;
private $messageTextBuilder;
public function __construct(
MailerInterface $mailer,
MailMessageFactoryInterface $messageFactory,
Escaper $escaper,
ReminderPasswordMessageTextBuilder $messageTextBuilder
) {
$this->mailer = $mailer;
$this->messageFactory = $messageFactory;
$this->escaper = $escaper;
$this->messageTextBuilder = $messageTextBuilder;
}
public function sendReminderPassword($user, $password)
{
$messageText = $this->prepareMessage($user, $password);
$message = $this->messageFactory->create();
$message->setFrom('admin@example.com');
$message->setTo($user['email']);
$message->setMessage($messageText);
$this->mailer->send($message);
}
private function prepareMessage($user, $password)
{
$userName = $this->escaper->escapeHtml($user['first_name']);
$password = $this->escaper->escapeHtml($password);
$message = $this->messageTextBuilder->buildMessageText($userName, $password);
$message = $this->format($message);
$message = $this->addHeaderAndFooter($message);
return $message;
}
// .
private function addHeaderAndFooter($message)
{
$message = "<html><body>{$message}<br> , !</body>";
return $message;
}
private function format($message)
{
return nl2br($message);
}
}
et ancien héritier
<?php
class ReminderPasswordCopyToManagerService
{
private $mailer;
private $messageFactory;
private $escaper;
private $messageTextBuilder;
public function __construct(
MailerInterface $mailer,
MailMessageFactoryInterface $messageFactory,
Escaper $escaper,
ReminderPasswordMessageTextBuilder $messageTextBuilder
) {
$this->mailer = $mailer;
$this->messageFactory = $messageFactory;
$this->escaper = $escaper;
$this->messageTextBuilder = $messageTextBuilder;
}
public function sendReminderPasswordCopyToManager($user)
{
$messageText = $this->prepareMessage($user);
$message = $this->messageFactory->create();
$message->setFrom('admin@example.com');
$message->setTo($user['email']);
$message->setMessage($messageText);
$this->mailer->send($message);
}
private function prepareMessage($user)
{
$userName = $this->escaper->escapeHtml($user['first_name']);
$message = $this->messageTextBuilder->buildMessageText($userName, '****');
return $message;
}
}
Ainsi, bien que les classes aient acquis un certain nombre de dépendances, il est devenu beaucoup plus pratique de les couvrir avec des tests ou de réutiliser des sections de code individuelles. Nous nous sommes débarrassés de la connexion entre eux et nous pouvons facilement développer chaque classe distincte indépendamment de l'autre.
PS Bien sûr, ces classes sont encore loin d'être idéales, mais plus à ce sujet une autre fois.