Model Search and Filtering in Laravel

Hi guys, as I have written this functionality a few times at this point, and I have been asked how to do it a couple of times, too. That is Laravel searching and filtering, I am going to develop a simple composer package which I can use to allow users to search and filter Laravel (Eloquent) Models using PHP traits which can be used on Laravel controllers.

Assumptions

I am going to work on the assumption that you are comfortable with basics.

I shall also assume you’re fairly comfortable with the Illuminate HTTP Request object, a Controller, and Eloquent Model.

Concept

So the first thing we want to tackle, as a concept, is that the best way, in my experience, is to start with a query, and then modify it as required. This allows us to optionally do almost anything, but equally allows us to do nothing, and simply return an unfiltered set if we want to.

This point would be true outside of Eloquent too, even if working with vanilla SQL.

Starting Off

To start with you want something like this within your Controller.

public function index(Request $request)
{
// Of course you can change this if you don't want to select everything
$query = YourModel::select('*');
}

Now we have the $query object which we can work with as and where we want to.

Filtering Records

Excluding deleted/is deleted, as we’ll cover that later on.

Let’s say you now want to filter results on author_id just to keep things simple.

if(!empty($request->author)){
$query->where('author_id', '=', $request->author);
}

Of course take into account everything here, you may need to do sanitisation or validation or anything else in there.

Multiple Filters, Save Lines

If you have many fields on which you wish to do this I usually find something like this is better

$fields = ['author_id', 'category_id'];
foreach($fields as $field){
if(!empty($request->$field)){
$query->where($field, '=', $request->$field);
}
}

That example assumes the exposure of your field names, you could always use an associative array so you could do “author” maps to “author_id” and so on, if you were so inclined.

Fluffy Search

Now you might want to do a fluffy search, taking the assumption of posts with content and an author name, and a category name (stored within the model) you could do something like this

if(!empty($request->search)){
$searchFields = ['title','content','author_name','category_name'];
$query->where(function($query) use($request, $searchFields){
$searchWildcard = '%' . $request->search . '%';
foreach($searchFields as $field){
$query->orWhere($field, 'LIKE', $searchWildcard);
}
})
}

Handling Trash

You probably want something which allows you to see soft deleted models, if you’re using them. I do something like this

if($request->trash == true){
// Retrieve soft deleted models only, you can only do this if you're using soft deletion on this model
$query->onlyTrashed();
}

Ordering and Limiting

Now to handle ordering and pagination – which Eloquent beautifully does for us, all we need to do is let the user modify it if they want to.

// Set the query ordering
$query->order($request->orderBy ?? 'updated_at', $request->order ?? 'DESC');

$perPage = $request->per_page ?? 25;

// It is a good idea to make sure we cap this
if($perPage > $maximumPerPage){
$perPage = $maximumPerPage;
}

// Final part - get the results
$results = $query->paginate($perPage);

// You will now need to do something with your results, and return some kind of Response - this could be a JSON response or adding the results to the data returned to the view, depending on your context

There’s an assumption here that our default order is updated at, last updated first, and that if not specified, we want to return 25 pages.

Of course you can store this information wherever you want.

In Summary, and Further Reading

Rather than creating a really complicated set of rules to handle all the things you may need from your API calls, start with a query, and then optionally modify that query.

Further Reading

Beware, the Anti Pattern!

In this article I am going to cover the application of patterns within your… application. In short, I am going to show you how to use design patterns in a logical manner.

Patterns are always sometimes awesome!

The first thing here is that all design patterns have a purpose, every design pattern has its place. Whilst I’m talking about design patterns the same can be said of development methodologies, database designs, and really any other kind of concept.

The proper application of design patterns can take a frustrating piece of software, and make it easy to maintain, or hyper-secure, or crazy performant.

The bad application of a design pattern will do the exact opposite.

But, why would you ever not use a pattern?

Well, when it’s not the right time to use it. Every pattern has its place, but not every pattern should be used everywhere, it’s a bad idea.

Don’t take my word for it, let’s look at some practical examples and experiences that really demonstrate what I am saying here.

I’m going to be deliberately controversial here, and I’m going to pick stuff that all developers seem to love and then prove where it will ruin your application.

I’ve heard of Patterns, what is an Anti Pattern?

An anti-pattern is a pattern. Subjectively an anti-pattern is when you take a pattern and either apply it in the wrong circumstances, or implement it badly.

The consequence of the application of an, otherwise great, pattern, causes adverse impacts (usually for maintainability, or for performance etc). Now, it is an anti-pattern.

Model View Controller (MVC)

Well all love MVC, right? I mean, what’s not to love? CodeIgniter, CakePHP, Laravel, Angular, and Joomla all follow this pattern. It is arguably one of the most used design patterns of recent times.

For good reason, it’s awesome! Because it’s awesome, developers are pretty darn impassioned about using it everywhere, and they are right, in the vast majority of cases.

So when, I hear you ask, would this not be a good idea? When could it be an anti-pattern?

What about if I am writing a daemon, which is going to continuously monitor the usage of the mounted hard drives on a server and, in certain scenarios, send an email to a system administrator?

Don’t need models – we’re not handling any data. We don’t have anything, not even a CLI output, so no need for views. Realistically there’s not a controller it’s a standalone script. MVC would be a bit overkill, in this instance, wouldn’t it?

That’s a bit of a drastic example though, let’s look at something more subtle.

Single Responsibility Principle

Ah, right. Let’s get controversial then, shall we?

A class should only have one reason to change

Single Responsibility Principle, SOLID Principles

Everyone loves this, and likes to really preach about it. It is crazy controversial, widely adopted, and I personally think that it’s a good idea.

The Single Responsibility Principle is like the best song in the world, that you hear 1,000 times per day on every music channel and radio station. It is best practice, yes. But sometimes, it’s okay to break it!

Oh Gosh, quick, get the smelling salts and a wet flannel, they’ve feinted!

The whole point of the SOLID principles are to make well structured, easy to maintain code.

So, time for a real-life example. I have a class, it is an Eloquent Model. It is responsible for some mission-critical, core functionality. This class has a method within it. This method is 150 lines long (probably 50 lines of code, once you take out empty lines and code comments).

This method, in of itself, could (and maybe even should) be abstracted into it’s own class. For some context it is a static method, responsible for fetching records, based on a set of arguments.

Every part of the Single Responsibility Principle dictates this method should be abstracted to its own class, perhaps even a set of classes.

Internally, I have had this code reviewed, and to quote the developers who have checked (and indeed worked on) it, it is “exceptionally easy to follow” and “super easy to add and change the functionality”.

It is, essentially, a set of if statements. Based on the outcome of those if statements, the Eloquent Query is modified, then returning either limited, or paginated, results.

So, I hear you scream, “why won’t you abstract it?!” – well I could. But following conversations with the development team, the code would actually be harder to follow if I were to abstract and refactor it.

In this instance, the code can be easily found, easily changed, and is incredibly stable.

Following Single Responsibility to the letter, this time, would be an anti-pattern. Rather than making life easier, it would make life more difficult. Abstraction can be bad! If it has no performance, functional, or security perks. It makes the code more difficult to maintain and follow; then abstracting it would not make any sense. Except to follow a pattern for the sake of following a pattern, at this point, it becomes an anti-pattern.

Dependency Injection

Oh God. I’ve been here before. It is bad. I remember the flames, vividly. As the Reddit Hellfire engulfed my computer. Just kidding, but this one does really evoke emotional reactions.

Just quickly and very simply, dependency injection is parsing an object (dependency) into another object on which the latter depends. Thus, injecting the dependency. It usually looks (forgetting containers and autowiring) something like this.

class Calculator{

public function __construct(
AdditionServiceProvider $addition,
SubtractionServiceProvider $subtraction
){
$this->additionService = $addition;
$this->subtractionService = $subtraction;
}
}

The point here is that I can control the services that the Calculator is using. So if I were, at a later date, wanting to swap out my AdditionServiceProvider for AcmeIncAdditionServiceProvider (assuming they implement the same interface, or extend the same base class) then I could, and the rest of the class would work as expected.

However, I have, several times, seen things like this.

class PaymentsIncorporatedWrapper{

public function __construct(
PaymentsInc\Payer $payer,
PaymentsInc\Refunder $refunder
){
$this->payer = $payer;
$this->refunder = $refunder;
}

}

Right, this makes sense, doesn’t it? Wire up the payer and the refunder, then drop them into your Wrapper. As I said above, take the dependency injection container side of it; whenever I want to do something with the PaymentsIncorporatedWrapper I have to do something like

$payer = new PaymentsInc\Payer($apiKey, $somethingElse);
$refunder = new PaymentsInc\Refunder($apiKey, $somethingElse);
$wrapper = new PaymentsIncorporatedWrapper($payer, $refunder);

That’s a kind of annoying amount of code to write, to instantiate a class. “But the container does that for you!” I hear you scream. Yes, yes it will.

But why? You don’t know, at the time of instantiation, that I need the refunder. I might just be querying a payment. Why do I need the refunder? I don’t. Ah, maybe this is an anti-pattern.

Also, this set of classes is specific to the PaymentsInc integration. So it’s not like I’m going to swap in another payment provider (otherwise this would all make sense).

In this instance, when I couldn’t possibly want to swap anything else in/out, perhaps this would make more sense?

class PaymentsIncFactory{

public static function getPayer() : PaymentsInc\Payer
{
$factory = new static();
return $factory->getPayer();
}

private function getApiKey() : string
{ ... }

private function getSomethingElse() : string
{ ... }

public function getPayer() : PaymentsInc\Payer
{ ... }

...

}

class PaymentsIncoporatedWrapper{

public function takePayment(float $amount)
{
$payer = PaymentsIncFactory::getPayer();
$payer->takePayment($amount);
}

}

Anybody who is passionate about Dependency Injection will argue this is wrong, and they will probably cite Unit Testing as the reason. But, to my knowledge, unit testing isn’t justification for using Dependency Injection.

In fact, I have implemented both Unit Testing, and Test Driven Development, without unnecessarily using Dependency Injection. Of course, Dependency Injection was used, just only where it was truly necessary.

And the point of those tales was….

Just because a pattern is the best thing since sliced bread, doesn’t mean you should apply it liberally, everywhere, without thinking about it.

In the above I’ve taken three of the most beloved patterns we possess, and given you three good places where perhaps those pattern were best not applied.

So think about the patterns you’re using, never blindly use it because someone on [insert social website or influencer here] said is is amazing.

The key, as with all things, is to genuinely understand the pattern, its application, its benefits, and its constraints. And then think, and make a decision, about whether it makes sense to apply it in your use case.

Further Reading / Sources

  1. Model View Controller (MVC) – Wikipedia
  2. Single Responsibility Principle – Wikipedia
  3. Dependency Injection – Wikipedia