Simplify Filters with Clean OOP Practices
Have you ever struggled to maintain complex filtering logic in your Laravel applications? Procedural filtering, a common approach, can quickly become messy and difficult to manage as your application grows.
While Laravel’s when
method offers a way to improve readability in procedural filtering, for complex scenarios, it can still lead to scattered logic.
This article introduces a solution using Object-Oriented Programming (OOP) principles. By creating reusable filter classes, we can improve the maintainability, readability, and testability of your filter logic.
Refactoring to an OOP approach offers several advantages:
- Promotes modularity — making your code easier to reason about
- Unit testing becomes more straightforward — ensuring the reliability of your filtering system
An Important Consideration
While we delve into the OOP solution, it’s worth noting that in Laravel, the Query Builder is passed by reference.
This means that modifications made to the query within a function can affect its behavior elsewhere in your code. We’ll keep this in mind as we explore how to effectively utilize OOP with the Query Builder for filtering.
To address this challenge, we’ll use the Strategy Pattern. This pattern allows us to define a family of filtering algorithms encapsulated within separate classes.
These classes will implement a common interface, ensuring a consistent approach to applying filters.
The goal of our code is to take what we discussed above and turn it into something like this:
Our goal is to create a consistent and reusable approach to applying filters.
To achieve this, we’ll define a single interface that all our filter classes will implement.
Building the interface
This interface will specify a method called applyTo
that takes a Laravel Query Builder instance as an argument.
Building the Submissions Filter
Now that we have the interface defined, we can create reusable filter classes.
Note: My experience with large projects has shown that filter requirements often involve a variety of heterogeneous filtering criteria. This means filters have unique logic specific to their purpose.
The Strategy Pattern excels in such scenarios for several reasons:
- Encapsulation of Filtering Logic — each filter class encapsulates specific functionality
- Flexibility and Extensibility — new filters can be easily implemented
Our filter classes will typically receive an array containing filter options.
However, it’s common practice to send null
values within this array to indicate that a particular filter isn't being applied.
To ensure clean and efficient filtering, we'll implement logic to filter out any empty values (including null
) before processing the remaining valid filters.
This keeps our filter classes focused on handling the actual filtering logic without needing to account for the absence of filters.
The filters we will work with are the reportId and projectId. These filters will be passed as an array containing their respective values or null
if not applied.
We'll transform this array into a Laravel collection for easier manipulation within our filter classes.
Once we have a collection of valid filters (excluding empty values), we can iterate through them to extract the filter name and value.
Now that we have this, we can employ the Variable Functions that PHP has to avoid overengineering this solution by adding a factory pattern to match the filters with the method that should apply them.
By using the Strategy Pattern and a common interface, we’ve established a flexible and maintainable foundation for filter implementation in our Laravel application.
This approach allows to easily add new filter types without modifying existing code, promoting long-term code health.
Sample Usage
Now that we have reusable filter classes, using them in our application becomes straightforward.
Here’s a basic demonstration:
Endings Notes
This article has explored the benefits of refactoring procedural filter logic to an object-oriented approach using the Strategy Pattern and interfaces.
We’ve seen how this approach promotes code readability, maintainability, and extensibility. By defining a common interface for filters and creating dedicated filter classes, you can effectively manage complex filtering requirements in your Laravel applications.