مرحبا. في كثير من الأحيان ، عند العمل باستخدام كود قديم (وأحيانًا ليس كثيرًا) ، أو عند محاولة استخدام نوع من المكتبات ، فإنك تواجه قيود الامتداد. في كثير من الأحيان لن تكون هناك مشكلة إذا كان الرمز متعلمًا من الناحية المعمارية. هناك العديد من القواعد والأنماط المعمارية التي تسهل في النهاية توسيع وإعادة بناء وإعادة استخدام الكود. في هذه المقالة أريد أن أتطرق إلى بعضها في أمثلة.
منذ زمن بعيد ، في مشروع بعيد ، ظهرت خدمة ترسل رسالة بكلمة مرور جديدة للمستخدمين. شيء من هذا القبيل:
<?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), , ),
عام - نص الرسالة ، طريقة escapeHtml .
دعنا نحاول وضع العام في فصول منفصلة.
<?php
class ReminderPasswordMessageTextBuilder
{
public function buildMessageText($userName, $password)
{
return " {$userName}!
{$password}";
}
}
class Escaper
{
public function escapeHtml($string)
{
return htmlentities($string);
}
}
إذا نظرنا إلى الاختلافات ، فعادةً ما تختلف كلتا الخدمتين فقط في نص الرسالة ، وكذلك في المستلمين. دعنا نعيد كتابة كلتا الخدمتين بحيث تكونان مستقلين عن بعضهما البعض وتحتويان فقط على اختلافات.
<?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);
}
}
ووريث سابق
<?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;
}
}
وبالتالي ، على الرغم من أن الفئات قد اكتسبت عددًا من التبعيات ، إلا أنها أصبحت أكثر ملاءمة بشكل ملحوظ للتغطية بالاختبارات أو إعادة استخدام الأقسام الفردية من الكود. لقد تخلصنا من الارتباط بينهما ، ويمكننا بسهولة تطوير كل فئة منفصلة بشكل مستقل عن الأخرى.
ملاحظة: بالطبع ، لا تزال هذه الفصول الدراسية بعيدة عن المثالية ، ولكن أكثر من ذلك في وقت آخر.