Blog Detail

05

Oct
The most Enhanced & Latest Container Package for Laravel  cover image

arrow_back The most Enhanced & Latest Container Package for Laravel

Michael 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.

Requirements

The package requires

  • PHP 8.0

  • Laravel 8.x

Support for future versions of PHP & Laravel will be provided.

Installation

You can install this package via composer. You’ve to run this command for the installation.

composer require michael-rubel/laravel-enhanced-container

Basic binding with new syntax

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

Contextual binding with new syntax

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

Contextual method binding with input parameter interception

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

Author : Rizwan Aslam
AUTHOR
Rizwan Aslam

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 your project

Launch project