Laravel - is there a way to combine whereHas and with - php

I'm currently facing a small problem. I want to return a model only if a relation with certain conditions exists. That's working fine with the whereHas()-method.
$m = Model
::whereHas(
'programs',
function($q) {
$q->active();
}
);
However, calling the relation as a property like this will give me all (not filtered results).
$m->programs;
So basically what I'm doing now is this:
$m = Model
::whereHas(
'programs',
function($q) {
$q->active();
}
)
->with(array('programs' => function($q) {
$q->active();
}))
;
That's working fine but I feel very bad about doing the same thing again. That can't be the right way. How can I achieve this without kind of duplicating the code?

If a concept of an "active program" is important in your application, consider creating a separate relation just for active programs (in this case I'm presuming you have a HasMany relation):
class Model
{
public function activePrograms()
{
return $this->hasMany(Program::class)->active();
}
}
Then you can simplify your query to:
Model::with('activePrograms')->has('activePrograms')->get();

In Laravel 9.17 withWhereHas
Example:
$a= Model::withWhereHas('programs', function ($query) {
$query->active();
})->get();
Check out the documentation for more information.

Related

Foreach with eloquent in laravel

I'm trying to loop through the items using eloquent in laravel but I'm getting 0. Please see my code below.
Model
Class Store{
public function products(){
return $this->hasMany('App\Product');
}
}
Controller
$products_count = 0;
foreach($store->products() as $product)
{
if($product->status == 1)
{
$products_count++;
}
}
dd($products_count);
Note: I have data in my database.
You can also use withCount method something like that
Controller
$stores = Store::withCount('products')->get();
or
$store = Store::where('id', 1)->withCount('products')->first();
WithCount on the particular status
$stores = Store::withCount(['products' => function ($query) {
$query->where('status', 1);
}
])
->get();
ref: withcount on relationship
That's because $store->products() returns an eloquent collection which doesn't contain the data from the database yet. You need to do $store->products instead.
If you need to get the count from the database then use
$store->products()->where('status', 1)->count()
With the function-annotation (i.e. products()) you are retrieving the \Illuminate\Database\Eloquent\Builder-instance, not the actual Eloquent-collection.
Instead, you would have to use $store->products – then you will get retrieve the related collection.
In Laravel $store->products() makes you access the QueryBuilder instance, instead there is the Laravel way of doing $store->products, which loads the QueryBuilder and retrieves the collection automatically and down the line is easy to optimise.

Return belonging name with ID form Laravel, check for the type?

sorry for the title of this question but I am not sure how to ask it...
I am working on a project where I have two Models Trains and Cars, to this model I have a belonging Route.
I want to make a query and check if the routeable_type is App\Car than with the selected routeable_id to get the data from the Car. And if the routeable_type is Train then with the ID to get the data from the Tran.
So my models go like this:
Train:
class Train extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Car:
class Car extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Route:
class Route extends Model
{
public function routeable()
{
return $this->morphTo();
}
}
And the query I have at the moment is:
$data = Route::leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')
->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')
->select('routes.id', 'cars.model AS carmodel', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
With this query if I have the same ID in cars and trains I get the data from both and all messes up. How do I check if routeable_type is Car ... do this, if routeable_type is Train .. do that?
Will something like this be possible in a 1 single query:
$data = Route::select('routes.id', 'routeable_type', 'routes.created_at');
if(routeable_type == 'Car'){
$data = $data->leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')->select('routes.id', 'cars.model AS carmodel', 'routeable_type', 'routes.created_at');
}else{
$data = $data->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')->select('routes.id', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
}
Maybe this is what you are looking for?
DB::table('routes')
->leftJoin('cars', function ($join) {
$join->on('cars.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Car');
})
->leftJoin('trains', function ($join) {
$join->on('trains.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Train');
})
->select('routes.id', 'cars.model AS car_model', 'trains.model AS train_model', 'routes.routeable_type', 'routes.created_at');
->get();
I think you may want to follow the morphedByMany design.
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many-polymorphic-relations
This was also a neat visual for the different relation types.
https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
I was faced with a similar issue though I failed to follow the correct design initially and was forced to query the many possible relations then wrote custom logic after to collect the relation types and ids then do another query and assign them back through iteration. It was ugly but worked... very similar to how Eloquent does things normally.
i don't have enough repo, so i can't comment. that's why i am putting as an answer.
You should use 2 different queries, for each model.
This will be better, code wise as well as performance wise. also if both models have similar fields you should merge them to 1 table and add a 'type' column.
and put non-similar fields in a 'meta' column.
( in my opinion )

Laravel Eloquent, multiple schema query

I have the below query (simplified):
$q = ModelOne::with('relation_one', 'relation_two')
->whereHas('relation_three', function ($q) {
$q->where('object', 'Obj1');
})
->whereHas('relation_four', function ($q) {
$q->where('object', 'Obj2');
})
->get();`
It loads the relation_one and relation_two relationships fine, I also need to load another relationship per row, either relation_three or relation_four depending on the value of ModelOne->object.
The issue I am having is that ModelOne is from schema1 and the tables used in relation_three & relation_four are from schema2.
Both models are set up correct with their individual protected $connection and protected $table variables.
The error I am recieving is that the tables for relationship_three or relationship_four does not exist as the sub-query is checking the wrong schema.
Can anyone suggest how to fix this? Have had a look through the docs but couldn't find a solution.
Maybe not the most elegant solution but got this working by calling relationships and joining as follows:
$q = ModelOne::with('relation_one', 'relation_two')
->with(['relation_three' => function ($q) {
$q->leftJoin(
'schema1.model_one',
'table_three.id',
'=',
'model_one.object_id'
)
->where('object', 'Obj1');
}])
->with(['relation_four' => function ($q) {
$q->leftJoin(
'schema1.model_one',
'table_four.id',
'=',
'model_one.object_id'
)
->where('object', 'Obj2');
}])
->get();`
If anyone can suggest some improvements or a more efficient way to do this please let me know
I would suggest separating the different databases relations to different fields, at least. This way you can then load both (as suggested in comments) and differentiate the logic within controller/model code.
Also, I guess you'll need to define the connection name on the Model level, if not done yet:
class Model_Two_Relation {
protected $connection = 'your-database-name-from-config';
}
You also might want to specify the connection within the relation join condition:
$q = ModelOne::with('relation_one', 'relation_two')
->whereHas('relation_three', function ($q) {
$q->from('resources.one')->where('object', 'Obj1');
})
->whereHas('relation_four', function ($q) {
$q->from('resources.two')->where('object', 'Obj2');
})
->get();
Links: http://fideloper.com/laravel-multiple-database-connections

Laravel Multiple Models Eloquent Relationships Setup?

I have 3 models
User
Pick
Schedule
I'm trying to do something like the following
$picksWhereGameStarted = User::find($user->id)
->picks()
->where('week', $currentWeek)
->first()
->schedule()
->where('gameTime', '<', Carbon::now())
->get();
This code only returns one array inside a collection. I want it to return more than 1 array if there is more than 1 result.
Can I substitute ->first() with something else that will allow me to to return more than 1 results.
If not how can I set up my models relationship to allow this to work.
My models are currently set up as follow.
User model
public function picks()
{
return $this->hasMany('App\Pick');
}
Schedule model
public function picks()
{
return $this->hasMany('App\Pick');
}
Pick model
public function user()
{
return $this->belongsTo('App\User');
}
public function schedule()
{
return $this->belongsTo('App\Schedule');
}
Since you already have a User model (you used it inside you find method as $user->id), you can just load its Pick relationship and load those Picks' Schedule as follows:
EDIT:
Assuming you have a schedules table and your picks table has a schedule_id column. Try this.
$user->load(['picks' => function ($q) use ($currentWeek) {
$q->join('schedules', 'picks.schedule_id', '=', 'schedules.id')
->where('schedules.gameTime', '<', Carbon::now()) // or Carbon::now()->format('Y-m-d'). See what works.
->where('picks.week', $currentWeek);
}])->load('picks.schedule');
EDIT: The code above should return the user's picks which have a schedules.gameTime < Carbon::now()
Try it and do a dump of the $user object to see the loaded relationships. That's the Eloquent way you want.
Tip: you may want to do $user->toArray() before you dump $user to see the data better.
EDIT:
The loaded picks will be in a form of Collections so you'll have to access it using a loop. Try the following:
foreach ($user->picks as $pick) {
echo $pick->schedule->gameTime;
}
If you only want the first pick from the user you can do: $user->picks->first()->schedule->gameTime
I think a foreach loop may be what you're looking for:
$picks = User::find($user->id)->picks()->where('week', $currentWeek);
foreach ($picks as $pick){
$pickWhereGameStarted = $pick->schedule()->where('gameTime', '<', Carbon::now())->get();
}
Try this and see if it's working for you

Merge 'with' and 'whereHas' in Laravel 5

I have this code in Laravel 5, using Eloquent, which is working perfectly:
$filterTask = function($query) use ($id) {
$query->where('taskid', $id);
};
User::whereHas('submissions', $filterTask)->with(['submissions' => $filterTask])->get();
Basically the goal is to get only those users with their filtered submissions, which has any of them.
However, it seems wasting to run both whereHas and with methods with the same callback function. Is there a way to simplify it?
Thanks.
In terms of performance you can't really optimize anything here (except if you were to move from eloquent relations to joins). With or without whereHas, two queries will be run. One to select all users another one to load the related models. When you add the whereHas condition a subquery is added, but it's still two queries.
However, syntactically you could optimize this a bit by adding a query scope to your model (or even a base model if you want to use this more often):
public function scopeWithAndWhereHas($query, $relation, $constraint){
return $query->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
Usage:
User::withAndWhereHas('submissions', function($query) use ($id){
$query->where('taskid', $id);
})->get();
The 'macroable' way (Laravel 5.4+)
Add this inside a service provider's boot() method.
\Illuminate\Database\Eloquent\Builder\Eloquent::macro('withAndWhereHas', function($relation, $constraint){
return $this->whereHas($relation, $constraint)->with([$relation => $constraint]);
});
I want to extend the answer from #lukasgeiter using static functions.
public static function withAndWhereHas($relation, $constraint){
return (new static)->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
Usage is the same
User::withAndWhereHas('submissions', function($query) use ($id){
$query->where('taskid', $id);
})->get();
You can now achieve that In Laravel 9.17
Example:
use App\Models\User;
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();
Check out the documentation for more information

Categories