Custom filtering on Eloquent model - php

My application supports fetching data with filters. My current implementation (which works fine) is
Model::select($fields)->with($relations)->tap(function ($query) use ($filters) {
// A lot of filtering logic here
// $query->where()......
})->get();
However, I would like to move the filtering logic directly into the Model so I could just do
Model::select($fields)
->with($relations)
->applyFilters($filters)
->get();
I have tried to add a filter method to the Model but at that point I'm working with a Builder and it does not recognize my function:
Call to undefined method Illuminate\Database\Eloquent\Builder::applyFilters()
Is there an easier way to do this, other than creating a new builder class and use that?

I figured it out! I just had to add a scopeApplyFilters to my Model class. It injects the Builder as the first parameter automatically, so the logic ends up looking like
public function scopeApplyFilters($query, $filters)
{
// Perform filtering logic with $query->where(...);
return $query;
}
Then I can just call it with Model::applyFilters($filters);

Related

How to pass a part of Laravel Eloquent query to method

I want to use the different queries inside one method and I want to pass a part of query inside that.
My method looks like this:
static function methodName($partOfQuery)
{
ModelName::where('...')->$partOfQuery->...;
}
And I want to do something like:
$partOfQuery = where('columnName', '>=', 5)->whereRaw('Other Condition');
self::methodName($partOfQuery);
But I was faced with this error:
Call to undefined function App\Classes\ClassName\where()
Anyone could help me with this issue? Thanks
I think that the error is because where is called without an eloquent model class.
Something you can do with query builder is to call a function inside your where condition like:
SomeModel::where(function($query){
// do something
$query->where(...)
})
I doubt there is nothing exactly like what you described. But there are alternatives, if I understand correctly, you are trying to chain multiple methods.
If so, you can do the following:
In your model class:
static function getActiveBooks()
{
return self::where('status', 'active');
}
public function getFeaturedBooks() {
return $this->getActiveBooks()->where('featured', 'active');
}
Usage:
$activeBooks = (new Book())->getFeaturedBooks()->get();
There are multiple ways, you can also use scope as #levi described in the comment section , they are 2 sides of the same coin.

Laravel difference between Model, Builder and Collection

I came across this code snippet on Laravel doc
// Retrieve a model by its primary key...
$flight = App\Flight::find(1);
// Retrieve the first model matching the query constraints...
$flight = App\Flight::where('active', 1)->first();
where and find are builder functions, why App\Flight as a Model can call these function. And what are the differences between Model, Builder and Collection in Laravel?
You're able to call Builder functions on an Eloquent model, because the Model class uses the magic __call method.
As you can see in the method definition below, if the method doesn't exist on the class, or it's not increment or decrement, a new Builder query is created, on which the method is called.
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
try {
return $this->newQuery()->$method(...$parameters);
} catch (BadMethodCallException $e) {
throw new BadMethodCallException(
sprintf('Call to undefined method %s::%s()', get_class($this), $method)
);
}
}
https://github.com/illuminate/database/blob/master/Eloquent/Model.php#L1439
As for the difference between a Model, Builder and Collection:
Model: This follows the pattern where a model is essentially an instance of a database row, allowing you to create, update and delete a single row.
Builder: This is a layer of abstraction between your application and the database. Typically it's used to provide a common API for you to build platform agnostic database queries. E.g. they'll work on MySQL, Postgres, SQL Server, etc.
Collection: This is basically an array on steroids. It provides a chainable API for standard array_* type PHP functions, together with other useful functions for manipulating a collection of data.

Laravel best practice for get data from DB

I'm confused about the best way to get data from DB. I have this controller (Task) that get from Task model the tasks of each customers. What's the best way to get these data?
1° Example
In this example I have a "general" function (getTasksCompany) that join the tables (Task and Companies). The showTasks call this function and then use where clause for get only tasks with customer code = 000001
public function showTasks() {
$tasks = $this->getTasksCompany()->where("company_code", "=", "000001")->get();
dd($tasks);
}
public function getTasksCompany() {
$tasks = Task::join("companies AS c", "c.code", "=", "company_code");
return $tasks;
}
2° Example
In this example I have a "specific" function that get tasks from the code in the passed as argument.
public function showTasks2() {
$tasks = $this->getTasksFromCompany("000001");
dd($tasks);
}
public function getTasksFromCompany($company_code) {
$tasks = Task::join("companies AS c", "c.code", "=", "company_code")->where("company_code", "=", $company_code)->get();
return $tasks;
}
3° Example
In this example I have a "general" function (getTasksCompany) that join the tables (Task and Companies) and I use the scope defined from Task model to filter the tasks.
public function showTasks3() {
$tasks = $this->getTasksCompany()->company("000001")->get();
dd($tasks);
}
public function getTasksCompany() {
$tasks = Task::join("companies AS c", "c.code", "=", "company_code");
return $tasks;
}
public function scopeCompany($query, $company_code) {
return $query->where("company_code", "=", $company_code);
}
My question is, what's the good practice to do? And Why?
Based on my understanding, asking for best practices would attract opinionated answer but generally because you use Laravel, I would try as much as possible to make use of the functionalities it provide.
While I would prefer the third example more than the others because using model scope helps to create and bind query builder from an instance of the model. This would make things easier when you reuse this function.
This means you don't need to statically call any query builder method since they bind to the initial model in the first place.
An example if I would do the above I would simply employ Model Relationship that would handle my joins under the hood:
//Company model
public function scopeShowTask($company_code = "000001")
{
return $this->tasks()->where("company_code", "=", $company_code);
}
public function tasks()
{
return $this->hasMany(Task::class, 'company_code', 'code');
}
Using this method helps to construct your query based on the relationship between Task and Company. In order to understand how this works, you should check out Eloquent Relationship
One great advantage of using this method is that you can easily take advantage of the various method laravel provides when you declare a relationship in your model this way. To see some of them, you can check out Querying relationship
PS: Best practice, no, maybe just a better practice given the situation. This answer is open to an update
I suggest that you study eloquent and query builder thoroughly and that's where the best practice is.
If you use eloquent with query builder properly you won't need another function in order fetch the data that you want.
https://laravel.com/docs/5.5/queries
https://laravel.com/docs/5.5/eloquent

Alter the query builder from a specific model everytime - Laravel 5.3

How do I alter the query builder everytime I'm getting a new model?
I've found that overriding the method below works, but I'm not feeling good doing this. Is there a better way to do this?
So I want ->join() to be executed everytime for a specific model.
!! I don't want to use the protected $with = []; property, cause I don't want extra queries to be executed when not necessary.
public function newQueryWithoutScopes()
{
$builder = $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
);
// Once we have the query builders, we will set the model instances so the
// builder can easily access any information it may need from the model
// while it is constructing and executing various queries against it.
return $builder->setModel($this)->join('join statement comes here')->with($this->with);
}
To affect the query every time a model is used, you can use the newQuery method.
public function newQuery($excludeDeleted = true){
return parent::newQuery()->join('orders', 'users.id', '=', 'orders.user_id');
}
But this will probably break Eloquent since your Model isn't representative of your database model anymore; it is now a Frankenmodel of two separate models.

Using a query scope in a collection laravel

My Association model looks like this (irrelevant code redacted):
class Association extends Model
{
public function members() {
return $this->hasMany('App\Member');
}
}
My Member model looks like this:
class Member extends Model
{
public function scopeActive($query) {
return $query->where('membership_ended_at', Null);
}
public function scopeInactive($query) {
return $query->whereNotNull('membership_ended_at');
}
}
This is what I want to be able to do:
$association = Association::find(49);
$association->members->active()->count();
Now, I'm aware there's a difference between a Query and a Collection. But what I'm basically asking is if there's some kind of similar scope for collections. Of course, the optimal solution would be to not have to write TWO active methods, but use one for both purposes.
(question already answered in the comments, but might as well write a proper answer)
It is not possible to use a query scope in a Colletion, since query scope is a concept used in Eloquent to add constraints to a database query while Collections are just a collection of things (data, objects, etc).
In your case, what you need to do is to change this line:
$association->members->active()->count();
to:
$association->members()->active()->count();
This works because when we call members as a method, we are getting a QueryBuilder instance, and with that we can start chaining scopes to the query before calling the count method.

Categories