Skip to content

Instantly share code, notes, and snippets.

@AbiruzzamanMolla
Created November 21, 2024 12:11
Show Gist options
  • Save AbiruzzamanMolla/d4869c64e3e8293699d92ab185cc7b5d to your computer and use it in GitHub Desktop.
Save AbiruzzamanMolla/d4869c64e3e8293699d92ab185cc7b5d to your computer and use it in GitHub Desktop.
Resolver way the get extended class valuels

I see, your scenario involves multiple layers of extensions to the PaymentMethodService class (e.g., first by PaymentGatewayService, then by BkashGatewayService), and you want to call PaymentMethodService but ensure that all of its extensions (including those applied by various service providers) are taken into account.

In Laravel, when you bind a class to the service container, it will always resolve the last binding unless you explicitly manage the resolution. This can cause issues when you have multiple extensions happening in different service providers, as each extension might overwrite the previous one in terms of the container's resolution behavior.

Solution: Dynamic Class Resolution Using Multiple Extensions

To handle multiple extensions without facing issues where a facade or the service container always resolves the final binding, here's a more dynamic approach:

  1. Use Multiple Bindings: You can make the service container aware of all the extended classes (like PaymentGatewayService, BkashGatewayService, etc.) by registering multiple bindings that all resolve to the correct service instance dynamically.

  2. Use a Custom Resolver: Instead of directly calling the facade or resolving a class with app(), you can create a custom resolver that can take into account the entire extension chain, ensuring that the latest extended version of the service is always used.

Step-by-Step Solution:


1. Use a Custom Resolver for PaymentMethodService

You need a custom resolver that will return the most extended class of PaymentMethodService, taking into account all extensions like PaymentGatewayService, BkashGatewayService, etc.

First, create a PaymentMethodServiceResolver class that will return the resolved service with all extensions.

Example: PaymentMethodServiceResolver

namespace App\Services;

use Illuminate\Container\Container;
use Modules\BasicPayment\app\Services\PaymentMethodService;

class PaymentMethodServiceResolver
{
    /**
     * Resolve the fully extended PaymentMethodService class.
     *
     * @param \Illuminate\Container\Container $container
     * @return \Modules\BasicPayment\app\Services\PaymentMethodService
     */
    public function resolve(Container $container): PaymentMethodService
    {
        // Resolve the base PaymentMethodService and allow for extensions
        $service = $container->make(PaymentMethodService::class);

        // Dynamically resolve the latest extension of PaymentMethodService
        // Loop through each extension provider and apply the changes
        foreach ($container->getBindings() as $abstract => $concrete) {
            // Only apply extensions that are extending PaymentMethodService
            if (is_subclass_of($concrete, PaymentMethodService::class)) {
                $service = $container->make($concrete);
            }
        }

        return $service;
    }
}

2. Register the Resolver in the Service Container

Now, register the PaymentMethodServiceResolver in your service provider, so it can be used to dynamically resolve the correct instance of PaymentMethodService.

Example: In your AppServiceProvider or another provider:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\PaymentMethodServiceResolver;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Register PaymentMethodServiceResolver to the container
        $this->app->singleton(PaymentMethodServiceResolver::class, function ($app) {
            return new PaymentMethodServiceResolver();
        });
    }

    public function boot()
    {
        // Resolve the PaymentMethodService dynamically
        $serviceResolver = $this->app->make(PaymentMethodServiceResolver::class);
        $paymentService = $serviceResolver->resolve($this->app);

        // Now you have the fully extended PaymentMethodService
        // You can make it available globally if needed
        $this->app->instance(PaymentMethodService::class, $paymentService);
    }
}

3. Using the Resolver in Your Controllers or Views

Now that you have a custom resolver for the PaymentMethodService, you can use it in your controllers or views, ensuring that all extensions are applied correctly.

Example: In a Controller

namespace App\Http\Controllers;

use App\Services\PaymentMethodServiceResolver;
use Modules\BasicPayment\app\Services\PaymentMethodService;

class PaymentController extends Controller
{
    public function index(PaymentMethodServiceResolver $resolver)
    {
        // Resolve the fully extended PaymentMethodService
        $paymentService = $resolver->resolve($this->app);

        // Now you can use the extended payment service
        $supportedPayments = $paymentService->getSupportedPayments();

        return view('payments.index', compact('supportedPayments'));
    }
}

Example: In a Blade View

@php
    // Resolve the PaymentMethodService with all extensions
    $service = app(App\Services\PaymentMethodServiceResolver::class)->resolve(app());

    // Use the extended functionality
    $supportedPayments = $service->getSupportedPayments();
@endphp

<ul>
    @foreach ($supportedPayments as $payment)
        <li>{{ $payment }}</li>
    @endforeach
</ul>

Explanation of the Solution:

  1. Custom Resolver (PaymentMethodServiceResolver):

    • It resolves the most extended version of PaymentMethodService by looping through all bindings in the service container and ensuring that the latest class extending PaymentMethodService is used.
  2. Service Container Registration:

    • In the AppServiceProvider (or another appropriate provider), the PaymentMethodServiceResolver is registered and then used to dynamically resolve the fully extended PaymentMethodService class. This ensures that when you call PaymentMethodService from the container, all extensions are accounted for.
  3. Dynamic Resolution:

    • The custom resolver will dynamically find and return the correct instance of PaymentMethodService with all extensions applied, so you can call it as if it's a single service, without worrying about which class is extending it at any point.

Advantages of this Approach:

  • No Overwrites: Each extension is applied in sequence, ensuring that all classes that extend PaymentMethodService are included.
  • Facilitates Multiple Extensions: You can have multiple service providers extending PaymentMethodService and this solution will dynamically pick up the extended functionality.
  • Cleaner Code: By abstracting the resolution logic into a resolver, your controllers or views remain clean, focusing only on the PaymentMethodService functionality without worrying about the chain of extensions.

By using this approach, you should now have a more dynamic and flexible way to manage multiple class extensions without facing issues related to facade resolution or overwriting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment