Simplifying Service Selection in Laravel Using Resolvers
Source: Dev.to
The Problem: Too Many if‑else Conditions
if ($paymentType === 'stripe') {
$service = new StripePaymentService();
} elseif ($paymentType === 'paypal') {
$service = new PaypalPaymentService();
} else {
throw new Exception('Invalid payment type');
}
$service->charge($data);
What’s wrong with this approach?
- Business logic is tightly coupled to concrete implementations.
- Adding a new payment gateway requires modifying existing code.
- Hard to test and maintain.
- Violates the Open/Closed Principle.
The code works, but it doesn’t scale well.
What Is a Resolver?
A Resolver is a class responsible for deciding which service implementation should be used based on runtime data. In simple terms, a resolver returns the correct service class for you, keeping your business logic clean.
Real‑World Example: Payment Service Resolver
Step 1: Create a Common Interface
$this->stripePaymentService,
'paypal' => $this->paypalPaymentService,
default => throw new InvalidArgumentException('Unsupported payment type'),
};
}
}
Why Use Constructor Injection?
- Makes dependencies explicit and easier to understand.
- Improves unit testability (services can be mocked).
- Keeps the resolver focused on selection logic.
- Leverages Laravel’s dependency injection container.
Note: You could also resolve services using
app()inside the resolver, but constructor injection is generally preferred for cleaner architecture and better testing.
Step 4: Use the Resolver in Your Business Logic
get('payment_type');
$service = $resolver->resolve($paymentType);
$service->charge($request->all());
return response()->json([
'message' => 'Payment processed successfully',
]);
}
}
✨ Clean, readable, and easy to extend.
Adding a New Service Is Easy
Want to add Razorpay later?
- Create
RazorpayPaymentServicethat implementsPaymentServiceInterface. - Update the resolver:
'razorpay' => $this->razorpayPaymentService,
No changes are needed in controllers or other business logic.
Benefits of Using Resolvers
- Cleaner and more readable code.
- Easy to add new services.
- Follows SOLID principles.
- Centralized service selection logic.
- Simple to unit test.
Bonus: Making the Resolver More Flexible
Config‑Based Mapping
// config/payment.php
return [
'stripe' => StripePaymentService::class,
'paypal' => PaypalPaymentService::class,
];
Resolver Using the Config
<?php
class PaymentServiceResolver
{
public function resolve(string $type): PaymentServiceInterface
{
$service = config("payment.$type");
if (! $service) {
throw new InvalidArgumentException('Unsupported payment type');
}
return app($service);
}
}
This approach makes the system even more configurable.
Final Thoughts
Resolvers are a simple yet powerful pattern for managing multiple service implementations in Laravel. They help you:
- Avoid messy conditionals.
- Keep business logic clean.
- Build systems that scale gracefully.
If you work with multiple APIs, payment gateways, or providers, resolvers can significantly improve your code quality.