user6784929
user6784929

Reputation: 31

Laravel dynamic class usage

I need implement a system for class calling like this:

Imagine there is a class Calendar.php handling database queries (or a model if you like) which should be used by default. If there is a need to implement some new behaviour I would like to implement a class MaterialCalendar.php which will extend the Calendar.php class so I will be able to use all the methods from the Calendar.php in the newly created MaterialCalendar.php with overriding parent methods if needed.

The issue is how to implement this kind of behaviour used anywhere in the code, in a view or in a controller:

if (child class exists) { use child class } else { use default parent class }

This behaviour above in pseudocode should be dynamic, so if there is no MaterialCalendar.php Calendar.php should be used by default, if MaterialCalendar.php exists then that should be used. It needs to be dynamic, irrelevant of the class names used in this example.

Upvotes: 3

Views: 2375

Answers (2)

samrap
samrap

Reputation: 5673

Andrej is off to a good start, but there is no need to design a container for this purpose. Laravel ships with a more than capable container in which you can bind implementations to interfaces.

Using Andrej's interface and concrete classes, you could create a service provider that binds the class you want to the ICalendar interface:

(Within the provider's register method):

$this->app->bind('App\Contracts\ICalendar', 'App\Models\MaterialCalendar');

Of course, it is here that you will conditionally decide which class to bind to the interface (see function class_exists).

From this point, you can rely on Laravel's container to inject this dependency wherever you need it. For example, in a CalendarController's store method, you could simply type hint the interface and Laravel will automagically give you an instance of the concrete class that you bound to it:

use App\Contracts\ICalendar;

class CalendarController extends Controller
{
    ...

    public function store(Request $request, ICalendar $calendar)
    {
        // Code...
    }
}

I definitely recommend reading the documentation on the container as well as providers. Something of interest to you might be Contextual Binding in which you can conditionally bind implementations to interfaces.

Upvotes: 0

Andrej
Andrej

Reputation: 7504

Define interface to guarantee that your code will work with any implementation of ICalendar:

interface ICalendar {
    public function method1();
    public function method2();
}

Create implementations:

class Calendar implements ICalendar {
    public function method1() {}
    public function method2() {}

}

class MaterialCalendar extends Calendar {
    public function method1() {}
    public function method2() {}
}

Simple container

class SimpleContainer {
    private static $binds = [];

    public static function bind($interface, $class) {
        self::$binds[$interface] = $class;
    }

    public static function make($interface) {
        if (array_key_exists($interface, self::$binds)) {
            return new self::$binds[$interface];
        }

        throw new \Exception("Interface is not binded");
    }
}

Somewhere in bootstrap you should identify what class to bind to the interface like:

SimpleContainer::bind(ICalendar::class, MaterialCalendar::class);

Use the following code in all places where you need to get calendar object:

SimpleContainer::make(ICalendar::class);

Upvotes: 1

Related Questions