adapik/sms-bundle
Symfony bundle for sending and scheduling SMS via multiple providers (MessageBird, SMS.ru, SMS Aero, SMS Discount, SMS Center). Configure multiple providers, pick one via ProviderManager, create Sms objects with optional delivery time, and send.
composer require yamilovs/sms-bundle ^1.0
config/bundles.php:
return [
// ...
Yamilovs\Bundle\SmsBundle\SmsBundle::class => ['all' => true],
];
config/packages/sms.yaml:
sms:
providers:
message_bird:
api_key: "%env(MESSAGE_BIRD_API_KEY)%"
# Other provider-specific configs
use Yamilovs\Bundle\SmsBundle\Service\ProviderManager;
use Yamilovs\Bundle\SmsBundle\Sms\Sms;
public function sendSms(ProviderManager $providerManager)
{
$sms = new Sms('+1234567890', 'Hello from Laravel!');
$provider = $providerManager->getProvider('message_bird');
$provider->send($sms);
}
src/Resources/docs/providers/ for provider-specific guides (e.g., message_bird.md).ProviderManager to dynamically switch providers..env (e.g., MESSAGE_BIRD_API_KEY).$sms = new Sms(
recipient: '+1234567890',
body: 'Your OTP is 123456',
sender?: 'YourApp', // Optional
scheduledAt?: new \DateTime('+1 hour') // Optional
);
$provider = $providerManager->getProvider('sms_ru');
$provider->send($sms);
DateTime object to scheduledAt.ProviderManager into services/controllers to avoid tight coupling.
public function __construct(private ProviderManager $providerManager) {}
$providers = ['message_bird', 'sms_ru'];
foreach ($providers as $providerName) {
try {
$provider = $providerManager->getProvider($providerName);
$provider->send($sms);
break;
} catch (\Exception $e) {
// Log or handle failure
}
}
sms.sent or sms.failed events (if supported; check bundle docs).ProviderManager and providers in unit tests:
$this->mockProviderManager->shouldReceive('getProvider')->andReturnSelf();
$this->mockProvider->shouldReceive('send')->once();
$activeProvider = $this->configService->get('sms.active_provider');
$provider = $providerManager->getProvider($activeProvider);
foreach ($recipients as $phone) {
$provider->send(new Sms($phone, $message));
}
$template = $this->templateRepository->find('welcome');
$sms = new Sms($phone, $template->render(['name' => 'John']));
Provider-Specific Quirks:
originator (sender ID) to be alphanumeric and pre-approved.from instead of sender in the API payload.src/Resources/docs/providers/ for provider-specific constraints.Rate Limiting:
try {
$provider->send($sms);
} catch (RateLimitException $e) {
sleep(2 ** $attempt); // Exponential backoff
retry();
}
Timezone Issues:
scheduledAt with a timezone-aware DateTime:
$scheduledAt = new \DateTime('+1 hour', new \DateTimeZone('UTC'));
$sms = new Sms($phone, $message, scheduledAt: $scheduledAt);
Missing Error Handling:
try-catch:
try {
$provider->send($sms);
} catch (\Exception $e) {
$this->logger->error('SMS failed', ['error' => $e->getMessage()]);
// Re-throw or handle gracefully
}
Configuration Overrides:
sms.yaml are merged. Override specific values per environment:
# config/packages/sms.yaml (dev)
sms:
providers:
message_bird:
api_key: "%env(MESSAGE_BIRD_API_KEY_DEV)%"
Enable Debug Mode:
Set debug: true in sms.yaml to log provider responses:
sms:
debug: true
var/log/dev.log.Validate Phone Numbers:
Use a library like libphonenumber to validate formats before sending:
use libphonenumber\PhoneNumberUtil;
use libphonenumber\PhoneNumberFormat;
$phoneUtil = PhoneNumberUtil::getInstance();
$phone = $phoneUtil->parse($rawPhone, 'RU'); // Country code
$validPhone = $phoneUtil->format($phone, PhoneNumberFormat::E164);
Check Provider Status:
$status = $provider->getStatus($sms->getId());
Custom Providers:
Implement the ProviderInterface to add support for new providers:
use Yamilovs\Bundle\SmsBundle\Provider\ProviderInterface;
class CustomProvider implements ProviderInterface {
public function send(Sms $sms): void {
// Custom logic (e.g., HTTP request to your SMS gateway)
}
}
Register it in services.yaml:
services:
Yamilovs\Bundle\SmsBundle\Provider\CustomProvider:
tags: ['sms.provider']
Middleware for SMS:
Add preprocessing/validation middleware by extending the Sms class or using a decorator pattern:
class ValidatedSms extends Sms {
public function __construct(string $recipient, string $body) {
if (!preg_match('/^\+[0-9]{10,15}$/', $recipient)) {
throw new \InvalidArgumentException('Invalid phone number');
}
parent::__construct($recipient, $body);
}
}
Database Logging: Extend the bundle to log sent SMS to a database table:
// After $provider->send($sms)
$this->smsRepository->create([
'phone' => $sms->getRecipient(),
'message' => $sms->getBody(),
'status' => 'sent',
'provider' => $providerName,
]);
Webhook Integration: Listen for provider webhooks (e.g., delivery reports) by setting up a Symfony event subscriber:
use Symfony\Component\HttpKernel\Event\RequestEvent;
class SmsWebhookSubscriber {
public function onKernelRequest(RequestEvent $event) {
if ($event->getRequest()->getPathInfo() === '/sms/webhook') {
$this->handleWebhook($event->getRequest());
}
}
}
How can I help you explore Laravel packages today?