Extend Laravel with our own components

Task


Add an alert system to users through SMS messages with the option of choosing a provider.

Decision


The best solution, in my opinion, is to add your own component.
A component is a program block with a clearly defined set of actions (contract) that can solve the tasks assigned to it through various drivers. For example, the built-in component Cache may use the driver: file, memcachedor redis.

When building our component, we will apply the principle of designing the Bridge, the same principle on which the Laravel components are built.

So let's get started.

Drivers


As one of the postulates says: program in accordance with the interface, and not with the implementation.

We’ll start with it, but in the context of the component it is more accurate to use the term contract. A contract is essentially a set of interfaces for describing the functionality of a component.

interface SmsContract
{
    public function send();
}

Next, add the drivers that execute the contract. We take out the general logic in an abstract class.

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();
}

And we will directly describe the sending logic in each driver class.

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)
        ]);
    }
}

Component Management


To select and configure drivers, add a class SmsManagerinherited from the class 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']
        );
    }

}

Now it’s enough to specify the necessary parameters in the configuration file and the class SmsManager will tell our component through which driver to send SMS messages.

Facade


To access the component’s functionality from anywhere in the application, add a facade .
First, create the facade class itself:

use Illuminate\Support\Facades\Facade;

class Sms extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'sms';
    }
}

Next, we associate the class SmsManager with the facade using the service provider and register an alias.

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'];
    }
}

And lastly, register our service provider along with the other providers in the configuration file ( app/config/app.php).

'providers' => [
    ...
    App\Providers\SmsServiceProvider::class,
],    

Application


When a facade is created for our component and the parameters are specified in the configuration file, to send an SMS message, just add the following code:

Sms::to($phone)->content('Beware! He`s not who he is')->send();

Or, if you need to explicitly specify the driver:

Sms::setDriver('sms_ru')->to($phone)->content('why don`t you answer me?')->send();

Conclusion


Our component is built on the principle of designing a Bridge, that is, implementation and abstraction are separated in such a way that they can be changed independently. We can manage drivers through the class SmsManager, while the application code itself remains unchanged.
The facade provides easy access to the functionality of our component.

This approach to component design provides simplicity and flexibility in their use.

References


Laravel Socialite component for authentication through various social networks

All Articles