Php 使用多个Laravel作用域进行关系筛选时出现意外结果

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:

我使用的是Laravel5.6,我试图过滤我的
用户
模型中的关系。用户可以参加课程,这些课程都有分数限制

用户可以通过参加课程获得这些积分。这是一种
归属关系

我尝试在这个
用户
模型中创建一个范围,该范围仅包括几年内参加的课程

/**
 * 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);
}
在my
CourseCegory
模式中,范围如下所示:

/**
 * 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我不太清楚你的数据库结构,你能包括一些迁移文件,这样我就可以在我的本地环境中重现你的结构吗?