Skip to content

adding a driver

alizeinodin edited this page Sep 12, 2026 · 1 revision

Adding a custom driver

You can plug in any SMS gateway (or messenger) without forking the package by registering a driver.

There are two registration styles:

Method When to use
Sms::registerDriver($type, $class) Class is constructed as new $class($config) from DriverRegistry
Sms::extend($type, $closure) You need a custom factory (DI, mocked client, named connections)

1. Choose the right base

Base class Implements Use for
AbstractDriver SmsDriver Iranian / HTTP SMS panels (recommended)
AbstractMessengerDriver MessengerDriver Telegram, Bale, WhatsApp-style bots
AbstractHttpDriver MessageDriver Shared HTTP helpers only

For SMS you almost always extend AbstractDriver:

  • Override getName(), send(), and usually sendTemplate().
  • Everything else (getCredit, inbox, reports, …) throws UnsupportedDriverMethodException until you implement it.
  • Helpers like request(), toResponse(), normalizeMobiles() are inherited.

2. Minimal SMS driver

<?php

namespace App\Sms;

use Alizeinodin\SmsApi\Drivers\AbstractDriver;
use Alizeinodin\SmsApi\DTOs\SendResult;

class MyPanelDriver extends AbstractDriver
{
    public function getName(): string
    {
        return (string) ($this->config['name'] ?? 'mypanel');
    }

    public function send(string|array $mobiles, string $message, array $options = []): SendResult
    {
        $payload = [
            'to' => $this->normalizeMobiles($mobiles),
            'text' => $message,
            'from' => $options['lineNumber'] ?? $this->lineNumber,
        ];

        $decoded = $this->request('POST', 'send', $payload);

        return $this->toResponse($decoded, driver: $this->getName());
    }

    public function sendTemplate(
        string $mobile,
        int|string $templateId,
        array $parameters = [],
        array $options = [],
    ): SendResult {
        $decoded = $this->request('POST', 'verify', [
            'mobile' => $this->normalizeMobile($mobile),
            'template' => $templateId,
            'tokens' => $parameters,
        ]);

        return $this->toResponse($decoded, driver: $this->getName());
    }

    protected function buildClient(array $headers = []): \GuzzleHttp\Client
    {
        return parent::buildClient(array_merge([
            'Authorization' => 'Bearer ' . ($this->config['api_key'] ?? ''),
        ], $headers));
    }
}

Set base_url (and credentials) in config so buildClient() / request() work.


3. Register the driver

Option A — registerDriver (class map)

In AppServiceProvider::boot():

use Alizeinodin\SmsApi\Facades\Sms;
use App\Sms\MyPanelDriver;

Sms::registerDriver('mypanel', MyPanelDriver::class);

Then configure:

// config/smsapi.php
'drivers' => [
    'mypanel' => [
        'driver' => 'mypanel',
        'api_key' => env('MYPANEL_API_KEY'),
        'base_url' => env('MYPANEL_BASE_URL', 'https://api.mypanel.example/v1'),
        'line_number' => env('MYPANEL_LINE'),
        'timeout' => 30,
    ],
],
SMSAPI_DRIVER=mypanel
MYPANEL_API_KEY=...
Sms::driver('mypanel')->send('09121234567', 'Hello');

Option B — extend (factory closure)

Useful when the constructor needs more than $config, or you want named connections:

use Alizeinodin\SmsApi\Facades\Sms;
use App\Sms\MyPanelDriver;

Sms::extend('mypanel', function ($app, $name) {
    $config = $app->make(\Alizeinodin\SmsApi\SmsManager::class)->configuration($name);

    return new MyPanelDriver($config);
});

extend receives ($app, $connectionName). One-argument closures fn ($app) => … still work for BC.

Named connections (several accounts, one transport)

// config/smsapi.php
'connections' => [
    'mypanel-otp' => [
        'driver' => 'mypanel',
        'api_key' => env('MYPANEL_OTP_KEY'),
        'line_number' => env('MYPANEL_OTP_LINE'),
        'base_url' => env('MYPANEL_BASE_URL'),
    ],
    'mypanel-marketing' => [
        'driver' => 'mypanel',
        'api_key' => env('MYPANEL_MARKETING_KEY'),
        'line_number' => env('MYPANEL_MARKETING_LINE'),
        'base_url' => env('MYPANEL_BASE_URL'),
    ],
],
Sms::connection('mypanel-otp')->sendTemplate(...);
Sms::driver('mypanel-marketing')->send(...);

getName() → connection name (mypanel-otp)
getProvider() → transport / driver type (mypanel)


4. Messenger bots

Extend AbstractMessengerDriver and implement send() (recipients are chat/user ids). Register the same way:

Sms::registerDriver('mybot', \App\Sms\MyBotDriver::class);
Sms::driver('mybot')->send($chatId, 'Hello', ['chat_id' => $chatId]);

Do not type-hint SmsDriver for messengers — use MessageDriver / MessengerDriver.
Sms::smsDriver() rejects non-SMS channels.


5. Templates & failover

Once registered, the type key works everywhere else:

'templates' => [
    'otp-login' => [
        'mypanel' => 1001,
        'smsir' => 100000,
    ],
],
SMSAPI_FAILOVER_DRIVERS=mypanel,smsir,kavenegar
Sms::failover(['mypanel', 'kavenegar'])->send(...);
Sms::template('otp-login')->to($user)->send(['CODE' => '1234']);

6. Checklist

  1. Extend AbstractDriver (SMS) or AbstractMessengerDriver (chat).
  2. Implement at least getName() + send() (+ sendTemplate() for OTP patterns).
  3. Return SendResult via toResponse() / new SendResult(...).
  4. Register with registerDriver or extend.
  5. Add drivers.* or connections.* in config/smsapi.php.
  6. Optionally map templates and include the type in failover lists.

See also: Drivers · Configuration · Templates

Clone this wiki locally