带有约束的 Laravel 嵌套急切加载

Posted

技术标签:

【中文标题】带有约束的 Laravel 嵌套急切加载【英文标题】:Laravel Nested Eager Loading with Constraints 【发布时间】:2015-11-17 09:27:05 【问题描述】:

我有一个 Question Eloquent 模型,一个 Course Eloquent 模型,一个 University Eloquent 模型。大学和课程之间存在一对多的关系。问题和课程之间存在多对多关系。三种型号如下图:

问题模型

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');
    

课程模式

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);
    

大学模式

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);
    


在我的主页上,我显示了问题列表,并允许筛选课程和大学。控制器方法如下所示:

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)
        ]);

    

我在上面所做的是从数据库中返回所有问题,并将它们组合起来以删除所有与过滤器不匹配的问题。这显然是非常低效的。我想使用带有约束的嵌套急切加载,除非我在定义约束的外观时遇到了很多麻烦。此外,我想使用服务器端分页来使客户端在低速互联网连接上的体验更好。

这是我的尝试之一:

$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);

当我没有任何过滤器时,这可以正常工作。

当我确实定义了 university_id 时,我收到错误:尝试获取非对象的属性(查看:/var/www/testing.com/resources/views/welcome.blade.php)

当我确实定义了 course_id 时,我收到错误:SQLSTATE[42S22]: Column not found: 1054 Unknown column 'courses.id' in 'where Clause' (SQL: select * from universities where @987654327 @.id in (1, 2) 和 courses.id = 1)

当我定义 course_id 时,我预计会出现错误(因为我对 $query->where 方法的第一个参数进行了盲目测试。

我正在寻求有关定义嵌套急切加载约束的帮助。

【问题讨论】:

您想要所选大学的课程。如果需要,您可以从查询中完全删除 University。试一下将$query->where('id', $request->input('university_id')); 更改为$query->where('university_id', $request->input('university_id')); 【参考方案1】:

我在一篇中型文章中找到了解决方案。该解决方案适用于更高版本的 laravel,因为它使用 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);

对于你的情况,一个简单的 whereHas 应该可以解决问题。

$questions = Question::whereHas('courses', function($query) use ($request)
  return $query->where('university_id', $request->input('university_id'));
)->with(['courses.university'])->paginate(10);

我还建议使用 when 子句来减少代码量。

$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);

【讨论】:

可以分享中篇文章的链接吗?

以上是关于带有约束的 Laravel 嵌套急切加载的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 约束急切加载 - 如果关系列应用限制!= 1

laravel 嵌套急切加载获取和第一个函数

如何在 Laravel 中为 ORM 急切加载添加多个键约束?

Laravel 混合获取 Eloquent 急切加载嵌套多个模型

laravel eloquent - 在嵌套急切加载的关系上不使用

使用 Laravel 将两个模型合并到一个分页查询中,并带有急切加载的关系