Laravel 9 sub-query target linked model columns in where clause - php

I'm working inside a Laravel 9 project and am trying to perform a query and return my Monitor model where the User role of the monitor is a customer (eventually adding more fields).
My current query attempt throws an error:
Property [user] does not exist on the Eloquent builder instance.
What am I missing?
/**
* Get inactive users
*
* #return array
*/
protected function getInactiveUsersWithMonitors()
{
$monitors = Monitor::with('user')->where(function ($query) {
$query->user->where('role', 'customer');
})->get();
return $monitors;
}

If you have already defined the user function in the Monitor model. Here is what you should do.
/**
* Get inactive users
*
* #return array
*/
protected function getInactiveUsersWithMonitors()
{
/* $monitors = Monitor::with('user')->where(function ($query) {
$query->user->where('role', 'customer');
})->get(); */
$monitors = Monitor::whereHas('user', function($query)
{
$query->where('role', 'customer');
})->get();
return $monitors;
}
This will get all the details from the monitors table as well as the corresponding details from users table.

Related

Laravel - Getting count of nested relationship

I have an application where users can control their properties and leases.
These are the relationships defined:
//Team.php
/**
* A team can have many properties.
*/
public function properties():
{
return $this->hasMany(Property::class);
}
//Property.php
/**
* A property can have many leases.
*/
public function leases():
{
return $this->hasMany(Lease::class);
}
As you can see here, a Team can have many properties, and each property can also have many leases.
I am trying to figure out how I can get the number of leases that is associated with a Team:
$team = Auth::user()->currentTeam;
return $team->properties->leases->count();
However, the above code returns an error:
Property [leases] does not exist on this collection instance.
You could add this method to your Team.php:
public function leasesCount(){
return count(
Team::
join('properties', 'properties.team_id', '=', 'teams.id')
->join('leases', 'leases.property_id', '=', 'properties.id')
->where('teams.id', $this->id)
->get()
);
}
I ended up using a hasManyThrough relationship on the Team model, like so:
//Team.php
/**
* Get all of the leases for the team.
*/
public function leases()
{
return $this->hasManyThrough(Lease::class, Property::class);
}
Then I can simply get the number of leases created by a specific team by:
return $team->leases()->count();

Correct way to set up this Laravel relationship?

I'm after a bit of logic advice. I am creating a system where users login and register their participation at an activity. They can participate at an activity many times. What is the best way to do this? I want to ensure I can use eloquent with this rather than creating my own functions.
I am imagining...
Users:
id
Activitys:
id
name
Participations:
id
user_id
activity_id
time_at_activity
I want to later be able to do such things as:
$user->participations->where('activity_id', 3)
for example.
What is the best way to set this up? I had in mind..
User: hasMany->Participations
Activity: belongsTo->Participation
Participation: hasMany->Activitys & belongsTo->User
Does this look correct?
The users schema can relate to activities through a pivot table called participations:
/**
* Indicate that the model belongs to a user.
*
* #see \App\Model\User
*
* #return BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Indicate that the model belongs to many activity participations.
*
* #see \App\Model\Activity
*
* #return BelongsTo
*/
public function participations()
{
return $this->belongsToMany(Activity::class, 'participations');
}
$user->participations()->attach($activity);
You may want to add the reciprocal relationships. These can be separated out into traits for code reuse. ->attach(['participated_at' => now()])
You can use Many-to-Many Relationship.
Use Participation as your pivot table. Define relationships as
/* in User */
public function activity()
{
return $this->belongsToMany('App\Models\Activitys','participation','user_id','activity_id')->as('participation')->withPivot('time_at_activity');
}
/* in Activity */
public function user()
{
return $this->belongsToMany('App\Models\Users','participation','activity_id','user_id')->as('participation')->withPivot('time_at_activity');
}
DB schema
// App\User
public function participations()
{
return $this->hasMany('App\Participation');
}
// You may create App\Participation Model
// App\Participation
public function user()
{
return $this->belongsTo('App\User');
}
// Controller
$userParticipations = $user->participations->where('activity_id', 3);
// eager loading version
$userWithParticipations = $user->with(['participations' => function($q) { $q->where('activity_id', 3) }])->get();

Laravel Pagination / Count - Not Accurate AFTER Filters Applied

When I retrieve the count for my results via laravel's paginator ($var->total() function) OR adding on ->count() to my DB query, it returns the incorrect count. Even when there is ZERO items on the page after a filter is applied, it still shows the incorrect number. This number stays the same, it doesn't change - it's the total # of rows in my parent model. However, again - it shouldn't show the total # of rows in my parent model - it should show the rows that are available after a filter is applied.
Filtered Query in Question:
$orders = list_formstack::with(['order' => function($query) use ($checktv, $dstart, $dend, $thestatus, $pharmacy, $searchid, $searchsubid, $searchrefill, $searchgrams, $filterrep) {
$query->whereNotNull('subid');
if($checktv == "No") {
$query->whereNull('intake');
}
elseif($checktv == "Yes") {
$query->whereNotNull('intake');
}
if($dstart && $dend)
$query->whereBetween('orders.created_at', [$dstart, $dend]);
if($thestatus)
$query->where('orders.status', $thestatus);
if($pharmacy)
$query->where('pharmacy', $pharmacy);
if($searchid)
$query->where('orders.id', $searchid);
if($searchsubid)
$query->where('orders.subid', $searchsubid);
if($searchrefill)
$query->where('refill', $searchrefill);
if($searchgrams)
$query->where('grams', $searchgrams);
if($filterrep)
$query->where('salesrep', $filterrep);
}])->where(function($query) use ($prodgroup, $state, $search, $columns, $searchpatient, $searchcarrier) {
if($state)
$query->where('state', $state);
if($searchpatient)
$query->where('first', 'like', $searchpatient)->orWhere('last', $searchpatient)->orWhere('phone', $searchpatient);
if($searchcarrier)
$query->where('insurancecarrier', 'like', $searchcarrier);
})->orderBy('created_at', 'desc')->paginate(50);
Parent Model:
class list_formstack extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'formstack';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['parentid','first','last','state','insurancecarrier'];
/**
* Get the form for an order
*/
public function order()
{
return $this->hasMany('App\list_orders', 'formid', 'id')->groupBy('id');
}
}
Child / Relationship Model:
class list_orders extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'orders';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['id','status','subid','formid','salesrep','billedamt','copayamt','completed','refillcount','gramsleft','billedamt','copayamt','salesrep','intake','created_at'];
public function formstack()
{
return $this->belongsTo('App\list_formstack', 'orderid');
}
}
So as you can see in my Eloquent query, I filter out results based on whether a user chooses that filter or not. So if I filter out "thestatus" to one specific status, it shows the correct results for only that status HOWEVER the count DOES NOT change.
I even tried to throw the results into a collection:
$count = collect($orders);
$count->count();
No luck - same incorrect number.
Any insight on this? What am I doing wrong? Thanks in advance!
EDIT: I should note that using Laravel's Query Builder (NO Eloquent ORM Models) results in the pagination count working correctly.
Parent Model DB Table
Parent Model Table Structure
Child Model DB Table
Child Model Table Structure

Yii 2: Unable to create a relation with a condition about the related table

Simplifing, I've two tables:
Product: id, name
Datasheet: id, product_id
Where product_id points to products.id. Each Product could have 0 or 1 Datasheet.
Into my Product class (wich extends ActiveQuery) I've created this relation
/**
* #return \yii\db\ActiveQuery
*/
public function getDatasheet()
{
return $this->hasOne(Datasheet::className(), ['product_id' => 'id']);
}
I'm able now to query in this way
$products_without_datasheet = Product::find()
->with('datasheet')
->all();
What I really need is to retrieve only the products without the datasheet.
I'd like to create a 'scope' (like in yii 1) to be able to reuse the resulting condition datasheet.id IS NULL because this situation has a lot of variants and will be used all around the app.
I'm not able to understand how to create a relation with an added filter, something like getWithoutDatasheet() to be used as
Product::find()->with('withoutDatasheet')->all();
or
Product::find()->withoutDatasheet()->all();
Is it possible? And how?
You need create ActiveQuery for Product. See how gii generated ActiveRecord with ActiveQuery.
In Product:
/**
* #inheritdoc
* #return ProductQuery the active query used by this AR class.
*/
public static function find()
{
return new ProductQuery(get_called_class());
}
In ProductQuery:
public function withoutDatasheet()
{
$this->with('datasheet');
return $this;
}
Usage:
Product::find()->withoutDatasheet()->all();
To retrieve only the products without the datasheet you can do it like this:
$productsWithDatasheet = Datasheet::find()
->select('product_id')
->distinct()
->asArray()
->all();
$productIdsWithDatasheet = ArrayHelper::getColumn($productsWithDatasheet, 'product_id');
$productsWithoutDatasheet = Product::find()
->where(['not in', 'id', $productIdsWithDatasheet ])
->all();

Laravel Nested Eager Loading with Constraints

I have a Question Eloquent Model, a Course Eloquent Model, a University Eloquent Model. A One to Many relationship exists between the University and the Course. A Many to Many relationship exists between the Question and the Course. The Three models are shown below:
Question Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Question extends Model
{
/**
* The database table that the Model uses
* #var string
*/
protected $table = "questions";
/**
* The fields that are mass assignable
* #var array
*/
protected $fillable = ['title','body','images','quality_score','deactivated','creator_id','reviewer_id'];
/**
* Images is stored as serialized json.
* So we cast it to a PHP array.
* See: http://laravel.com/docs/5.1/eloquent-mutators#attribute-casting
*/
protected $casts = [
'images' => 'array',
];
public function courses(){
return $this->belongsToMany('App\Course');
}
}
Course Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Course extends Model
{
/**
* The database table used by the model
* #var string
*/
protected $table = "courses";
/**
* The fields that can be mass assigned
* #var array
*/
protected $fillable = ['name', 'instructor', 'acronym', 'university_id', 'creator_id', 'reviewer_id'];
/**
* There exists a many to one relationship between the Course and User
* This user is the creator of the course
*
* #method void
*
*/
public function creator(){
return $this->hasOne('App\User','creator_id');
}
/**
* There exists a many to one relationship between the Course and User
* This user is the reviewer of the course
* The reviewer of the Course will always be an admin
* If an Admin is the creator, then the reviewer is also the same admin
*
* #method void
*/
public function reviewer(){
return $this->hasOne('App\User','reviewer_id');
}
/**
* There exists a one to many relationship between the University and the Course
* This university is where the course is held
* Courses may float i.e. not be associated to any university
*
* #method void
*/
public function university(){
return $this->belongsTo('App\University');
}
/**
* This method is an accessor. It automatically changes the acronym to be all capitals
* regardless of how it is stored in the database.
* See: http://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
* #param $value (String from Database)
* #return string (Capitalized String)
*/
public function getAcronymAttribute($value){
return strtoupper($value);
}
}
University Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class University extends Model
{
/**
* The database table used by the model
* #var string
*/
protected $table = "universities";
/**
* The fields that can be mass assigned
* name = Name of the University (Example: University of Illinois at Urbana Champaign)
* acronym = Acronym of the University (Example: UIUC)
* creator_id = Id of User that created the University
* reviewer_id = Id of User that reviewed and approved the University
*
* Universities will not be displayed to users without admin role unless they have been reviewed.
*
* #var array
*/
protected $fillable = ['name','acronym','creator_id','reviewer_id'];
/**
* This method is an accessor. It automatically changes the acronym to be all capitals
* regardless of how it is stored in the database.
* See: http://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
* #param $value (String from Database)
* #return string (Capitalized String)
*/
public function getAcronymAttribute($value){
return strtoupper($value);
}
}
On my home page I am showing a list of questions and allow filters for course and university. The controller method is shown here:
public function getHome(Request $request){
/**
* Eager Load with Course and University
*/
$questions = Question::with('courses.university')->get();
/*
* Filter Questions to remove unwanted entries based on course id
*/
if($request->has('course_id') && $request->input('course_id') != -1){
$questions = $questions->filter(function($question) use ($request){
foreach($question->courses as $course){
if ($course->id == $request->input('course_id')){
return true;
}
}
});
}
/*
* Filter Questions to remove unwanted entries based on university id
*/
if($request->has('university_id') && $request->input('university_id') != -1){
$questions = $questions->filter(function($question) use ($request){
foreach($question->courses as $course){
if ($course->university->id == $request->input('university_id')){
return true;
}
}
});
}
/*
* Return the Welcome View with Pagination on the Questions Displayed
* List of Courses and List of Universities
*/
return view('welcome',[
'questions' => $questions,
'courses' => Course::all(),
'universities' => University::all(),
'selected_university_id' => $request->input('university_id',-1),
'selected_course_id' => $request->input('course_id',-1)
]);
}
What I am doing above is returning all the questions from the database and them combing them through to remove all the ones that don't match the filters. This is obviously quite inefficient. I want to use Nested Eager Loading with constraints except I am having a lot of trouble defining what the constraint would look like. Further, I want to use server side paginate to make the client experience better on lower speed internet connections.
Here is one of my attempts:
$questions = Question::with(['courses.university' => function($query) use ($request){
if($request->has('university_id') && $request->input('university_id') != -1) {
$query->where('id', $request->input('university_id'));
}
if($request->has('course_id') && $request->input('course_id') != -1){
$query->where('courses.id',$request->input('course_id'));
}
}])->paginate(10);
This works fine when I don't have any filters.
When I do have a university_id defined, I get the error: Trying to get property of non-object (View: /var/www/testing.com/resources/views/welcome.blade.php)
When I do have a course_id defined, I get the error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'courses.id' in 'where clause' (SQL: select * from universities where universities.id in (1, 2) and courses.id = 1)
I expected the error when I have course_id defined (because I took a blind stab at the first argument of the $query->where method.
I am looking for help in defining the Nested Eager Loading Constraints.
I found the solution in a medium article. The solution works for later versions of laravel as it uses whereHas.
// If you want to put the constraint on the second relation
$questions = Question::with(['courses' => function($query) use($request){
return $query->whereHas('university', function($inner_query) use($request){
return $inner_query->where('id', $request->input('university_id'));
});
}, 'courses.university'])->paginate(10);
For your case, a simple whereHas ought to do the trick.
$questions = Question::whereHas('courses', function($query) use ($request){
return $query->where('university_id', $request->input('university_id'));
})->with(['courses.university'])->paginate(10);
I would also recommend using when clauses to reduce the amount of code.
$questions = Question::when(($request->has('course_id') && $request->input('course_id') != -1), function ($query) use($request){
return $query->where('course_id', $request->input('course_id'));
})->when($request->has('university_id') && $request->input('university_id') != -1, function ($outer_query) use($request){
return $outer_query->whereHas('courses', function($query) use($request){
return $query->where('university_id', $request->input('university_id'));
})->with(['courses.university']);
})->with(['courses.university'])->paginate(10);

Categories