05
OctMichael Rubel introduced the most advanced Laravel Container Package with improved Laravel service container features. This package provides syntax sugar for the Laravel container calls and bindings, automatic resolution of bound implementation, method forwarding, and an enhanced version of the Laravel method binding feature.
The package requires
PHP 8.0
Laravel 8.x
Support for future versions of PHP & Laravel will be provided.
You can install this package via composer. You’ve to run this command for the installation.
composer require michael-rubel/laravel-enhanced-container
After the installation, You can do the basic binding with this package, here is the example:
bind(ServiceInterface::class)->to(Service::class);
bind(Service::class)->itself();
As singleton:
bind(ServiceInterface::class)->singleton(Service::class);
singleton(Service::class);
As a scoped instance:
bind(ServiceInterface::class)->scoped(Service::class);
scoped(Service::class);
bind(ServiceInterface::class)
->contextual(Service::class)
->for(ClassWithTypeHintedInterface::class);
As variadic dependency:
bind(ServiceInterface::class)
->contextual(
fn ($app) => [
$app->make(Service::class, ['param' => true]),
$app->make(AnotherServiceSharingTheSameInterface::class),
]
)
->for(ClassWithTypeHintedInterface::class);
As primitive:
bind('$param')
->contextual(true)
->for(ClassWithTypeHintedPrimitive::class);
Assuming that is your function in the service class:
class Service
{
public function yourMethod(int $count): int
{
return $count;
}
}
Perform the call to your service through container:
call(Service::class)->yourMethod(100)
You can pass it as $this or basic class object too:
call(new Service())->yourMethod(100)
Then override the method behavior in any place of your app:
bind(Service::class)->method()->yourMethod(function ($service, $app, $params) {
return $service->yourMethod($params['count']) + 1;
});
Alternative syntax:
bind(Service::class)->method('yourMethod', function ($service, $app, $params) {
return $service->yourMethod($params['count']) + 1;
});
The result next call: 101
You can easily mock the methods in your tests as well, and it counts as code coverage.
For example:
bind(ServiceInterface::class)->to(Service::class);
bind(ServiceInterface::class)->method(
'externalApiRequestReturnsFalse',
fn () => false
);
$service = call(ServiceInterface::class);
$call = $service->externalApiRequestReturnsFalse();
$this->assertFalse($call);
You should remember that you need to utilize call()
to method binding to work. It returns the instance of CallProxy
. If you depend on interfaces, the proxy will automatically resolve bound implementation for you, no need to do it manually.
Optionally, if you want to easily wrap all your class constructor’s dependencies to CallProxy
, you can use BootsCallProxies trait and then call $this->bootCallProxies()
in your constructor. It will bootstrap the proxy
class property that utilizes Laravel’s native Fluent
object. What it would look like:
use MichaelRubel\EnhancedContainer\Traits\BootsCallProxies;
class AnyYourClass
{
use BootsCallProxies;
/**
* @param ServiceInterface $service
*/
public function __construct(
private ServiceInterface $service
) {
$this->bootCallProxies();
}
/**
* @return object
*/
public function getProxiedClass(): object
{
return $this->proxy->service; // your proxied service
}
/**
* @return object
*/
public function getOriginal(): object
{
return $this->service; // your original is still available
}
}
Method forwarding
Method forwarding feature automatically forwards the method when it doesn’t exist in your base class to another class if the namespace/classname
structure is met.
Usual use case:
If you have some kind of Service or Domain, which includes business or application logic, then some kind of Repository
or Builder
, which holds your database queries, but you don’t want your controllers
(or View/Livewire
components) to be dependent on the repositories directly, and don’t want to write the “proxy”
methods in the Service that references the Repository
when it comes to just fetch the data without any additional operations.
To enable this feature, publish the config:
php artisan vendor:publish --tag="enhanced-container-config"
Then turn the forwarding_enabled option on and set the class names that met your application structure.
Assuming your structure is:
Logic:
- App/Services/Users/UserService
Queries:
- App/Repositories/Users/UserRepository
Then your classes:
class UserService
{
public function someMethod(): bool
{
return true;
}
}
class UserRepository
{
public function yourUser(): bool
{
return true;
}
}
Then call the method to fetch the user:
call(UserService::class)->yourUser()
The result will be true
even though the method is missing in UserService
. If you put the same method in the UserService
, it will fetch the result from the service itself.
Note: if you use PHPStan/Larastan
you’ll need to add the @method
docblock to the service to make it static-analyzable, otherwise it will return an error that the method doesn’t exist in the class.
If you want to know more about this package you can view its documentation on Github.
Published at : 05-10-2021
I am a highly results-driven professional with 12+ years of collective experience in the grounds of web application development especially in laravel, native android application development in java, and desktop application development in the dot net framework. Now managing a team of expert developers at Codebrisk.
Launch project