Strategy Pattern for Business Rules in Laravel
Stop writing massive if-else chains for business logic. Learn how to implement the Strategy pattern in Laravel to keep your services clean and extensible.
Previously in this course, we explored using Value Objects to encapsulate primitive data behavior. While Value Objects handle data integrity, business rules often vary based on context or user input. When you find yourself writing complex switch or if/else statements inside your services, you've hit a wall where the Strategy pattern can help.
The Strategy pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This lets the algorithm vary independently from the clients that use it.
The Problem: When Logic Becomes a Liability
In our project board application, let's say we need to calculate the "priority score" for a task. Initially, it's a simple calculation. But as we grow, we need different strategies: one for "Urgent" projects, one for "Standard" projects, and perhaps one for "Maintenance" tasks.
If you put this logic in a TaskService, you'll end up with a maintenance nightmare:
PHPpublic function calculateScore(Task $task) { if ($task->type === 'urgent') { return $task->points * 2; } elseif ($task->type === 'maintenance') { return $task->points * 0.5; } return $task->points; }
As you add more project types, this method grows indefinitely. This violates the Open/Closed Principle—you should be able to add new scoring rules without modifying existing service code.
Defining the Strategy Interface
To fix this, we first define an interface that every scoring strategy must implement. This ensures our service doesn't care how the score is calculated, only that it can be calculated.
PHPnamespace App\Contracts; use App\Models\Task; interface TaskScoringStrategy { public function calculate(Task $task): int; }
Implementing Concrete Strategies
Now, create concrete classes that implement this interface. These are the specific "strategies" for our business rules.
PHPnamespace App\Strategies\Scoring; use App\Contracts\TaskScoringStrategy; use App\Models\Task; class UrgentScoring implements TaskScoringStrategy { public function calculate(Task $task): int { return $task->points * 2; } } class MaintenanceScoring implements TaskScoringStrategy { public function calculate(Task $task): int { return (int) ($task->points * 0.5); } }
Injecting Strategies into Services
Instead of hardcoding logic, our TaskService now depends on the interface. We can inject the specific strategy we need at runtime.
PHPnamespace App\Services; use App\Contracts\TaskScoringStrategy; use App\Models\Task; class TaskService { public function calculateTaskScore(Task $task, TaskScoringStrategy $strategy): int { return $strategy->calculate($task); } }
By leveraging the Service Layer and proper dependency injection, our service remains thin and focused. We aren't concerned with the implementation details, only the contract.
Hands-on Exercise
- Create a
DiscountStrategyinterface for our project board's subscription module. - Implement two strategies:
StandardDiscount(10% off) andEnterpriseDiscount(25% off). - Create a
BillingServicethat accepts aDiscountStrategyin itscalculateTotalmethod and verify it works with a simple test.
Common Pitfalls
- Over-Engineering: Don't apply the Strategy pattern if you only have one or two simple rules. It adds file overhead. Use it when the number of rules is high or the logic is volatile.
- Missing the Context: Sometimes, a strategy needs extra data. Ensure your interface methods are flexible enough to accept the necessary context (like the
Taskmodel) without making the interface too rigid. - Tight Coupling: Ensure your concrete strategies don't become dependent on other services. If a strategy needs database access, inject the Repository into the strategy's constructor.
Recap
The Strategy pattern is your best friend when refactoring complex, conditional business logic. By defining a clear interface, you decouple your service layer from the specific implementation of your rules. This makes your code modular, testable, and significantly easier to extend as your application's requirements evolve.
Up next: We will explore how to manage complex multi-step workflows using Job Batching to keep our background processing resilient.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.