Laravel where on relationship object - php

I'm developing a web API with Laravel 5.0 but I'm not sure about a specific query I'm trying to build.
My classes are as follows:
class Event extends Model {
protected $table = 'events';
public $timestamps = false;
public function participants()
{
return $this->hasMany('App\Participant', 'IDEvent', 'ID');
}
public function owner()
{
return $this->hasOne('App\User', 'ID', 'IDOwner');
}
}
and
class Participant extends Model {
protected $table = 'participants';
public $timestamps = false;
public function user()
{
return $this->belongTo('App\User', 'IDUser', 'ID');
}
public function event()
{
return $this->belongTo('App\Event', 'IDEvent', 'ID');
}
}
Now, I want to get all the events with a specific participant.
I tried with:
Event::with('participants')->where('IDUser', 1)->get();
but the where condition is applied on the Event and not on its Participants. The following gives me an exception:
Participant::where('IDUser', 1)->event()->get();
I know that I can write this:
$list = Participant::where('IDUser', 1)->get();
for($item in $list) {
$event = $item->event;
// ... other code ...
}
but it doesn't seem very efficient to send so many queries to the server.
What is the best way to perform a where through a model relationship using Laravel 5 and Eloquent?

The correct syntax to do this on your relations is:
Event::whereHas('participants', function ($query) {
return $query->where('IDUser', '=', 1);
})->get();
This will return Events where Participants have a user ID of 1. If the Participant doesn't have a user ID of 1, the Event will NOT be returned.
Read more at https://laravel.com/docs/5.8/eloquent-relationships#eager-loading

#Cermbo's answer is not related to this question. In that answer, Laravel will give you all Events if each Event has 'participants' with IdUser of 1.
But if you want to get all Events with all 'participants' provided that all 'participants' have a IdUser of 1, then you should do something like this :
Event::with(["participants" => function($q){
$q->where('participants.IdUser', '=', 1);
}])
N.B:
In where use your table name, not Model name.

for laravel 8.57+
Event::whereRelation('participants', 'IDUser', '=', 1)->get();

With multiple joins, use something like this code:
$userId = 44;
Event::with(["owner", "participants" => function($q) use($userId ){
$q->where('participants.IdUser', '=', 1);
//$q->where('some other field', $userId );
}])

Use this code:
return Deal::with(["redeem" => function($q){
$q->where('user_id', '=', 1);
}])->get();

for laravel 8 use this instead
Event::whereHas('participants', function ($query) {
$query->where('user_id', '=', 1);
})->get();
this will return events that only with partcipats with user id 1 with that event relastionship,

I created a custom query scope in BaseModel (my all models extends this class):
/**
* Add a relationship exists condition (BelongsTo).
*
* #param Builder $query
* #param string|Model $relation Relation string name or you can try pass directly model and method will try guess relationship
* #param mixed $modelOrKey
* #return Builder|static
*/
public function scopeWhereHasRelated(Builder $query, $relation, $modelOrKey = null)
{
if ($relation instanceof Model && $modelOrKey === null) {
$modelOrKey = $relation;
$relation = Str::camel(class_basename($relation));
}
return $query->whereHas($relation, static function (Builder $query) use ($modelOrKey) {
return $query->whereKey($modelOrKey instanceof Model ? $modelOrKey->getKey() : $modelOrKey);
});
}
You can use it in many contexts for example:
Event::whereHasRelated('participants', 1)->isNotEmpty(); // where has participant with id = 1
Furthermore, you can try to omit relationship name and pass just model:
$participant = Participant::find(1);
Event::whereHasRelated($participant)->first(); // guess relationship based on class name and get id from model instance

[OOT]
A bit OOT, but this question is the most closest topic with my question.
Here is an example if you want to show Event where ALL participant meet certain requirement. Let's say, event where ALL the participant has fully paid. So, it WILL NOT return events which having one or more participants that haven't fully paid .
Simply use the whereDoesntHave of the others 2 statuses.
Let's say the statuses are haven't paid at all [eq:1], paid some of it [eq:2], and fully paid [eq:3]
Event::whereDoesntHave('participants', function ($query) {
return $query->whereRaw('payment = 1 or payment = 2');
})->get();
Tested on Laravel 5.8 - 7.x

Related

Laravel Eloquent Model with relationship

I have implemented eloquent relationship in my code but Laravel unable to read the function that I created to map the eloquent relationship in the model.
User Model
public function products(){
return $this->hasMany(Product::class,'userid');
}
Product Model
public function users(){
return $this->belongsTo(User::class);
}
Product Controller
$products = Product::with('Users')->Users()->where('users.isActive',1)->get();
return view('product',compact('products'));
I keep getting error from the product controller, I also attached the error that I current encountered as below.
How can I get all the product and user data with the where condition such as "Users.isActive = 1".
Thanks.
You can use whereHas to filter from a relationship.
$products = Product::with('users')
->whereHas('users', function ($query) {
$query->where('isActive', 1);
})
->get();
Also it is generally a good idea to use singular noun for belongsTo relationship because it returns an object, not a collection.
public function user() {
return $this->belongsTo(User::class);
}
$products = Product::with('user')
->whereHas('user', function ($query) {
$query->where('isActive', 1);
})
->get();
EDIT
If you want to retrieve users with products you should query with User model.
$users = User::with('products')
->where('isActive', 1)
->get();
Then you can retrieve both users and products by
foreach($users as $user) {
$user->products;
// or
foreach($users->products as $product) {
$product;
}
}
You can use whereHas() method for this purpose. Here is the doc
$products = Product::with('users')->whereHas('users', function (Illuminate\Database\Eloquent\Builder $query) {
$query->where('isActive', 1);
})->get();
$users = $products->pluck('users');
return view('product',compact('products'));
You have a typo after the with, is users instead of Users and you're redundant about the Query Builder, remove the ->Users():
Before:
$products = Product::with('Users')->Users()->where('users.isActive',1)->get();
return view('product',compact('products'));
After:
$products = Product::with('users')->where('users.isActive',1)->get();
return view('product',compact('products'));
Fix that and all should work.

Complex query constraint on queryScope in laravel

users table have the following columns:
created_at (timestamp)
last_order (timestamp)
when new users is created then last_order is null and created_at is current timestamp.
When user place an order then last order column is updated with timestamp of order placing time.
Now I want to retrieve all users who didn't place any order last 7 days. I define my user model as follows:
class User extends Model
{
/**
* #var string The database table used by the model.
*/
public $table = 'users';
/**
* #var array Validation rules
*/
public $rules = [
];
public $hasMany = [
'voucher' => ['ItScholarBd\Api\Models\Voucher'],
'order' => ['ItScholarBd\Api\Models\OrderLog']
];
public function scopeCustomer($query)
{
return $query->where('role_id', '=', 5);
}
public function scopeNew($query,$days)
{
return $query->where('created_at', '>=', Carbon::now()->subDays($days));
}
public function scopeIdle($query,$days)
{
$dayOffset = Carbon::now()->subDays($days);
return $query->where(function($q,$dayOffset){
$q->whereNull('last_order')
->where('created_at','<',$dayOffset);
})->orWhere(function($q,$days){
$q->whereNotNull('last_order')
->whereRaw("DATEDIFF(created_at,last_order)>$days");
});
}
}
Here scopeNew() is working perfectly but scopeIdle() is throwing the following error:
Type error: Too few arguments to function App\User::App\{closure}(),
1 passed in .\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Builder.php on line 222 and exactly 2 expected
You don't get to make up parameters for functions you don't control. The closure will be passed a single argument, and you need to import any other variables with use:
public function scopeIdle($query, $days)
{
$dayOffset = Carbon::now()->subDays($days);
return $query
->where(function ($q) use ($dayOffset) {
$q->whereNull('last_order')
->where('created_at', '<', $dayOffset);
})
->orWhere(function ($q) use ($days) {
$q->whereNotNull('last_order')
->whereRaw("DATEDIFF(created_at, last_order) > ?", [$days]);
});
}
Note the use of parameters in the whereRaw statement, which can save you from SQL injection. You will also find your code easier to work on if you consistently indent and space it.
Try using a closure:
return $query->where(function($q) use ($dayOffset){

Laravel Eloquent rather complex relationship

I have two tables, users and jobs. A user owns a job:
from User model:
public function getJobs(){
return $this->belongsTo('App\Jobs','operative_id','id');
}
public function getIncompleteJobs(){
return $this->belongsTo('App\Jobs','operative_id','id')
->where('jobStatus_id', '=', 1);
}
This is fine but the job has a status (completed, pending etc) in a field called 'status_id' and I want to further refine getJobs() so it only returns those with status_id equalling 2.
May be I am being thick!
Edit from the comments:
$op = App\User::where('client_id', $us->getCompany->id)
->where('operative', 1)
->orderby('name')
->where('active', 1)
->with('getIncompleteJobs')
->get();
You could create the relationship, and them call it with whatever condition you want in another method.
Like that:
public function jobs()
{
return $this->belongsTo('App\Jobs','operative_id','id');
}
public function completedJobs()
{
return $this->jobs()->where('status_id', '=', 2);
}

Laravel Eloquent deep nested query

I'm still learning Laravel and I can't find the solution for this problem.
I need to get invoices(with expenses) that are related to specific Partner Type.
I tried this:
$p = Project::with(['invoices.partner.partnerType' => function($query){
$query->where('partnerTypeName', 'Lieferant');
}, 'expenses'
])->where('id', $id)
->first();
I want to select invoices for Lieferant, but I get all invoices for one project.
Project Model:
public function invoices()
{
return $this->hasMany('App\Invoice');
}
Invoice Model
public function expenses()
{
return $this->hasMany('App\Expense');
}
public function partner()
{
return $this->belongsTo('App\Partner');
}
Partner Model
public function partnerType()
{
return $this->belongsTo('App\PartnerType');
}
Edit: PartnerType Model
public function partners()
{
return $this->hasMany('App\Partner');
}
Edit 2: Database
Partner(partnerID, name, partnerTypeId)
PartnerType(partnerTypeId, partnerTypeName)
Project(projectID, name)
Invoice(invoiceID, name, projectID, partnerID)
Expenses(expenseID, invoiceID)
If your models look like that.
Should be like :
$p = Project::with(['invoices' => function($query){
$query->where('partnerTypeName', 'Lieferant')
->with(['expenses','partner' => function($q){
$q->with('partnerType');
}]);
}])->where('id', $id)
->first();
return dd($p);
The solution to your problem is to update your query like this:
$p = Project::with(['invoices' => function($query){
$query->with('expenses')->whereHas('partner.partnerType', function($q){
$q->where('partnerTypeName', 'Lieferant');
});
}])
->where('id', $id)
->first();
But a cleaner solution would be using a scope for your problem.
In your Invoice model.
// Invoice.php
public function scopeByPartnerType($query, $partnerType)
{
$query->whereHas('partner.partnerType', function($q) use ($partnerType) {
$q->where('partnerTypeName', $partnerType);
});
}
And then in your Project model, add another relation that will just get Invoices with a particular partner type.
// Project.php
public function lieferantInvoices()
{
return $this->hasMany('App\Invoices')->byPartnerType('Lieferant');
}
Now you can do just this:
$project->find($id)->load('lieferantInvoices');

use laravel advanced where clauses to run a query

Suppose I have a Course Model like this :
class Course extends Model
{
public function users ()
{
return $this->belongsToMany('App\User', 'course_user', 'course_id', 'user_id');
}
public function lessons ()
{
return $this->hasMany('App\Lesson', 'course_id', 'course_id');
}
}
Course fields are :
course_id
title
Each Course can have multiple lessons.
Lesson Model is like :
class Lesson extends Model
{
public function course ()
{
return $this->belongsTo('App\Course', 'course_id', 'course_id');
}
public function users ()
{
return $this->belongsToMany('App\User', 'lesson_user', 'lesson_id', 'user_id');
}
}
And it's fields are:
lesson_id
title
course_id
As you see there is a OneToMany relation between Course and Lesson and a ManyToMany relation between User and Course.
User And Course Pivot table named ~course_user` have these fields :
course_id
user_id
In the other hand there is a ManyToMany relation between User and Lesson. pivot table for those named lesson_user and have these fields :
lesson_id
user_id
passed
passed field show status of a user in a lesson. if it was 0 ,means user has not passed it yet otherwise he passed it.
User Model is like :
class User extends Model
{
public function lessons()
{
return $this->belongsToMany('App\Lesson', 'lesson_user', 'user_id', 'lesson_id')
}
public function courses ()
{
return $this->belongsToMany('App\Course', 'course_user', 'user_id', 'course_id');
}
}
Now I want to get user courses and calculate percent of passed lessons in each Course via best way, for example nested where clauses.
I think this might be not the best way. But it is easy to understand and maintainable
$courses = $user->courses->map(function($cource){
$all_lessions = $cource->pivot->count();
$done_lessions = $cource->pivot->where(passed,'<>',0)->count();
$percent = $done_lessions * 100 / $all_lessions;
return $cource->push(['percent'=>$percent]);
});
Now you can access through
foreach ($courses as $cource){
$cource->percent;
$cource->title;
//...
}
With inspiration from #KmasterYC answer I wrote bellow codes and all things work:
$userCourses =
$currentUser->courses()
->take(3)
->get();
$userCourses->map(function ($course) use ($currentUser) {
$allLessonsCount = $course->lessons->count();
$courseLessonID = $course->lessons->lists('lesson_id')->toArray();
$userLessonsCount = $currentUser->lessons()
->where('passed', '=', true)
->whereIn('lesson_user.lesson_id', $courseLessonID)
->count();
$percent = round($userLessonsCount * 100 / $allLessonsCount);
$course['percent'] = $percent;
});

Categories