I have Clients, which have Users, which have Surveys with a many-to-many table. So user_surveys.
I'm wondering how I can count some relations deep. I would like to the count of all surveys the users have for that client
What I've tried
Client.php
public function countSurveys()
{
$employees = $this->employees;
// this returns Property [surveys] does not exist on this collection instance.
return $employees->surveys->count();
// Method whereHas does not exist
return $employees->whereHas('surveys')->count();
}
This my employees method, which is a subset of Users
public function employees()
{
return $this->users()->whereHas('roles', function ($q) {
$q->where('name', 'employee');
});
}
And this is the User model
namespace App\Models;
use App\LoginToken;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
public function surveys()
{
return $this->belongsToMany(Survey::class, 'user_surveys', 'user_id', 'survey_id')
->withPivot('completed_on', 'status')
->withTimestamps();
}
public function journey()
{
return $this->belongsTo(Scan::class);
}
public function client()
{
return $this->belongsTo(Client::class);
}
}
It might be late, I might be confused and/or stupid.
Looking forward to your responses!
Another approach would be
$user = App\User::find(1);
return $user->surveys()->count();
Or try
$users = App\User::withCount('surveys')->get();
foreach($users as $user) {
$user->surveys_count;
}
Try this:
return $this->employees()->withCount('surveys')->get();
There is no native relationship for this case.
I created a HasManyThrough relationship with support for BelongsToMany: Repository on GitHub
After the installation, you can use it like this:
class Client extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function surveys() {
return $this->hasManyDeep(Survey::class, [User::class, 'user_surveys']);
}
}
$count = $client->surveys()->count();
Related
I could not find this in the laravel docs on aggregate relationships
I was able to do something like this
private function refreshUsers()
{
$this->users = User::withSum(['taskTimeSessions'=> function ($query) {
$query->whereMonth('created_at',$this->month)
->where('is_reconciled',1);
}],'session_duration_in_seconds')
->get();
}
But now I am trying to query what is the total time a Sprint has or at the very least what the individual tasks inside a sprint have so that I can just sum the total of those somehow.
Sprint has many SprintTasks (pivot table)
SprintTask belongs to one Task
Task has many TaskTimeSessions
So I am trying to go find the total time of the TaskTimeSessions
Sprint::with([
'sprintTasks.task'=> function ($query) {
$query->withSum('taskTimeSessions','session_duration_in_seconds');
}])
->get();
I am not getting any errors, but not finding the result anywhere when dd
I thought i would get lucky and have something like this work
->withSum('sprintTasks.task.taskTimeSessions', 'session_duration_in_seconds')
But I am getting this error
Call to undefined method App\Models\Sprint::sprintTasks.task()
If anyone can help me out with some guidance on how to go about this, even if it doesn't include withSum it would be much appreciated.
As requested, these are the models.
// Sprint
public function sprintTasks()
{
return $this->hasMany(SprintTask::class, 'sprint_id');
}
// SprintTask
protected $fillable = [
'sprint_id',
'task_id',
'is_completed'
];
public function task()
{
return $this->belongsTo(Task::class,'task_id');
}
public function sprint()
{
return $this->belongsTo(Task::class,'sprint_id');
}
// Task
public function taskTimeSessions()
{
return $this->hasMany(TaskTimeSession::class, 'task_id');
}
// TaskTimeSessions
protected $fillable = [
'task_id',
'session_duration_in_seconds'
];
public function task()
{
return $this->belongsTo(Task::class,'task_id');
}
Is it possible to abstract this into the model as like
public function totalTaskTime() {
// using the relationship stuff to figure out the math and return it?
}
Looking for any advice on what the best approach is to do this.
Right now I am literally doing this in the blade and seems very bad
#php
$timeTracked = 0;
foreach ($sprint->sprintTasks as $sprintTask) {
$timeTracked += $sprintTask->task->time_tracked_in_seconds;
}
#endphp
You have a many to many relation between sprint and task
For that you can setup a direct relation belongsToMany with sprint_tasks as the pivot table
// Sprint
public function sprintTasks()
{
return $this->hasMany(SprintTask::class, 'sprint_id');
}
public function tasks()
{
return $this->belongsToMany(Task::class, 'sprint_tasks', 'sprint_id', 'task_id')->withPivot('is_completed');
}
Now you can use that relation to query your needs
Sprint::with(['tasks'=> function ($query) {
$query->withSum('taskTimeSessions','session_duration_in_seconds');
}])
->get();
There is a good package for Laravel for complex relationships - eloquent-has-many-deep. You can use it to build relationships through an unlimited number of tables.
composer require staudenmeir/eloquent-has-many-deep:"^1.7"
Sprint.php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Staudenmeir\EloquentHasManyDeep\HasManyDeep;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
class Sprint extends Model
{
use HasRelationships;
public function tasks(): BelongsToMany
{
return $this->belongsToMany(Task::class, 'sprint_tasks');
}
public function taskTimeSessions(): HasManyDeep
{
return $this->hasManyDeepFromRelations($this->tasks(), (new Task())->taskTimeSessions());
}
}
Task.php
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Task extends Model
{
use HasFactory;
public function taskTimeSessions(): HasMany
{
return $this->hasMany(TaskTimeSession::class);
}
}
Result:
$sprints = Sprint::withSum('taskTimeSessions', 'session_duration_in_seconds')->get();
I have the following model:
class EmailAddress extends Model
{
public function scopePrimary($query)
{
return $query->firstWhere('is_primary', true);
}
}
class User extends Model
{
public function emailAddresses()
{
return $this->hasMany(EmailAddress::class);
}
}
echo $user->emailAddresses()->primary()->get();
I would expect Laravel to return a model since firstWhere() essentially does LIMIT 1 in the query but instead I always get a collection with one model. Am I doing something wrong? How to fix that?
Thanks in advance!
Maybe you can utilize the hasOne of many relationship:
class User extends Model
{
public function primaryEmailAddress()
{
$this->hasOne(EmailAddresses::class)->ofMany([], function ($query) {
$query->where('is_primary', true);
});
}
}
I'am beginner in Laravel. I have project in Laravel 5.8.
I have User model:
class User extends Authenticatable implements MustVerifyEmail
{
use Notifiable;
use psCMS\Presenters\UserPresenter;
use scopeActiveTrait;
public static $roles = [];
public $dates = ['last_activity'];
// ...
public function scopeHistory()
{
return $this->hasMany('App\UserLoginHistory');
}
// ...
}
and UserLoginHistory:
class UserLoginHistory extends Model
{
protected $quarded = ['id'];
public $timestamps = false;
protected $fillable = ['user_id', 'date_time', 'ip'];
public function user()
{
return $this->belongsTo('App\User');
}
}
I want show user login history by this code:
User::history()->where('id', $idAdmin)->orderBy('id', 'desc')->paginate(25);
but it's not working.
This function not working - I haven't got results.
How can I fixed it?
First of all, you are defining your relationship as a scope (prefixing the relationship with the scope keyword). Try updating your model relationship to this:
public function history()
{
return $this->hasMany('App\UserLoginHistory');
}
Then, given your query, it seems that you want to get all the UserLoginHistory
records for a given User. You could accomplish this in two ways (at least).
From the UserLoginHistory model itself, constraining the query by the foreign key value:
$userId = auth()->id(); // get the user ID here.
$results = UserLoginHistory::where('user_id', $userId)->paginate(15);
// ^^^^^^^ your FK column name
From the User model using your defined relationship:
$userId = auth()->id(); // get the user ID here.
$results = User::find($userId)->history;
The downside of the second approach is that you'll need to paginate the results manually.
in your User model you should define your relation by this way :
public function history()
{
return $this->hasMany('App\UserLoginHistory');
}
then if you would like to select with history model you can do that with WhereHas() method :
User::whereHas(['history'=>function($q) use ($idAdmin) {
$q->where('id',$idAdmin)
}])->orderBy('id', 'desc')->paginate(25);
You must be do this changes
public function history()
{
return $this->hasMany('App\UserLoginHistory');
}
usage
$user = User::find($idAdmin);
$userHistories = $user->history()->latest()->paginate(25);
or get user with all history
User::with('history')->find($idAdmin);
// Post model
namespace App;
use App\User;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function categories()
{
return $this->belongsToMany('App\Category')->withTimestamps();
}
}
// Category model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
public function posts()
{
return $this->belongsToMany('App\Post')->withTimestamps();
}
}
So I have the following relational structure:
User - UserTask - Task
My pivot model (UserTask) needs to access a property from the Task model.
So I have an accessor function in my UserTask model in which I need to access the Task->document_upload_required property.
Any one know how I can access this?
Note:
I cannot set the accessor in the parent model because I need to use the Media functions set on the pivot model.
Here is how I've defined the relationships:
Task:
class Task extends Model
{
public function users()
{
return $this->belongsToMany(User::class, 'user_task')->using('App\Models\UserTask')->withPivot('completed');
}
}
UserTask:
class UserTask extends Pivot implements HasMedia
{
use HasMediaTrait;
public function getCompletedAttribute()
{
return "Need to access parent attribute here";
}
}
User:
class User extends Authenticatable
{
use HasApiTokens, Notifiable, Billable;
public function tasks()
{
return $this->belongsToMany(Task::class);
}
}
UPDATE:
Here is my pivot (UserTask) table that I am trying to fetch data from:
Here is the function in my pivot model:
public function isComplete() {
if($this->task->document_upload_required) {
return $this->getMedia()->isEmpty() && $this->completed;
} else {
return $this->completed;
}
}
public function getCompletedAttribute()
{
return $this->isComplete();
}
Here is how I make the call to join the models:
$sections = $this->model->with(['subsections.tasks.users' => function($q){
$q->where('users.id', '=', Auth::id());
}])->where('parent', NULL)->doesntHave('assessments')->sorted()->get();
You need to declare the relationship first and then access its property, like this:
public function task()
{
return $this->belongsTo(Task::class);
}
public function getCompletedAttribute()
{
return $this->task->completed;
}
EDIT:
$collection = $this->model->with(['subsections.tasks.users' => function($q){
$q->where('users.id', '=', Auth::id());
}])->where('parent', NULL)->doesntHave('assessments')->sorted()->get();
$collection->each(function($subsection) {
$subsection->tasks->each(function($task) {
$task->users->each(function($user) {
dump($user->pivot->completed);
});
});
});
dd();
I have the following models.
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment');
}
}
class Comment extends Eloquent {
public function user() {
return $this->belongsTo('User');
}
}
For the sake of this example, a user could have 1,000s of comments. I am trying to limit them to just the first 10. I have tried doing it in the User model via
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment')->take(10);
}
}
and via UserController via closures
$users = User::where('post_id', $post_id)->with([
'comments' => function($q) {
$q->take(10);
}
]);
Both methods seem to only work on the first record of the result. Is there a better way to handle this?