69 lines
2.3 KiB
PHP
69 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Sms;
|
|
|
|
/**
|
|
* Кто повезёт сообщение. Точный оператор выигрывает у универсала, при равных
|
|
* условиях выигрывает цена. Так билайновский номер уходит через Билайн за 4,75 ₽,
|
|
* а остальные через универсала — без ручного выбора канала начальником.
|
|
*/
|
|
final class SmsRouter
|
|
{
|
|
/** @param array<int, SmsProvider> $providers */
|
|
public function __construct(private readonly array $providers) {}
|
|
|
|
public function pick(string $operator): ?SmsProvider
|
|
{
|
|
$exact = [];
|
|
$universal = [];
|
|
|
|
foreach ($this->providers as $provider) {
|
|
$serves = $provider->servesOperators();
|
|
|
|
if ($operator !== '' && in_array($operator, $serves, true)) {
|
|
$exact[] = $provider;
|
|
} elseif (in_array('*', $serves, true)) {
|
|
$universal[] = $provider;
|
|
}
|
|
}
|
|
|
|
$candidates = $exact !== [] ? $exact : $universal;
|
|
|
|
if ($candidates === []) {
|
|
return null;
|
|
}
|
|
|
|
usort(
|
|
$candidates,
|
|
fn (SmsProvider $a, SmsProvider $b) => $a->priceKopecks($operator) <=> $b->priceKopecks($operator),
|
|
);
|
|
|
|
return $candidates[0];
|
|
}
|
|
|
|
/**
|
|
* Запасной канал, если основной отказал. Работает ТОЛЬКО при включённом
|
|
* флаге services.sms.failover_to_universal (по умолчанию ВЫКЛ). Возвращает
|
|
* универсала, отличного от основного. Точка расширения — сейчас не вызывается
|
|
* из джоба (решение владельца 23.07.2026 — без резерва).
|
|
*/
|
|
public function fallbackFor(string $operator): ?SmsProvider
|
|
{
|
|
if (! config('services.sms.failover_to_universal')) {
|
|
return null;
|
|
}
|
|
|
|
$primary = $this->pick($operator);
|
|
|
|
foreach ($this->providers as $provider) {
|
|
if (in_array('*', $provider->servesOperators(), true) && $provider !== $primary) {
|
|
return $provider;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|