Laravel Eloquent rather complex relationship - php

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);
}

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.

Laravel: create a fake belongsToMany withPivot relationship

I have a belongsToMany() relationship between a User and a Group. The user has a level within any group he belongs to.
public function groups()
{
return $this->belongsToMany('App\Group', 'user_group', 'user_id', 'group_id')
->withPivot('level');
}
This works great.
However if the User is an admin, I would like the groups function to return ALL Groups with level = 3, regardless of whether that relationship exists in the pivot table or not.
I can successfully create a Collection which mirrors the data structure as follows:
\App\Group::all()->transform(function ($item, $key) use ($uid) {
$item->pivot = collect(['user_id'=>$uid,'group_id'=>$item->id,'level'=>3]);
return $item;
});
However, I cannot substitute the two outputs as one returns a belongsTo relationship instance and the other returns a Collection. This means I can call ->get() on the former but not the latter.
I thought about using the DB:: facade and creating a Builder for the latter, but I cannot add the Pivot values manually.
Any thoughts on how to achieve this?
-- UPDATE --
I am currently cheating by adding the ->get() inside the groups() method, but this is messy and I would still like to know if there is a better way to solve this problem.
public function groups()
{
if ($this->isAdmin()) {
return \App\Group::all()->transform(function ($item, $key) use ($uid) {
$item->pivot = collect(['user_id'=>$uid,'group_id'=>$item->id,'level'=>3]);
return $item;
});
} else {
return $this->belongsToMany('App\Group', 'user_group', 'user_id', 'group_id')
->withPivot('level')->get();
}
}
So this solution should work(not tested), but it is not the "cleanest" it would be better to access all groups through some other mechanism but because I don't know your admin implemention it is hard to guess.
public function groups()
{
return $this->belongsToMany('App\Group', 'user_group', 'user_id', 'group_id')
->withPivot('level');
}
public function scopeSpecialGroups($query)
{
return $query->when($this->role === 'admin',function($query){
return Group::where('level', '>', 3');
})->when($this->role != 'admin',function($query){
return $query->with('groups');
});
}
Then you should be able to call User::specialGroups()->get();

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');

Eloquent Query Scope on Relationships

I have two models, App\Song (belongsTo App\Host) and App\Host (hasMany App\Song).
I have the following query in my Controller:
$songs = Song::whereHas('host', function($query) {
$query->where('skip_threshold', '>', \DB::raw('songs.attempts'))
->where('active', 1);
})
->whereNull('downloaded')
->get();
For reusability I would like to turn into a query scope(s).
I'm quite new to Eloquent so I'm not sure this is the correct way to do this being that its two Models as its not returning any results (where there should be).
Song.php
public function scopeEligable($query)
{
$query->where('skip_threshold', '>', \DB::raw('songs.attempts'));
}
public function scopeActiveHost($query)
{
$query->where('active', 1);
}
public function scopeInDownloadQueue($query)
{
$query->whereNull('downloaded');
}
You should put scopes into Models they belong to. Looking at your initial query scopes scopeEligable and scopeActiveHost belongs to Host model, so you should move them into Host model and then you'll be able to use your query using scopes like this:
$songs = Song::whereHas('host', function($query) {
$query->eligable()->activeHost();
})->inDownloadedQueue()->get();
and as already pointed in comment you should add return to each scope so they could be used as they intended.
EDIT
If you would like to make using it shorter, you could create new relationship in Song model:
public function activeHost()
{
return $this->belongsTo(Host:class)->eligable()->activeHost();
}
so now, you could write:
$songs = Song::whereHas('activeHost')->inDownloadedQueue()->get();
I think you're mistaken about 2 models. I think this should work
Song.php
public function scopeEligable($query, $active) {
return $query->whereHas('host', function($q) {
$q->where('skip_threshold', '>', \DB::raw('songs.attempts'))->where('active', $active);
})
}
public function scopeInDownloadQueue($query)
{
$query->whereNull('downloaded');
}
Usage
$songs = Song::eligable(true)->inDownloadQueue()->get();

Laravel where on relationship object

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

Categories