Unexpected result on relationship filtering using multiple Laravel scopes









up vote
1
down vote

favorite
1












I'm using Laravel 5.6 and I'm trying to filter a relationship inside my User model. A user can attend courses, which has points bound to them.



Users can earn these points by attending the courses. This is a BelongsToMany relationship.



I'm tried creating a scope in this User model that would only include the attended courses in a range of years.



/**
* Retrieves the courses which the user has attended
*/
public function attendedCourses()

return $this->belongsToMany(Course::class, 'course_attendees');


/**
* Searches the user model
*
* @param IlluminateDatabaseEloquentBuilder $builder
* @param array $years
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeAttendedCoursesInYears(Builder $builder, array $years)

# Filter on the years
$callback = function($q) use ($years)
foreach ($years as $year)
$q->year($year);

;

return $builder->with(['attendedCourses' => $callback]);



In my Course model, I have a scope that filters on the year that course was in.



public function scopeYear(Builder $query, int $year)

return $query->whereYear('end_date', $year);



With this attendedCoursesInYears scope I hoped I could then calculate the amount of points for each user by summing up the course points, using other scopes on the Course model.



public function scopeExternal(Builder $query, bool $flag = true)

$categoryIsExternal = function($query) use ($flag)
$query->external($flag);
;

return $query->whereHas('category', $categoryIsExternal);



In my CourseCategory modal, the scope looks like this:



/**
* Scope a query to only include external categories.
*
* @param IlluminateDatabaseEloquentBuilder $query
*
* @param bool $flag
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeExternal(Builder $query, $flag = true)

return $query->where('type', '=', $flag ? 'Extern' : 'Intern');



For calculating this, I tried doing something like this.



# Retrieve all the active users
$users = User::all()->attendedCoursesInYears($years);
$test = $users->find(123);

# Calculate the points
$test->attendedCourses()->external(false)->sum('points');


This however returned the total sum of all courses.



I noticed when just calling $test->attendedCourses it retrieves the filtered collection. The problem with this is that I can't apply the scopes on this.



Questions



  • How does it come that it will not sum the filtered collection?

  • How can I make it so that it will filter this collection accordingly?









share|improve this question























  • Can you show us the external scope of your Category?
    – Chin Leung
    yesterday











  • @ChinLeung Updated my question.
    – Bas
    yesterday











  • I just tried to reproduce it locally but I've been getting the correct answer for both external true and false. Try to do the following: $test->attendedCourses()->count(), $test->attendedCourses()->external(false)->count() and $test->attendedCourses()->external(true)->count(), do the numbers add up correctly?
    – Chin Leung
    yesterday










  • @ChinLeung Nope. The numbers add up correctly from the external scope. But but the $test->attendedCourses()->count() returns everything, not using the attendedCoursesInYears scope for some reason.
    – Bas
    yesterday










  • Ah now I understand what you mean... Well your attendedCoursesInYears is filtering the users that has taken a course in the years X-Y, but it's not filtering the courses by the year it's taken. The scope should be done on your pivot table instead.
    – Chin Leung
    yesterday














up vote
1
down vote

favorite
1












I'm using Laravel 5.6 and I'm trying to filter a relationship inside my User model. A user can attend courses, which has points bound to them.



Users can earn these points by attending the courses. This is a BelongsToMany relationship.



I'm tried creating a scope in this User model that would only include the attended courses in a range of years.



/**
* Retrieves the courses which the user has attended
*/
public function attendedCourses()

return $this->belongsToMany(Course::class, 'course_attendees');


/**
* Searches the user model
*
* @param IlluminateDatabaseEloquentBuilder $builder
* @param array $years
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeAttendedCoursesInYears(Builder $builder, array $years)

# Filter on the years
$callback = function($q) use ($years)
foreach ($years as $year)
$q->year($year);

;

return $builder->with(['attendedCourses' => $callback]);



In my Course model, I have a scope that filters on the year that course was in.



public function scopeYear(Builder $query, int $year)

return $query->whereYear('end_date', $year);



With this attendedCoursesInYears scope I hoped I could then calculate the amount of points for each user by summing up the course points, using other scopes on the Course model.



public function scopeExternal(Builder $query, bool $flag = true)

$categoryIsExternal = function($query) use ($flag)
$query->external($flag);
;

return $query->whereHas('category', $categoryIsExternal);



In my CourseCategory modal, the scope looks like this:



/**
* Scope a query to only include external categories.
*
* @param IlluminateDatabaseEloquentBuilder $query
*
* @param bool $flag
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeExternal(Builder $query, $flag = true)

return $query->where('type', '=', $flag ? 'Extern' : 'Intern');



For calculating this, I tried doing something like this.



# Retrieve all the active users
$users = User::all()->attendedCoursesInYears($years);
$test = $users->find(123);

# Calculate the points
$test->attendedCourses()->external(false)->sum('points');


This however returned the total sum of all courses.



I noticed when just calling $test->attendedCourses it retrieves the filtered collection. The problem with this is that I can't apply the scopes on this.



Questions



  • How does it come that it will not sum the filtered collection?

  • How can I make it so that it will filter this collection accordingly?









share|improve this question























  • Can you show us the external scope of your Category?
    – Chin Leung
    yesterday











  • @ChinLeung Updated my question.
    – Bas
    yesterday











  • I just tried to reproduce it locally but I've been getting the correct answer for both external true and false. Try to do the following: $test->attendedCourses()->count(), $test->attendedCourses()->external(false)->count() and $test->attendedCourses()->external(true)->count(), do the numbers add up correctly?
    – Chin Leung
    yesterday










  • @ChinLeung Nope. The numbers add up correctly from the external scope. But but the $test->attendedCourses()->count() returns everything, not using the attendedCoursesInYears scope for some reason.
    – Bas
    yesterday










  • Ah now I understand what you mean... Well your attendedCoursesInYears is filtering the users that has taken a course in the years X-Y, but it's not filtering the courses by the year it's taken. The scope should be done on your pivot table instead.
    – Chin Leung
    yesterday












up vote
1
down vote

favorite
1









up vote
1
down vote

favorite
1






1





I'm using Laravel 5.6 and I'm trying to filter a relationship inside my User model. A user can attend courses, which has points bound to them.



Users can earn these points by attending the courses. This is a BelongsToMany relationship.



I'm tried creating a scope in this User model that would only include the attended courses in a range of years.



/**
* Retrieves the courses which the user has attended
*/
public function attendedCourses()

return $this->belongsToMany(Course::class, 'course_attendees');


/**
* Searches the user model
*
* @param IlluminateDatabaseEloquentBuilder $builder
* @param array $years
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeAttendedCoursesInYears(Builder $builder, array $years)

# Filter on the years
$callback = function($q) use ($years)
foreach ($years as $year)
$q->year($year);

;

return $builder->with(['attendedCourses' => $callback]);



In my Course model, I have a scope that filters on the year that course was in.



public function scopeYear(Builder $query, int $year)

return $query->whereYear('end_date', $year);



With this attendedCoursesInYears scope I hoped I could then calculate the amount of points for each user by summing up the course points, using other scopes on the Course model.



public function scopeExternal(Builder $query, bool $flag = true)

$categoryIsExternal = function($query) use ($flag)
$query->external($flag);
;

return $query->whereHas('category', $categoryIsExternal);



In my CourseCategory modal, the scope looks like this:



/**
* Scope a query to only include external categories.
*
* @param IlluminateDatabaseEloquentBuilder $query
*
* @param bool $flag
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeExternal(Builder $query, $flag = true)

return $query->where('type', '=', $flag ? 'Extern' : 'Intern');



For calculating this, I tried doing something like this.



# Retrieve all the active users
$users = User::all()->attendedCoursesInYears($years);
$test = $users->find(123);

# Calculate the points
$test->attendedCourses()->external(false)->sum('points');


This however returned the total sum of all courses.



I noticed when just calling $test->attendedCourses it retrieves the filtered collection. The problem with this is that I can't apply the scopes on this.



Questions



  • How does it come that it will not sum the filtered collection?

  • How can I make it so that it will filter this collection accordingly?









share|improve this question















I'm using Laravel 5.6 and I'm trying to filter a relationship inside my User model. A user can attend courses, which has points bound to them.



Users can earn these points by attending the courses. This is a BelongsToMany relationship.



I'm tried creating a scope in this User model that would only include the attended courses in a range of years.



/**
* Retrieves the courses which the user has attended
*/
public function attendedCourses()

return $this->belongsToMany(Course::class, 'course_attendees');


/**
* Searches the user model
*
* @param IlluminateDatabaseEloquentBuilder $builder
* @param array $years
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeAttendedCoursesInYears(Builder $builder, array $years)

# Filter on the years
$callback = function($q) use ($years)
foreach ($years as $year)
$q->year($year);

;

return $builder->with(['attendedCourses' => $callback]);



In my Course model, I have a scope that filters on the year that course was in.



public function scopeYear(Builder $query, int $year)

return $query->whereYear('end_date', $year);



With this attendedCoursesInYears scope I hoped I could then calculate the amount of points for each user by summing up the course points, using other scopes on the Course model.



public function scopeExternal(Builder $query, bool $flag = true)

$categoryIsExternal = function($query) use ($flag)
$query->external($flag);
;

return $query->whereHas('category', $categoryIsExternal);



In my CourseCategory modal, the scope looks like this:



/**
* Scope a query to only include external categories.
*
* @param IlluminateDatabaseEloquentBuilder $query
*
* @param bool $flag
*
* @return IlluminateDatabaseEloquentBuilder
*/
public function scopeExternal(Builder $query, $flag = true)

return $query->where('type', '=', $flag ? 'Extern' : 'Intern');



For calculating this, I tried doing something like this.



# Retrieve all the active users
$users = User::all()->attendedCoursesInYears($years);
$test = $users->find(123);

# Calculate the points
$test->attendedCourses()->external(false)->sum('points');


This however returned the total sum of all courses.



I noticed when just calling $test->attendedCourses it retrieves the filtered collection. The problem with this is that I can't apply the scopes on this.



Questions



  • How does it come that it will not sum the filtered collection?

  • How can I make it so that it will filter this collection accordingly?






php laravel






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited yesterday

























asked yesterday









Bas

1,1021134




1,1021134











  • Can you show us the external scope of your Category?
    – Chin Leung
    yesterday











  • @ChinLeung Updated my question.
    – Bas
    yesterday











  • I just tried to reproduce it locally but I've been getting the correct answer for both external true and false. Try to do the following: $test->attendedCourses()->count(), $test->attendedCourses()->external(false)->count() and $test->attendedCourses()->external(true)->count(), do the numbers add up correctly?
    – Chin Leung
    yesterday










  • @ChinLeung Nope. The numbers add up correctly from the external scope. But but the $test->attendedCourses()->count() returns everything, not using the attendedCoursesInYears scope for some reason.
    – Bas
    yesterday










  • Ah now I understand what you mean... Well your attendedCoursesInYears is filtering the users that has taken a course in the years X-Y, but it's not filtering the courses by the year it's taken. The scope should be done on your pivot table instead.
    – Chin Leung
    yesterday
















  • Can you show us the external scope of your Category?
    – Chin Leung
    yesterday











  • @ChinLeung Updated my question.
    – Bas
    yesterday











  • I just tried to reproduce it locally but I've been getting the correct answer for both external true and false. Try to do the following: $test->attendedCourses()->count(), $test->attendedCourses()->external(false)->count() and $test->attendedCourses()->external(true)->count(), do the numbers add up correctly?
    – Chin Leung
    yesterday










  • @ChinLeung Nope. The numbers add up correctly from the external scope. But but the $test->attendedCourses()->count() returns everything, not using the attendedCoursesInYears scope for some reason.
    – Bas
    yesterday










  • Ah now I understand what you mean... Well your attendedCoursesInYears is filtering the users that has taken a course in the years X-Y, but it's not filtering the courses by the year it's taken. The scope should be done on your pivot table instead.
    – Chin Leung
    yesterday















Can you show us the external scope of your Category?
– Chin Leung
yesterday





Can you show us the external scope of your Category?
– Chin Leung
yesterday













@ChinLeung Updated my question.
– Bas
yesterday





@ChinLeung Updated my question.
– Bas
yesterday













I just tried to reproduce it locally but I've been getting the correct answer for both external true and false. Try to do the following: $test->attendedCourses()->count(), $test->attendedCourses()->external(false)->count() and $test->attendedCourses()->external(true)->count(), do the numbers add up correctly?
– Chin Leung
yesterday




I just tried to reproduce it locally but I've been getting the correct answer for both external true and false. Try to do the following: $test->attendedCourses()->count(), $test->attendedCourses()->external(false)->count() and $test->attendedCourses()->external(true)->count(), do the numbers add up correctly?
– Chin Leung
yesterday












@ChinLeung Nope. The numbers add up correctly from the external scope. But but the $test->attendedCourses()->count() returns everything, not using the attendedCoursesInYears scope for some reason.
– Bas
yesterday




@ChinLeung Nope. The numbers add up correctly from the external scope. But but the $test->attendedCourses()->count() returns everything, not using the attendedCoursesInYears scope for some reason.
– Bas
yesterday












Ah now I understand what you mean... Well your attendedCoursesInYears is filtering the users that has taken a course in the years X-Y, but it's not filtering the courses by the year it's taken. The scope should be done on your pivot table instead.
– Chin Leung
yesterday




Ah now I understand what you mean... Well your attendedCoursesInYears is filtering the users that has taken a course in the years X-Y, but it's not filtering the courses by the year it's taken. The scope should be done on your pivot table instead.
– Chin Leung
yesterday












1 Answer
1






active

oldest

votes

















up vote
1
down vote













The problem is that your scopeAttendedCoursesInYears method in your User class is to scope out the users who took the courses in the specific years and not scope out the courses that were taken during the years.



To do what you want, you could add a parameter to your relation to filter out the pivot table instead of your users table:



public function attendedCourses(array $years = null)

$query = $this->belongsToMany(Course::class, 'course_user');

if ($years)
$query->whereRaw('YEAR(end_date) IN (?)', [
'years' => implode(',', $years)
]);


return $query;



Then you could achieve your result like this:



$user = User::find(123);

$user->attendedCourses($years)->external(false)->sum('points');





share|improve this answer




















  • Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
    – Bas
    yesterday











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53224247%2funexpected-result-on-relationship-filtering-using-multiple-laravel-scopes%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
1
down vote













The problem is that your scopeAttendedCoursesInYears method in your User class is to scope out the users who took the courses in the specific years and not scope out the courses that were taken during the years.



To do what you want, you could add a parameter to your relation to filter out the pivot table instead of your users table:



public function attendedCourses(array $years = null)

$query = $this->belongsToMany(Course::class, 'course_user');

if ($years)
$query->whereRaw('YEAR(end_date) IN (?)', [
'years' => implode(',', $years)
]);


return $query;



Then you could achieve your result like this:



$user = User::find(123);

$user->attendedCourses($years)->external(false)->sum('points');





share|improve this answer




















  • Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
    – Bas
    yesterday















up vote
1
down vote













The problem is that your scopeAttendedCoursesInYears method in your User class is to scope out the users who took the courses in the specific years and not scope out the courses that were taken during the years.



To do what you want, you could add a parameter to your relation to filter out the pivot table instead of your users table:



public function attendedCourses(array $years = null)

$query = $this->belongsToMany(Course::class, 'course_user');

if ($years)
$query->whereRaw('YEAR(end_date) IN (?)', [
'years' => implode(',', $years)
]);


return $query;



Then you could achieve your result like this:



$user = User::find(123);

$user->attendedCourses($years)->external(false)->sum('points');





share|improve this answer




















  • Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
    – Bas
    yesterday













up vote
1
down vote










up vote
1
down vote









The problem is that your scopeAttendedCoursesInYears method in your User class is to scope out the users who took the courses in the specific years and not scope out the courses that were taken during the years.



To do what you want, you could add a parameter to your relation to filter out the pivot table instead of your users table:



public function attendedCourses(array $years = null)

$query = $this->belongsToMany(Course::class, 'course_user');

if ($years)
$query->whereRaw('YEAR(end_date) IN (?)', [
'years' => implode(',', $years)
]);


return $query;



Then you could achieve your result like this:



$user = User::find(123);

$user->attendedCourses($years)->external(false)->sum('points');





share|improve this answer












The problem is that your scopeAttendedCoursesInYears method in your User class is to scope out the users who took the courses in the specific years and not scope out the courses that were taken during the years.



To do what you want, you could add a parameter to your relation to filter out the pivot table instead of your users table:



public function attendedCourses(array $years = null)

$query = $this->belongsToMany(Course::class, 'course_user');

if ($years)
$query->whereRaw('YEAR(end_date) IN (?)', [
'years' => implode(',', $years)
]);


return $query;



Then you could achieve your result like this:



$user = User::find(123);

$user->attendedCourses($years)->external(false)->sum('points');






share|improve this answer












share|improve this answer



share|improve this answer










answered yesterday









Chin Leung

6,4532831




6,4532831











  • Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
    – Bas
    yesterday

















  • Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
    – Bas
    yesterday
















Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
– Bas
yesterday





Thanks, but is this the only way? I'd like to have the attendedCourses filtered before I sum the points. Something like using a scope in my controller. This because I want the fields as columns so I can order and access them easily without too many if statements in a view.
– Bas
yesterday


















 

draft saved


draft discarded















































 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53224247%2funexpected-result-on-relationship-filtering-using-multiple-laravel-scopes%23new-answer', 'question_page');

);

Post as a guest














































































Popular posts from this blog

How to how show current date and time by default on contact form 7 in WordPress without taking input from user in datetimepicker

Syphilis

Darth Vader #20