Задача
Добавить в приложение систему оповещения пользователей через СМС-сообщения с возможностью выбора провайдера.
Решение
Оптимальным решением, на мой взгляд, является добавление собственного компонента.
Компонент — это блок программы с четко определенным набором действий (контрактом), способный решать возложенные на него задачи посредством различных драйверов.
При построении нашего компонента, мы будем придерживаться тех же принципов, на которых построены собственные компоненты Laravel. Так, например, компонент
Cache
может использовать драйвера: file
, memcached
или redis
.Итак, приступим.
Драйвера
Как гласит один из постулатов: программируйте в соответствии с интерфейсом, а не с реализацией.
С него и начнем, но в контексте компонента точнее использовать термин контракт. Контракт, по сути, набор интерфейсов для описания функционала компонента.
interface SmsContract
{
public function send();
}
Далее добавим драйвера, исполняющие контракт. Общую логику вынесем в абстрактный класс.
abstract class Driver implements SmsContract
{
protected $message;
protected $recipient;
public function to($phoneNumber)
{
$this->recipient = $phoneNumber;
}
public function content($message)
{
$this->message = $message;
}
abstract public function send();
}
А непосредственно логику отправки опишем в каждом классе драйвера.
class SmsRuDriver extends Driver
{
protected $client;
protected $from;
public function __construct(SmsRuClient $smsRuClient, $from)
{
$this->client = $smsRuClient;
$this->from = $from;
}
public function send()
{
return $this->client->sendSms([
'type' => 'text',
'from' => $this->from,
'to' => $this->recipient,
'text' => trim($this->message)
]);
}
}
class SmartSenderDriver extends Driver
{
protected $client;
protected $from;
public function __construct(SmartSenderClient $smartSenderClient, $from)
{
$this->client = $smartSenderClient;
$this->from = $from;
}
public function send()
{
return $this->client->message()->send([
'type' => 'text',
'from' => $this->from,
'to' => $this->recipient,
'text' => trim($this->message)
]);
}
}
Управление Компонентом
Для выбора и настройки драйверов добавим класс
SmsManager
, унаследованный от класса Manager
.use Illuminate\Support\Manager;
class SmsManager extends Manager
{
public function setDriver($name = null)
{
return $this->driver($name);
}
public function createSmsRuDriver(): SmsContract
{
return new SmsRuDriver(
$this->createSmsRuClient(),
$this->app['config']['sms.sms_ru.from']
);
}
public function createSmartSenderDriver(): SmsContract
{
return new SmartSenderDriver(
$this->createSmartSenderClient(),
$this->app['config']['sms.smart_sender.from']
);
}
public function getDefaultDriver()
{
return $this->app['config']['sms.default'];
}
protected function createSmsRuClient()
{
return new SmsRuClient(
$this->app['config']['sms.sms_ru.key'],
$this->app['config']['sms.sms_ru.secret']
);
}
protected function createSmartSenderClient()
{
return new SmartSenderClient(
$this->app['config']['sms.smart_sender.key'],
$this->app['config']['sms.smart_sender.secret']
);
}
}
Теперь достаточно указать нужные параметры в файле конфигурации и класс
SmsManager
укажет нашему компоненту через какой драйвер отправлять СМС-сообщения. Фасад
Для доступа к функционалу компонента из любой точки приложения добавим фасад.
Сначала создаем сам класс фасада:
use Illuminate\Support\Facades\Facade;
class Sms extends Facade
{
protected static function getFacadeAccessor()
{
return 'sms';
}
}
Далее связываем класс
SmsManager
с фасадом с помощью сервис-провайдера и регистрируем псевдоним.class SmsServiceProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->singleton('sms', function ($app) {
return new SmsManager($app);
});
}
public function provides()
{
return ['sms'];
}
}
И последнее, регистрируем наш сервис-провайдер вместе с остальными провайдерами в файле конфигурации (
app/config/app.php
).'providers' => [
...
App\Providers\SmsServiceProvider::class,
],
Применение
Когда для нашего компонента создан фасад и указаны параметры в файле конфигураций, для отправки СМС-сообщения достаточно добавить следующий код:
Sms::to($phone)->content('Beware! He`s not who he is')->send();
Или, если необходимо явно указать драйвер:
Sms::setDriver('sms_ru')->to($phone)->content('why don`t you answer me?')->send();
Вывод
Наш компонент построен по принципу проектирования Мост, то есть реализация и абстракция разделены таким образом, что могут изменяться независимо. Мы можем управлять драйверами через класс
SmsManager
, при этом сам код применения остается без изменений.Фасад обеспечивает простой доступ к функционалу нашего компонента.
Такой подход к проектированию компонентов обеспечивает простоту и гибкость их использования.
Ссылки
Laravel Socialite компонент для аутентификации через различные соц.сети
webdevium
Чем стандартное решение из коробки не подошло?