Использование ООП подхода для рассылки писем через Unione (php, Yii2)

от автора

В данной статье мы бы хотели описать использованный нами ООП подход к отправке писем через данный сервис рассылок на php. Суть которого заключается в создании объекта инкапсулирующего данные необходимые для отправки письма и методы работы с ними. Кажется что это довольно простая идея, но нам не встречалось ещё в доступных источниках такого подхода, поэтому хотим внести свой вклад в этом направлении. Тем не менее статья написана не для того чтобы пропагандировать данный подход. Она написана с целью развития компетенций в области ООП и способности его использования практических задач, будь то отправка писем или ещё что-то.

С методом апи сервиса для отправки письма можно ознакомится по данной ссылке.

Для начала давайте рассмотрим применение такого объекта для отправки письма на примере письма об успешном резерве средств при инвестировании в проект (здесь и далее код приводится в контексте Yii2, однако его (код) довольно легко адаптировать под другую среду.).

<?php  namespace app\jobs\mailing;  use app\models\Email\NewReserveMail;  $newReserveMail = new NewReserveMail($user->email, $subject); $res = $newReserveMail->sendMessage([$user, $investment], '/app/mail/unione/user/letter_reserve.php');

Тут мы создаём объект письма передав в его конструктор емейл адрес получателя и тему письма. После этого с помощью метода sendMessage происходит отправка письма. В этот метод передаётся массив объектов из которых извлекаются данные для письма и путь к сохранённому файлу шаблона письма составленного на самом сервисе с помощью его конструктора.

Теперь давайте посмотрим на сам объект письма и как в нём извлекаются данные из входных объектов.

<?php  namespace app\models\Email;  use app\models\Investment\InvestmentReserve; use app\models\User\User; use Yii;  /**  * New investment reserve message  *  * @property-read array $properties  * @property-read array $substitutions  */ class NewReserveMail extends Message {     public function __construct(string $email, string $subject, bool $useTemplate = false)     {         parent::__construct($useTemplate);         $this->email    = $email;         $this->subject  = $subject;         $this->from     = Yii::$app->params['unione']['from'];         $this->sender   = Yii::$app->params['unione']['name'];         $this->template = Yii::$app->params['unione']['templates']['new_reserve']['template_id'];     }      public function getProperties(): array     {         return [             User::class => [                 'fullname' => function (User $user) {                     return $user->fullName;                 },                 'user_funds' => function (User $user) {                     $reserved = array_reduce($user->activeInvestmentReserves, function (int $sum, InvestmentReserve $reserve) {                         return $sum + $reserve->amount;                     }, 0);                     return $this->formatter->asDecimal(($user->userFunds->amount - $reserved)/100, 2);                 }             ],             InvestmentReserve::class => [                 'amount' => function (InvestmentReserve $reserve) {                     return $this->formatter->asDecimal($reserve->amount / 100, 2);                 },                 'name' => function (InvestmentReserve $reserve) {                     return $reserve->project->name;                 }             ]         ];     }      /**      * {@inheritDoc}      */     public function getSubstitutions(string $email = null): array     {         return [             "Name"            => $this->getSubstitution('fullname'),             "Invested_amount" => $this->getSubstitution('amount'),             "name"            => $this->getSubstitution('name'),             "Account_balance" => $this->getSubstitution('user_funds')         ];     }      public function getOptions(): array     {         return [];     } } 

В шаблоне письма присутствуют т.н. подстановки определяемые с помощью двойных фигурных скобочек {{подстановка}}. В приведённом выше методе NewReserveMail::getSubstitutions как раз определяются значения для этих подстановок, которые сервис использует при создании тела письма. В методе NewReserveMail::getProperties определяются данные, которые будут извлекаться из массива входных объектов в методе NewReserveMail::sendMessage с помощью вспомогательной утилиты ArrayHelper::toArray. Эти данные доступны через метод NewReserveMail::getSubstitution. Например, $this->getSubstitution(‘fullname’) получает значение fullname из объекта User.

Таким образом, создание новых типов писем сводится к созданию новый классов писем и определения в них методов getProperties и getSubstitutions с помощью которых определяются данные которые нужно извлечь из входных параметров и определяются подстановки для шаблона тела письма соответственно. Остальная логика заключена в базовом классе Message от которого они наследуются. И прежде чем рассмотреть его взглянем на шаблон тела письма.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">  <head>   </head>  <body> <p> Dear {{ Name }}, You've invested {{Invested_amount}} in the {{name}} project. Your balance is {{Account_balance}}. </p> </body>  </html>

Теперь рассмотрим базовый класс Message благодаря которому и имеет место быть использованный ООП подход.

<?php  namespace app\models\Email;  use app\services\backend\email\EmailServiceInterface; use app\services\backend\email\UniOneService; use Throwable; use Yii; use yii\base\Model; use yii\helpers\ArrayHelper; use yii\httpclient\Exception; use yii\httpclient\Response; use yii\web\View;  /**  * @property-read array $recipients  * @property-read array $globalSubstitutions  */ abstract class Message extends Model {     protected array  $data;     protected $template;     protected $email;     protected $subject;     protected $from;     protected $sender;     protected $formatter;     protected $useTemplate;      /**      * @var UniOneService      */     private $mailer;      /**      * Determines properties to be extracted from input objects      * @return array      */     abstract public function getProperties(): array;      /**      * Determines substitution array for unione message body      * @return array      */     abstract public function getSubstitutions(string $email): array;     abstract public function getOptions(): array;      /**      * Constructor      */     public function __construct(bool $useTemplate)     {         parent::__construct();          $this->useTemplate = $useTemplate;          try {             $this->mailer = Yii::$container->get(EmailServiceInterface::class);             $apiKey = Yii::$app->params['unione']['apiKey'];             $baseUrl = Yii::$app->params['unione']['baseUrl'];             $this->formatter = Yii::$app->formatter;             $this->mailer->initClient($apiKey, $baseUrl);              $this->from       = Yii::$app->params['unione']['from'];             $this->sender     = Yii::$app->params['unione']['name'];         } catch (Throwable $e) {             Yii::error($e->getMessage(), 'app');         }     }      /**      * Send message to the recipient      * @throws Exception      */     public function sendMessage(array $data, string $path = null): Response     {         $this->prepareData($data);          return $this->mailer->sendEmail($this->composeMessage($path));     }      /**      * Prepares data to be input into a message      * @param $data      */     public function prepareData($data): void     {         $sub = ArrayHelper::toArray($data, $this->properties);         foreach ($sub as $el) {             foreach ($el as $key => $value) {                 $this->data[$key] = $value;             }         }     }      /**      * Return ready to use array to send to the recipient      * @return array[]      */     public function composeMessage(string $path): array     {         $message = [             "message" => [                 "recipients" => $this->recipients,                 "subject"       => $this->subject,                 "from_email"    => $this->from,                 "from_name"     => $this->sender,                 'global_substitutions' => $this->getGlobalSubstitutions(),                 'options'              => $this->getOptions(), //                "skip_unsubscribe"     => 0             ]         ];         if ($this->useTemplate) {             $message['message']['template_id'] = $this->template;          } else {             $message['message']['body'] = [                 "html"  => $this->render($path)             ];         }         return $message;     }      public function getRecipients(string $email = null): array     {         $recipients = [             [                 "email"  => $email? $email : $this->email,                 "substitutions" => $this->substitutions             ]         ];         return $recipients;     }      /**      * Gets value from prepared data by the key      */     public function getSubstitution(string $name, string $email = null)     {         return $this->data[$name];     }      public function getGlobalSubstitution(array &$data, string $name)     {         return $data[$name];     }      public function render(string  $path)     {         $view = new View();         return $view->renderFile($path, $this->getRenderVariables());     }      public function getGlobalSubstitutions(): array     {         return [];     }      public function prepareGlobalData(array $data): array     {         $sub = ArrayHelper::toArray($data, $this->globalProperties);         $subData = [];         foreach ($sub as $el) {             foreach ($el as $key => $value) {                 $subData[$key] = $value;             }         }         return $subData;     }      public function getRenderVariables(): array     {         return $this->getGlobalSubstitutions();     } } 

Собственно говоря, тут два основных метода. Это Message::prepareData — с помощью которого обрабатываются данные из входных объектов. И метод Message::composeMessage с помощью которого формируется тело http запроса к сервису рассылок. Все остальные методы используются им для того чтобы заполнять соответствующие значения ключей массива данных запроса. $this->mailer -представляет собой реализацию отправки http запросов к сервису рассылок с помощью Yii2 HttpClient\Client клиента.

Нами рассмотрена реализация ОПП подхода к отправке писем через сервис рассылок Unione. К его преимуществам можно отнести то, что вся логика по составлению тела запроса к сервису рассылок находится в одном месте в базовом классе, что позволяет править её только в одном месте сразу для всех классов писем. Добавление же новых типов писем заключается в создании наследника и определении в нем данных которые нужно извлечь и которые нужно подставить в шаблон тела письма, что уменьшает вероятность ошибки при составлении тела запроса к сервису и при надлежащем освоении реализации может упростить и создание новых писем для не знакомого с апи сервиса рассылок пользователя.


ссылка на оригинал статьи https://habr.com/ru/post/688090/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *