0

For some context - earlier today I was struggling to figure out how to implement a facade similar to Cache - where I could set a provider (like disk()), but also have a generic fall back provider when not supplied.

Now, I got the basic infrastructure working, but I think my implementation is nasty. Having call default() or provider() just stinks. However, there is a concept or something I'm missing to fill in the gaps here.

Implementing similar functionality to Cache::disk('x') in Laravel

Here is what I've done.

// Factories\SMSFactory.php

namespace App\Factories;

use App\IError;


class SMSFactory
{
    public static function default()
    {
        $defaultProvider = config('sms.default_provider');
        return self::provider($defaultProvider);
    }

    public static function provider($providerId)
    {
        $providerClass = config('sms.' . $providerId);

        if (class_exists($providerClass))
        {
            return (new $providerClass);
        }

        return new class implements IError {

        };
    }

}

// sms.php (config)

return [
    /**
     * Set the default SMS provider for the application
     */
    'default_provider' => 'smsglobal',

    /**
     * Map the SMS provider to a class implementation
     */
    'smsglobal' => 'App\SMSGlobal\SMSGlobal',
];

// Providers\SMSServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;


class SMSServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('sms', 'App\Factories\SMSFactory');
    }
}

// Facades\SMS.php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

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


// app.php

App\Providers\SMSServiceProvider::class,

# and in aliases

'SMS' => App\Facades\SMS::class,


// Controllers/TestController.php


namespace App\Http\Controllers\TestController;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

use App\Facades\SMS;


class TestController extends Controller
{
    public function sendSMS($destination, $message)
    {
        $data = $request->all();

        return SMS::default()->send([
            'destination' => $destination,
            'message' => $message,
        ]);
    }
}

What is really bothering me is having to always use default()...

I understand that the facade acts as a static class, but is it possible to set it up in such a way that I could make calls like this?

SMS::send($args);

// When I want to use another gateway
SMS::provider('nexmo')->send($args);
Community
  • 1
  • 1
Trent
  • 2,909
  • 1
  • 31
  • 46
  • In your provider classes do you have a way to change to a different provider? – Rwd Apr 04 '17 at 07:10
  • The service provider binds the factory. So I can currently switch gateways by SMS::provider('new-gateway') as long as 'new-gateway' is defined in the sms.php config file. That part is all working – Trent Apr 04 '17 at 08:25

1 Answers1

1

You can use the __call method in your SMS Factory class, and it must be changed accordingly:

class SMSFactory
{
    public function default()
    {
        $defaultProvider = config('sms.default_provider');

        return $this->provider($defaultProvider);
    }

    public function provider($providerId)
    {
        $providerClass = config('sms.' . $providerId);

        if (class_exists($providerClass))
        {
            return (new $providerClass);
        }

        return new class implements IError {

        };
    }

    public function __call($name, $arguments)
    {
        if (!method_exists($this, $name)) {
            $object = [$this->default(), $name];
        } else {
            $object = [$this, $name];
        }

        return call_user_func_array($object, $arguments);
    }
}

The SMSFactory class should not have static methods as the static methods can be accessed through facade.

Mihai Matei
  • 24,166
  • 5
  • 32
  • 50
  • So the magic method means I can make any method call to the facade as static, and this will try and generate a new instance and then run the command on that instance. When I just put this into my code it tells me call to undefined method SMSFactory::send(). From controller I run SMS::send('Testing'); – Trent Apr 04 '17 at 08:43
  • I also noticed call_user_func_array when method doesn't exist is not passing the function name down to the instance so I changed it to [$default, $name], $arguments - but still didn't solve the issue – Trent Apr 04 '17 at 08:48
  • Seems it - the magic method is being called - the error is being raised at line 221 of Facade.php - Going to check that out now – Trent Apr 04 '17 at 09:04
  • Take a look at my updated answer. The factory class should not have static methods as the facade is using static methods instead. I think this update answer will work for you – Mihai Matei Apr 04 '17 at 09:05
  • 1
    Minor edit $object = [$this->default(), $method] and all good! If you can just make that quick update so I can accept your answer - in case anyone else is battling with this. Thanks mate! – Trent Apr 04 '17 at 09:13