Querying eloquent relationship - php

I have the following set for relations in my eloquent models/db:
Student has many PhdReport
PhdReport has one Link
I need to get Students where the (single) most recent PhdReport (by the attribute date_to) is more than 6 months and the Link connected to the PhdReport has a status of 'complete'.
I am trying to do this using eloquent relationships and I'm pretty new to the concept of querying relationships so I would like to know if there's a better approach to the one I'm taking.
Here's the relevant code so far:
PhdReport.php
public function link()
{
return $this->belongsTo(\App\Models\Link::class);
}
Student.php
public function phdReport()
{
return $this->hasMany(\App\Models\PhdReport::class);
}
public function latestPhdReport()
{
return $this->hasOne(\App\Models\PhdReport::class)->latest('date_to');
}
/* this doesn't work! */
public function lastPhdReportSixMonthsAgo()
{
$sixMonthsAgo = \Carbon\Carbon::now()->subMonth(6);
return $this->whereHas('latestPhdReport', function ($query) use ($sixMonthsAgo) {
$query->where('date_to', '<=', $sixMonthsAgo);
});
}
This is my best shot at it so far but I am unsure if the first whereHas applies to the second whereHas also?
$sixMonthsAgo = \Carbon\Carbon::now()->subMonth(6);
$students = $this->student
->whereHas('phdReport.link', function ($query) {
$query->where('status', 'complete');
})
->whereHas('latestPhdReport', function ($query) use ($sixMonthsAgo) {
$query->where('date_to', '<=', $sixMonthsAgo);
})
->get();
If I run:
$students = $this->student
->has('lastPhdReportSixMonthsAgo')
->get();
I get:
BadMethodCallException in Builder.php line 1994:
Call to undefined method Illuminate\Database\Query\Builder::getRelated()
Any advice would be much appreciated!

This is my current solution, I haven't had time to refactor yet but I'm sure it can be improved by doing only a query and no filter. Will edit when I get time but thought I'd put it up as it might help someone else. Much credit to #JarekTkaczyk
$monthsAgo = \Carbon\Carbon::now()->subMonth($months);
// Query 1: students with a completed report
$studentsWithCompletedLink = $studentsWithCompletedLink
->whereHas('phdReport.link', function ($query) {
$query->where('status', 'complete');
})
->get();
if ($months != 0) {
// filter through to get students where the report date_to is more than $months months ago
$studentsWithReport = $studentsWithCompletedLink->filter(function ($student) use ($monthsAgo) {
return $student->latestReport->date_to <= $monthsAgo;
});
}
Student.php
public function latestPhdReport()
{
return $this->hasOne(\App\Models\PhdReport::class)->latest('date_to');
}

Related

Laravel Eloquent: hasManyThrough or Something Else?

In Laravel 5.7 I have read the Has Many Through documentation but I still can't use it correctly for my case.
Here's the DB:
Analytics
id
data
subscriber_id
Subscribers
id
city_id
Cities
id
name
I need the Analytics model to get data from Analytics with subscribers.id and cities.name
What I have done:
Connected Analytics and Subscribers models
<?php
class Analytics extends Model
{
public function subscriber()
{
return $this->belongsTo('App\Models\Subscriber');
}
}
class Subscriber extends Model
{
public function analytics()
{
return $this->hasMany('App\Models\Analytics');
}
}
Made a request that gets the data from the Analytics table with subscribers data:
$results = Analytics::where('survey_id', '4')
->with('subscriber')
->whereDate('created_at', '>=', $last_survey_date)
->orderBy('data')
->get();
If anybody has any ideas how to get city names, please, share it.
// maybe this will work for you?
class Analytics extends Model
{
public function subscriber()
{
return $this->belongsTo('App\Models\Subscriber');
}
public function cities() {
return $this->hasManyThrough('App\City', 'App\Subscriber');
}
}
I'm not sure if I understood your request correctly. Do you want to get all Analytics of Subscribers of a given City? Or do you want the city name of an analytics' subscriber? Either way, here are both solutions.
To get all analytics for subscribers of a given city:
$city = 'Vienna';
$results = Analytics::query()
->with('subscriber')
->whereHas('subscriber', function ($query) use ($city) {
$query->whereHas('city', function ($query) use ($city) {
$query->where('name', $city);
});
})
->where('survey_id', '4')
->whereDate('created_at', '>=', $last_survey_date)
->orderBy('data')
->get();
Or to get the city name for an analytics record, you have two options. One is to use the Laravel eager loading for relationships, which works, but will probably load a lot of unnecessary data into memory:
$results = Analytics::query()
->with('subscriber.city') // you can nest relationships as far as they go
->where('survey_id', '4')
->whereDate('created_at', '>=', $last_survey_date)
->orderBy('data')
->get();
foreach ($results as $analytics) {
$city = $analytics->subscriber->city->name;
}
The other is to join the tables yourself and select only the necessary data:
$results = Analytics::query()
->join('subscribers', 'subscribers.id', '=', 'analytics.subscriber_id')
->join('cities', 'cities.id', '=', 'subscribers.city_id')
->where('analytics.survey_id', '4')
->whereDate('analytics.created_at', '>=', $last_survey_date)
->orderBy('analytics.data')
->select('analytics.*', 'cities.name as city_name')
->get();
foreach ($results as $analytics) {
$city = $analytics->city_name;
}
Beware that you could use select('analytics.*', 'cities.name'), but this will override a name column selected of the analytics table if one exists. So it is better to use a column alias with as col_alias.
Thank you Nick Surmanidze and Namoshek for your answers!
I have found the way it works last night:
class Subscriber extends Model
{
public function analytics()
{
return $this->hasMany('App\Models\Analytics');
}
public function subscriberCity()
{
return $this->belongsTo('Modules\Directories\Entities\City', 'city_id', 'id');
}
}
class City extends Model
{
public function subscriber()
{
return $this->hasMany('App\Models\Subscriber');
}
}
And the needed result can be get by this way:
$results = Analytics::where('survey_id', '4')
->with(['subscriber' => function($i){
$i->with(['subscriberCity']);
}])
->whereDate('created_at', '>=', $last_survey_date)
->orderBy('data')
->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');

How to query multiple relationships in laravel eloquent

I'm kinda stuck here while querying multiple relationships in laravel eloquent I had the raw query like
SELECT * FROM tblSchedules,tblUserHomeCourts,tblHomeCourts
where tblHomeCourts.userId=6
and tblHomeCourts.homeCourtId=5
and (tblSchedules.timeFrom <= 1495617580
and tblSchedules.timeTo >= 1495617580)
and tblUserHomeCourts.userHomeCourtStatus=1
and tblSchedules.scheduleStatus=0
now I need to query this raw query into laravel eloquent. I've tried to to do so like this
$getSchedule= Schedule::with(['userSchedule' => function($query) use ($userId,$homeCourtId,$timeFrom, $timeTo) {
$query->where(['userId'=> $userId,'homeCourtId'=>$homeCourtId,'userHomeCourtStatus' => Constant::STATUS_1]);
}]) ->where('timeFrom','<=',$timeFrom)
->where('timeTo','>=',$timeTo)
->where(['scheduleStatus'=>Constant::STATUS_0])
->get();
but I'm not getting the result instead of this I'm getting blank message array where I'm supposed to get the records.
Question:
what I have done wrong?
suggest me the correct way of query this query.
Schedule Model
public function friendsFeed(){
return $this->hasOne(UserHomeCourt::class,'userHomeCourtId','userHomeCourtId')->with('user')->with('homeCourt');
}
public function userSchedule(){
return $this->hasOne(UserHomeCourt::class,'userHomeCourtId','userHomeCourtId')->with('homeCourt');
}
UserHomeCourt Model
public function homeCourt(){
return $this->belongsTo(HomeCourt::class,'homeCourtId','homeCourtId');
}
public function user(){
return $this->belongsTo(User::class,'userId','userId');
}
}
HomeCourt Model
public function friendsFeed()
{
return $this->hasOne(UserHomeCourt::class, 'homeCourtId', 'homeCourtId');
}
public function userSchedule()
{
return $this->hasOne(UserHomeCourt::class, 'homeCourtId', 'homeCourtId');
}
Response I have got from SQL Query
The time check from your sql to eloquent was wrong and when you pass multiple conditions to where, you need to set the conditions as an array.
$getSchedule= Schedule::with(['userSchedule' => function($query) use ($userId, $homeCourtId) {
$query->whereHas('homeCourt', function ($query) use ($userId, $homeCourtId) {
$query->where('userId', $userId)
->where('homeCourtId', $homeCourtId);
})->where('userHomeCourtStatus', 1);
}])
->where('timeFrom', '<=', $timeFrom)
->where('timeTo', '>=', $timeTo)
->where('scheduleStatus', 0)
->get();
try to use whereHas function:
Schedule::whereHas('userSchedule', function ($query)
{
$query->where('userId', $userId)
->where('homeCourtId', $homeCourtId)
->where('userHomeCourtStatus', Constant::STATUS_1);
})
->where('timeFrom', '<=', $timeFrom)
->where('timeTo', '>=', $timeTo)
->where('scheduleStatus', Constant::STATUS_0)
->get();

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 orWhere or different Model

I am trying to do a query that filters on 2 columns on one table OR 2 columns on another. This is what I have so far:
// In my controller
return $registry = Registry::search($first_name, $last_name)->get();
// In my model
public function user()
{
return $this->belongsTo('User');
}
public function scopeSearch($query, $first_name, $last_name)
{
$search = $query->where('coregistrant_first_name', 'LIKE', "%$first_name%")
->where('coregistrant_last_name', 'LIKE', "%$last_name%")
->orWhere(function($query) use ($first_name, $last_name)
{
$query->$this->user()->where('first_name', 'LIKE', "%$first_name%");
});
return $search;
}
I have tried a lot of different things and now I'm stuck on the $query->$this->user() line with this error:
Undefined property: Illuminate\Database\Query\Builder::$[]
Anyone know how I can fix this problem?
#Keith, In Laravel, you can not use relationship for selecting data dynamically.
AS far as i know, Laravel support the following rules to retrieve data using relationship:
$registers = Registry::with('user')->get();
$registers = Registry::where('xyz', '1')->get();
// then you can load the relationship if you needed
$registers->load('user');
In your model
public function user()
{
return $this->belongsTo('User')->where('first_name', 'LIKE', "%xyz%");
}
Then From your controller:
$registers = Registry::with('user')->get();
If you have one to many relationship,
Say you have licenses relationship
// You can specify an operator and a count:
$registers = Registry::has('licenses', '>=', 3)->get();
Reference: http://laravel.com/docs/eloquent#querying-relations
I ended up using query builder to do it.

Categories