Laravel Eloquent Accessor Strange Issue - laravel

Laravel Version: 5.6.39
PHP Version: 7.1.19
Database Driver & Version: mysql 5.6.43
Description:
When I chain where and orWhere in a model accessor to count related model , I get wrong result and here is my query. the count is returned strange result without filtering by the calling event id,
class Event extends Model
{
protected $table = 'events';
public function registrations()
{
return $this->hasMany('App\Components\Event\Models\Registration','event_id','id');
}
public function getSeatsBookedAttribute()
{
return $this->registrations()
->where('reg_status','=','Confirmed')
->orWhere('reg_status','=','Reserved')
->count();
}
}
Steps To Reproduce:
the following queries return me the expected results, however In my knowledge the first query should return the same result if i am not wrong, so i think this is a potential bug.
class Event extends Model
{
public function getSeatsBookedAttribute()
{
return $this->registrations()
->whereIn('reg_status', ['Confirmed', 'Reserved'])
->count();
}
}
class Event extends Model
{
public function getSeatsBookedAttribute()
{
return $this->registrations()
->where(function($query){
$query->where('reg_status','Confirmed')
->orWhere('reg_status','Reserved');
})
->count();
}
}
and here is the query dump,
here is the query when I donot explicit group it.
"select count(*) as aggregate from events_registration where (events_registration.event_id = ? and events_registration.event_id is not null and reg_status = ? or reg_status = ?) and events_registration.deleted_at is null "
and here is the query when i group it explicitly,
select count(*) as aggregate from events_registration where events_registration.event_id = ? and events_registration.event_id is not null and (reg_status = ? or reg_status = ?) and events_registration.deleted_at is null

The reason this happens is because you're chaining where() and orWhere(). What you don't see behind the scenes is a where event_id = :event_id applying to your query. You end up with a query that looks something like this:
select * from registrations where event_id = :event_id and reg_status = 'Confirmed' or reg_status = 'Reserved'
In normal SQL you'd want to put the last 2 conditions in parentheses. For Eloquent, you'd need to do something like this:
return $this->registrations()->where(function ($query) {
$query->where('reg_status', 'Confirmed')
->orWhere('reg_status', 'Reserved');
});
You can chain the toSql() method on these chains to see the difference. Note, that in this case, I believe whereIn() is the semantically correct thing to do.
Eloquent can handle this for you, though; scroll down to "Counting Related Models" in the Querying Relations part of the Eloquent Relationships docs:
$posts = App\Event::withCount([
'registrations as seats_booked_count' => function ($query) {
$query->where('reg_status','Confirmed')
->orWhere('reg_status','Reserved');
}
])->get();

Related

Filter based on collection or sub-attribute

My user model has a 'prevregistration' attribute
public function prevregistration()
{
return $this->hasMany(Prevregistration::class, 'prevregistration_userid');
}
My prevregistraton model has a 'prev' attribute
public function prev()
{
return $this->hasOne(Prev::class,'prev_id', 'prevregistration_previd');
}
In my controller I show prevregistrations for the current user:
mynextprevs = Auth::user()->prevregistration ;
Now I want to only show prevregistrations from which the connected prev its prev_date in the future, like this:
$mynextprevs = Auth::user()->prevregistration::whereDate('prev_date', '>=', Carbon::today()->toDateString());
But then I get:
BadMethodCallException
Method Illuminate\Database\Eloquent\Collection::whereDate does not exist.
I also tried like this:
$mynextprevs = Auth::user()->prevregistration->prev::whereDate('prev_date', '>=', Carbon::today()->toDateString());
But then I get:
Property [prev] does not exist on this collection instance.
Should I/how can I filter the collection? I'm curious why Auth::user()->prevregistration->prev is not working, since that are attributes.
Thanks
You need to use the condition whereHas on your prevregistration
$mynextprevs = Auth::user()->prevregistration()->whereHas('prev',function($prev) {
$prev->whereDate('prev_date', '>=', Carbon::today()->toDateString());
})->get();
Notice we used the relation as a method prevregistration() to access it as a query builder and not as a collection hence the need for the ->get() at the end.

whereBetween doesnt exist and i cant apply paginate

I've this method:
public function indexGuest($idAzienda, Request $request){
$companyId = $idAzienda;
$ddts = Ddt::where('company_id',$companyId);
$ddt_urls= Ddt_file_url::all();
if($request->start_date || $request->end_date){
$ddts->whereBetween('created_at',[new Carbon($request->start_date),new Carbon($request->end_date)]);
}
$ddts->paginate(10);
return view('guest.ddt-management')->with('ddts', $ddts)->with('ddt_urls',$ddt_urls)
->with('companyId',$companyId);
}
My start_date and end_date comes in strings like "yyyy-mm-dd".
I've tried to pass it straight to the query and like in the example like a carbon object with no hope!
After executing the query (now only the one without the wherebeeteween clause) i cant apply the method "paginate" to the collection, no error are raised but when i pass it to the view, the "link()" method not work and raise an error again.
where I wrong?
Laravel 5.4
Structure your wheres like this.
public function indexGuest($idAzienda, Request $request) {
[...]
$ddts = Ddt::where('company_id', $companyId)
->where(function($query) use ($request) {
if($s = $request->get("start_date") {
$s_date = Carbon::parse($s)->format("Y-m-d");
$query->whereDate("created_at", ">=", $s_date);
}
if($e = $request->get("end_date") {
$e_date = Carbon::parse($e)->format("Y-m-d");
$query->whereDate("created_at", "<=", $e_date);
}
})
->paginate(10);
[...]
}

Equivalent laravel relationship query for this join

this is my query
$personnel_info = \DB::table('assigns AS a')
->join('boxes AS b','b.id','=', 'a.box_id')
->join('positions AS p','p.id','=', 'b.position_id')
->select('a.id','b.id AS box_id','p.id as position_id','p.title','a.status','a.end_date')
->where('a.personnel_id','=',$personnel_id)
->get();
and this realtionship for boxes:
class Boxes extends Model
{
public function position()
{
return $this->belongsTo('Positions');
}
public function assign()
{
return $this->hasOne('Assigns', 'box_id');
}
}
how to use eloquent query(also realtionship) for replace DB facade query?
i want select some field for tables.without define fileds in boxes model
tnx
Try
$personnel_info = Assign::with('box.position')
->where('personnel_id', $personnel_id)
->get();
Then dd($personnel_info) to see everything that was returned. If you don't like the values, then add your select() clause.

Laravel 5.3 inner join not working properly

I'm having two tables as 'jobs' and 'desired_skills'.
Table structure is as follows.
jobs table
jobs Table
desired_skills table
desired_skils table
where desired_skills.job_id refers to jobs.job id
In controller I have (I am getting $id as an argument from the url, and I can confirm the argument grabs the desired value)
$jobs = DB::table('jobs')->where(function ($query) use ($id) {
$query->Join('desired_skills', 'desired_skills.job_id', '=', 'jobs.job_id')
->where('jobs.employer_id', '=', $id);
->select('*')
})->get();
when I dump and die $jobs it only returns values from jobs table.
but when I run the query
SELECT * FROM jobs INNER JOIN desired_skills ON desired_skills.job_id = jobs.job_id it returns the desired value set.
What am I doing wrong? Any help will be greatly appreciated.
I think it has to do with wrapping your join inside of a where clause. I don't think it's giving you your desired query with that there.
$jobs = DB::table('jobs')
->join('desired_skills', 'desired_skills.job_id', '=', 'jobs.job_id')
->where('jobs.employer_id', '=', $id)
->get();
The query SELECT * FROM jobs INNER JOIN desired_skills ON desired_skills.job_id = jobs.job_id
is not the same has what you are trying to do in the function. In this query there is not
mention of 'employer_id' in the table 'jobs'.
An alternative would be to use eloquent relationships, as refered in a comment.
You need 3 classes in models:
Employer
Job
DesiredSkill
Between Employer and Job -> one-to-many relation (an employer can have multiple jobs).
Between DesiredSkill and Job -> one-to-one relation.
I'm not sure what you are trying to get from the join, but i think that if you implement
the methods that allow the relationships i believe you solve whatever.
class Job extends Model
{
public function employer()
{
return $this->hasOne('App\Job');
}
}
class Employer extends Model
{
public function jobs()
{
return $this->hasMany('App\Employer');
}
public function desiredSkill()
{
return $this->hasOne('App\DesiredSkill');
}
}
class DesiredSkill extends Model
{
public function job()
{
return $this->hasOne('App\DesiredSkill');
}
}
Try this:
$jobs = DB::table('jobs')
->join('desired_skills', 'desired_skills.job_id', '=', 'jobs.job_id')
->select('jobs.*', 'desired_skills.*')
->get();

How to get relationship with union in eloquent?

How can I make relationship with union in laravel eloquent? I've already tried two different approaches.
User::with(['url' => function($query) use(&$some_property) {
$favouriteUrls = \DB::table('urls')
->select('urls.*')
->join('favourite_urls', function($join) {
$join->on('favourite_urls.url_id', '=', 'urls.id');
})
->where('some_condition', '=', $some_property);
$query = $query->union($favouriteUrls);
}]);
In the first attempt there wasn't any union in the query. Then I tried to move the logic to the model.
class User extends \Eloquent {
public function urls() {
$favouriteUrls = \DB::table('urls')
->select('urls.*')
->join('favourite_urls', function($join) {
$join->on('favourite_urls.url_id', '=', 'urls.id');
})
->where('some_condition', '=', $this->some_property);
return $this->belongsTo('Url')->union($favouriteUrls);
}
}
It has executed successfully but $this->some_property was set inside the query to the null value.
I can't create two separate relationship in this case. It has to be one with union. How can I fix it?
If you call that relation as User::with('urls') you will get that $this->some_property doesn't exists, because the object itself doesn't exists. But if you call the urls() method on an object, it should work. Something like this:
$user = User::find(1);
$user->urls; // here $this->some_property should have a value
Assuming you're calling the urls() method from a User object, the $this->some_property should give you the value. If for some reason you cannot access a property directly on an Eloquent model you can always refer to the attributes[] array inside of the model. For example
// calling
$this->some_property
// should be the same as
$this->attributes['some_property']
Fetch all the users joining with the condition
Assuming users is the table for all the users, in your query you could change $this->some_property with 'users.some_property' and everything should work as expected, for each user it will query based on that property. Here is the code:
class User extends \Eloquent {
public function urls() {
$favouriteUrls = \DB::table('urls')
->select('urls.*')
->join('favourite_urls', function($join) {
$join->on('favourite_urls.url_id', '=', 'urls.id');
})
->where('some_condition', '=', 'users.some_property');
return $this->belongsTo('Url')->union($favouriteUrls);
}
}
And then just call the method like this:
User::with('urls')->get();

Resources