Dependencies
Dependency management is done through the service container in the same way as in other frameworks. This allows to perform dependency injection and bind interfaces to implementations or class constructors to existing instances.
Bundled version: If you bundle the framework with your Plugin, you should prepend your plugin namespace to the Sci namespace when referencing framework components. For example, if you use the bundled version you should reference the MyPlugin\Sci\View class instead of the Sci\View class.
Introduction
The Container is usually referenced as the Service Container, as it's usually used to inject or bind services to the controllers. An instance of the Container is stored in the core Sci\Sci class. You can access the container and all of the methods it includes by just using the container
method of the Sci\Sci class:
namespace MyPlugin;use Sci\Sci;# Get Sci instance$sci = Sci::instance();# Get container instance$container = $sci->container();
After getting the container instance you will be able to use all the included methods, covered in this section. However, let's learn first an important concept.
The SCI WP Framework container allows to define dependencies in class constructor so the instances of the required dependencies are automatically created. If you are used to use Dependency Injection (DI) with other frameworks, then this is exactly the same. However, if you are new to it, it's worth to check this example, as it's easier than it sounds:
namespace MyPlugin\App\Controllers;use Sci\Controller;use MyPlugin\App\Services\CarManager;class CarController extends Controller{# Will contain the CarManager instanceprotected $carManager;# We use type in the CarManager dependencypublic function __construct(CarManager $carManager){# The CarManager instance is already created$this->carManager = $carManager;}# Use the CarManager to get a carpublic function getCar($carId){$car = $this->carManager->get($carId);return $car;}}
In the previous example we don't need to create a new instance of the CarManager, as we already injected it in as an argument. But how can this happen? the trick is in the make
method of the Container class. This controller is probably the endpoint of a route which creates an instance of this controller with the make method. Here is an example of a possible route linked to this controller:
namespace MyPlugin;use Sci\Route;Route::get('/cars/{carId}', 'MyPlugin\App\Controllers\CarController@getCar');
When a petition is sent to the /cars/{carId}
endpoint, the Route Manger creates a new instance of the CarController class using the make
method, which crates all required instances for the injected dependencies.
Make Method
The make
method of the Sci\Container class allows to create instances of any class, while creating also instances of the dependencies defined in the constructor.
You can also use the make
shortcut method included in the Sci\Sci class.
Usage
When using the make
method, dependency instances are created in a recurring way. That is, if the dependencies have more dependencies, they will also be instantiated when creating them, and so on.
For instance, let's create an instance of the CarController class we defined previously:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Controllers\CarController;# Get the container instance$container = Sci::instance()->container();# The CarManager will be injected$carController = $container->make(CarController:class);
Please note that the result of CarController:class
statement is equivalent to 'MyPlugin\App\Controllers\CarController'
, so you could also write this code and get the same result:
# The CarManager will be injected$carController = $container->make('MyPlugin\App\Controllers\CarController');
Yu can also do the same using the make
shortcut method of the Sci\Sci class. This method can be used both statically and dynamically. here is an example:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Controllers\CarController;# The CarManager will be injected$carController = Sci::make(CarController:class);
Arguments
You can also send arguments using a second optional parameter of the make
method. The second parameter should be an array with the name of the parameters as the array keys and the arguments as the array values. Let's modify our CarController class accordingly:
namespace MyPlugin\App\Controllers;use MyPlugin\Sci\Controller;use MyPlugin\App\Services\CarManager;class CarController extends Controller{# Will contain the CarManager instanceprotected $carManager;# We use type in the CarManager dependencypublic function __construct($brand, CarManager $carManager){# The CarManager instance is already created$this->carManager = $carManager;}# Use the CarManager to get a carpublic function getCar($carId){$car = $this->carManager->get($carId);if ($car) {echo($car->model);}}}
And now imagine that we want to create an instance injecting the required dependencys while sending tha brand as an argument. All you need to do is send the brand in the arguments array:
# The CarManager will be injected$carController = Sci::make(CarController:class, ['brand' => 'seat']);
However, if the parameters follow the correct order, you can skip the paramater names:
# The CarManager will be injected$carController = Sci::make(CarController:class, ['seat']);
For more parameters, just add more elements to the array.
Class Bindings
Imagine that you want to quickly replace a dependence with a different implementation. Ideally, the dependence would be referenced with an interface in the constructor so we can easily swap it for a different one. We can achieve this with bindings, and the best place to do this are the providers.
Closure Binding
You can use the bind
method of the Sci\Container class with a closure where you will return an instance of the requested class. The bind
also has a shortcut method in the Sci\Sci class. You can bind a single implementation:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\Storage;use MyPlugin\App\Services\CarManager;# Binding the CarManager inside a providerSci::bind(CarManager:class, function() {$storage = Sci::make(Storage::class, ['my-storage']);return new CarManager($storage);});# Get an instance$instance Sci::make(CarManager::class);
The previous function will be executed everytime a CarManager instance is crated with the make
method.
You can also use parameters with the function:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\Storage;use MyPlugin\App\Services\CarManager;# Binding the CarManager inside a providerSci::bind(CarManager:class, function($text) {echo($parameter);$storage = Sci::make(Storage::class, ['my-storage']);return new CarManager($storage);});# Get an instance$text = 'This text will be printed every time an instance is created with the make method.';$instance = Sci::make(CarManager::class, [$text]);
Or you can bind different implementations of the CarManager. For example, you can provide a different implementation based on the environment:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\Storage;use MyPlugin\App\Services\CarManager;# Bind the CarManager to a fixed implementationSci::bind(CarManager:class, function() {if (WP_DEBUG) {$storage = Sci::make(Storage::class, ['dev-storage']);} else {$storage = Sci::make(Storage::class ['my-storage']);}return new CarManager($storage);});
The previous results can also be achieved by having two service providers, swapping them based in the environment. We will see the providers in depth in the Providers section.
Instance Binding
You can bind an interface or a class with a specific instance of the class or interface. To do so, you just need create an instance of the class you want to bind and bind the class with the created instance. Here is an example:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;$carManager = new CarManager();# Bind the class with the created instanceSci::bind(CarManager:class, $carManager);
Method Binding
You can also bind a method of any class. Here we are binding a static method:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;# Bind the class to a static methodSci::bind(CarManager:class, 'MyPlugin\App\Services\CarManagerCreator::create');
And again, you can also use parameters with the method as long as the method accepts them:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;# Bind the class to a static method with parametersSci::bind(CarManager:class, 'MyPlugin\App\Services\CarManagerCreator::create');$instance = Sci::make(CarManager::class, ['text' => 'my-storage']);
You can also bind the class to a method of any instance:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Serviceuse MyPlugin\Test\Services\TestServiceCreator;$testServiceCreator = new TestServiceCreator();# Bind the class to a instance method with parametersSci::bind(CarManager:class, [$testServiceCreator, 'createInstance']);$instance = Sci::make(CarManager::class, ['text' => 'my-storage']);
You don't even need to create the class, as the container will try to create an instance of the called class if the used method is not static:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Serviceuse MyPlugin\Test\Services\TestServiceCreator;# Bind the class to a non static method with parametersSci::bind(CarManager:class, [TestServiceCreator::class, 'createInstance']);# The constructor will be called in the background$instance = Sci::make(CarManager::class, ['text' => 'my-storage']);
Singleton Binding
You can bind a singleton if you want to bind a single instance of any class in your plugin. This will allow to have just one instance of a class. Ideally, you should define singletons using the singleton
container method:
Class Singleton
You can force the creation of one instance of a class using the singleton
method with just the class as a paremeter:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;# Once created, always return the same instanceSci::singleton(CarManager:class);# We create an instanceSci::make('\MyPlugin\App\Services\SingletonService', 'text' => 'my-storage');# And if we try to create another instance, the previous instance will be returned. The new parameters are not useful any more.Sci::make('\MyPlugin\App\Services\SingletonService', 'text' => 'my-other-storage');
Closure Binding
Here we bind a method. The binded method will just be executed one time. For subsequent calls, the first created instance will be returned:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\Storage;use MyPlugin\App\Services\CarManager;# Always return the same instanceSci::singleton(CarManager:class, function($text) {$storage = Sci::make(Storage::class, [$text]);return new CarManager($storage);});$carManager = Sci::make(CarManager:class, 'my-other-storage');
Instance Binding
You can also bind an instance to be resolved with the singleton method. To bind an instance:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;# Create an instance of the CarManager$carManager = Sci::make(CarManager::class, ['text' => 'my-storage']);# Bind the instance with the classSci::singleton(CarManager:class, $carManager);# The instance with the new parameters will not be created, as the first created instance will be returned instead$carManager = Sci::make(CarManager:class, ['text' => 'my-other-storage']]);
Method Binding
Using the singleton
method you can also bind any method. Here we are binding a static method:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;# Bind the class to a static methodSci::singleton(CarManager:class, 'MyPlugin\App\Services\CarManagerCreator::create');
And again, you can also use parameters with the method as long as the method accepts them:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Services\CarManager;# Bind the class to a static method with parametersSci::singleton(CarManager:class, 'MyPlugin\App\Services\CarManagerCreator::create');$instance = Sci::make(CarManager::class, ['text' => 'my-storage']);
You can also bind the class to a method of any instance:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Serviceuse MyPlugin\Test\Services\TestServiceCreator;$testServiceCreator = new TestServiceCreator();# Bind the class to a instance method with parametersSci::singleton(CarManager:class, [$testServiceCreator, 'createInstance']);$instance = Sci::make(CarManager::class, ['text' => 'my-storage']);
You don't even need to create the class, as the container will try to create an instance of the called class if the used method is not static:
namespace MyPlugin;use Sci\Sci;use MyPlugin\App\Serviceuse MyPlugin\Test\Services\TestServiceCreator;# Bind the class to a non static method with parametersSci::singleton(CarManager:class, [TestServiceCreator::class, 'createInstance']);# The constructor will be called in the background$instance = Sci::make(CarManager::class, ['text' => 'my-storage']);
Interface Binding
Imagine that you want to inject an interface and easily swap it with different implementations. You can also use the bind
method to bind the interface to a specific implementation. For example, this is useful when we want o return different implementations based on any environment variable. Here is an example:
namespace MyPlugin;use Sci\Sci;use MyPLugin\App\Services\IMotorService;use MyPLugin\App\Services\MyMotorService;# Bind the IMotorService interface to the AcmeMotorService implementation$container->bind(IMotorService::class, MyMotorService::class);
Once the interface is binded, everytime the IMotorService is requested as an dependency, the MyMotorService implementation will be injected.
Instance Actions
The service container allows to add and execute a set of custom actions when a new instance of an object is created. You can add actions using the created
method. Here are the parameters supported by the created
method:
- class <string>: The class to add the action.
- action <string>: This is the function to be executed when an instance is created.
This action will be executed immediately after the container creates an instance of the specified class. Here is an example of how to bind an action to the creation event of a class:
namespace MyPlugin;use Sci\Sci;use MyPLugin\App\Services\MotorService;# Action executed when an instance of the MotorService class is createdSci::created(MotorService::class, function ($object, $sci) { /* ... */ });
This action will be executed when the instance is created. The function used can have a two optional arguments. The first one is the object which was just created, and the second one an instance of the Sci class. You can add more custom parameters after those.
namespace MyPlugin;use Sci\Sci;use MyPLugin\App\Services\MotorService;# You can add more paramters after the default onesSci::created(MotorService::class, function ($object, $sci, $customParameter = 'value') { /* ... */ });