Php 使用多个Laravel作用域进行关系筛选时出现意外结果
我使用的是Laravel5.6,我试图过滤我的Php 使用多个Laravel作用域进行关系筛选时出现意外结果,php,laravel,eloquent,Php,Laravel,Eloquent,我使用的是Laravel5.6,我试图过滤我的用户模型中的关系。用户可以参加课程,这些课程都有分数限制 用户可以通过参加课程获得这些积分。这是一种归属关系 我尝试在这个用户模型中创建一个范围,该范围仅包括几年内参加的课程 /** * Retrieves the courses which the user has attended */ public function attendedCourses() { return $this->belongsToMany(Course:
用户
模型中的关系。用户可以参加课程,这些课程都有分数限制
用户可以通过参加课程获得这些积分。这是一种归属关系
我尝试在这个用户
模型中创建一个范围,该范围仅包括几年内参加的课程
/**
* Retrieves the courses which the user has attended
*/
public function attendedCourses()
{
return $this->belongsToMany(Course::class, 'course_attendees');
}
/**
* Searches the user model
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param array $years
*
* @return \Illuminate\Database\Eloquent\Builder
*/
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]);
}
在我的课程
模型中,我有一个范围,可以根据课程所在年份进行筛选
public function scopeYear(Builder $query, int $year)
{
return $query->whereYear('end_date', $year);
}
有了这个attendedcoursesineyears
scope,我希望可以通过使用course
模型上的其他作用域,将课程积分相加,计算每个用户的分数
public function scopeExternal(Builder $query, bool $flag = true)
{
$categoryIsExternal = function($query) use ($flag) {
$query->external($flag);
};
return $query->whereHas('category', $categoryIsExternal);
}
在myCourseCegory
模式中,范围如下所示:
/**
* Scope a query to only include external categories.
*
* @param \Illuminate\Database\Eloquent\Builder $query
*
* @param bool $flag
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeExternal(Builder $query, $flag = true)
{
return $query->where('type', '=', $flag ? 'Extern' : 'Intern');
}
$user = User::find(123);
$user->attendedCourses($years)->external(false)->sum('points');
为了计算这个,我试着做了这样的事情
# Retrieve all the active users
$users = User::all()->attendedCoursesInYears($years);
$test = $users->find(123);
# Calculate the points
$test->attendedCourses()->external(false)->sum('points');
然而,这返回了所有课程的总和
正如我所看到的,在这里使用范围是唯一的选择。我想使用这样的访问器从这些值创建自定义属性。这样我就可以轻松地对计算值进行排序
/**
* The users internal course points
*
* @param array $years The years to look for attended courses
*
* @return float
*/
public function getInternalPointsAttribute() : float
{
return $this->attendedCourses()->external(false)->sum('points');
}
这里唯一的问题是年份过滤器。我希望在调用访问器之前可以过滤用户集合,就像我的第一个示例一样
我做错了什么
我目前正在使用此解决方法。这看起来很糟糕,因为我重复了太多代码
/**
* @param \Illuminate\Database\Eloquent\Builder $builder
*
* @param array $years
*
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
*/
public function scopeWithPoints(Builder $builder, array $years = [])
{
# Join all columns
$builder->join('user_roles', 'users.role_id', '=', 'user_roles.id')
->leftJoin('course_attendees', 'users.id', '=', 'course_attendees.user_id');
# Join the course table for the years
$builder->leftJoin('courses', function(JoinClause $join) use ($years) {
# Join the courses table with year filters
$join->on('course_attendees.course_id', '=', 'courses.id');
# Apply the filters if available
!empty($years) and $join->whereIn(DB::raw('YEAR(courses.end_date)'), $years);
});
# Select the columns
$builder->select('users.*')->groupBy('users.id');
# Sums
$internalPoints = 'SUM(courses.points_internal)';
$externalPoints = 'SUM(courses.points_external)';
# Select the points
$builder->selectRaw('COALESCE(' . $internalPoints . ', 0) as internal_points');
$builder->selectRaw('COALESCE(' . $externalPoints . ', 0) as external_points');
$builder->selectRaw('COALESCE(' . $internalPoints . ' + ' . $externalPoints . ', 0) as total_points');
# Sum up the course points
return $builder;
}
我的数据库结构的迁移可以在这里找到
Schema::create('course_attendees', function(Blueprint $table)
{
$table->integer('id', true);
$table->integer('user_id')->index('course_attendees_users_id_fk');
$table->integer('course_id')->index('course_attendees_courses_id_fk');
$table->boolean('mijnafas');
});
Schema::create('courses', function(Blueprint $table)
{
$table->integer('id', true);
$table->string('title');
$table->string('subject');
$table->string('presenter');
$table->date('start_date')->nullable()->comment('Set to not null later');
$table->date('end_date')->nullable();
$table->decimal('points', 4)->nullable();
$table->string('location');
$table->timestamps();
});
Schema::create('users', function(Blueprint $table)
{
$table->integer('id', true);
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->timestamps();
});
Schema::table('course_attendees', function(Blueprint $table)
{
$table->foreign('course_id', 'course_attendees_courses_id_fk')->references('id')->on('courses')->onUpdate('RESTRICT')->onDelete('RESTRICT');
$table->foreign('user_id', 'course_attendees_users_id_fk')->references('id')->on('users')->onUpdate('RESTRICT')->onDelete('RESTRICT');
});
我注意到,在调用$test->attendedCourses
时,它会检索过滤后的集合。问题是我不能在上面应用作用域
问题
- 为什么它不会对过滤后的集合求和
- 我如何使其能够相应地过滤此集合
问题在于,用户类中的ScopeAttendedCoursesInyear
方法是确定在特定年份学习课程的用户范围,而不是确定在特定年份学习的课程范围
要执行所需操作,可以向关系中添加一个参数,以筛选出透视表而不是用户表:
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;
}
然后,您可以实现如下结果:
/**
* Scope a query to only include external categories.
*
* @param \Illuminate\Database\Eloquent\Builder $query
*
* @param bool $flag
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeExternal(Builder $query, $flag = true)
{
return $query->where('type', '=', $flag ? 'Extern' : 'Intern');
}
$user = User::find(123);
$user->attendedCourses($years)->external(false)->sum('points');
你能告诉我们你的分类的外部范围吗?@ChinLeung更新了我的问题。我只是试图在本地复制它,但我已经得到了外部正确和错误的正确答案。试着做以下几件事:$test->attendedcources()->count()
,$test->attendedcources()->外部(false)->计数()
和$test->attendedcources()->外部(true)->计数()
,这些数字加起来正确吗?@ChinLeung Nope。这些数字从外部范围正确相加。但是$test->attendedcoursesineyears()->count()
返回所有内容,出于某种原因没有使用attendedcoursesineyears
范围。啊,现在我明白你的意思了。。。你参加的课程Inyear是在筛选X-Y年内参加过课程的用户,但它不是按参加课程的年份筛选课程。范围应该在透视表上完成。谢谢,但这是唯一的方法吗?我想先过滤参加的课程
,然后再计算积分。类似于在我的控制器中使用作用域。这是因为我希望字段作为列,这样我就可以轻松地排序和访问它们,而不必在视图中使用太多的if语句。@Bas你是什么意思?它们将被过滤。$user->attendedCourses($years)->external(false)->sum('points')
的结果应该是用户
模型中的一个属性/列。当想要收集整个用户列表时,我希望将这些数据作为列/属性。这样,我也可以快速重新排序列表。这就是为什么我希望我能用一个作用域来完成它。@Bas我不太清楚你的数据库结构,你能包括一些迁移文件,这样我就可以在我的本地环境中重现你的结构吗?