Attaching a hasOne model to another Laravel/Eloque

2020-08-11 00:00发布

问题:

Background

Given we have the following two tables where type_id references a row in questionType:

question

id | type_id | description
---+---------+------------
1  | 1       | A nice question
.. | ..      | ..

questionType

id | name 
---+----------------
1  | Multiple-choice 
.. | ..

with the following Eloquent models:

class Question extends Model {
    public function type() {
        return $this->hasOne( 'QuestionType', 'id', 'type_id' );
    }
}

class QuestionType extends Model {
}

Question 1

How can I add a new question that references an existing question type without manually doing anything with ids? For example the following works but is ugly imo since I have to manually assign the corresponding question type id:

$q = new Question;
$q->type_id = 1; // Multiple-choice
$q->description = 'This is a multiple-choice question';
$q->save();

One would think there was a way to let the ORM handle the id-assignment (isn't the point to avoid stuff like this with ORMs?), something along the lines of (this does not work in Eloquent ORM):

$q = new Question;
$q->type = QuestionType.where('name', '=', 'Multiple-choice');
$q->description = 'This is a multiple-choice question';
$q->save();

Question 2

In relation to question 1, how would I go about adding a new question that references a new question type without manually doing anything with ids? Similarly I imagine something along the lines of:

$t = new QuestionType;
$t->name = 'Another type';

$q = new Question;
$q->type = $t;
$q->description = 'This is a multiple-choice question';
$q->save();

Here I'd like $q->save() to save both the new question type and question (or something similar).

The following works, but again I'm assigning the id myself which I believe the ORM should handle:

$t = new QuestionType;
$t->name = 'Another type';
$t->save();

$q = new Question;
$q->type = $t->id;
$q->description = 'This is a multiple-choice question';
$q->save();

I've tried playing with different combinations of save(), update() methods without luck. I also looked for attach() which exists on the hasMany relationships but seem to be missing in hasOne.

回答1:

First off, you misunderstood the relation you refer to.

Here's what you need:

// Question model
public function questionType()
{
  return $this->belongsTo('QuestionType', 'type_id');
}

// QuestionType model
public function questions()
{
  return $this->hasMany('Question', 'type_id');
}

then you can link them together like this:

$questionType = QuestionType::where(..)->first();

$question = new Question;
... // do something with it

// associate
$question->questionType()->associate($questionType);

// or the other way around - save new question and link to the type:
$questionType->questions()->save($question);

You can explicitly pass an id to associate as well:

$question->type_id = $someTypeId;
$question->save();

You can't do this:

$question->questionType = $someQuestionType;

for this way Eloquent handles model attributes, not relations.


Question 2:

$questionType = new QuestionType(['name' => 'multiple']);
$questionType->save();

$question = new Question([ ... some values ... ]);

// then either this way:
$questionType->questions()->save($question);

// or, again, the other way around:
$question->questionType()->associate($questionType);
$question->save();


回答2:

Answering question 1 for me both methods are good, you don't need to change anything.

Answering question 2 you should do it as you showed. ORM won't create automatically QuestionType until you use manually save method.

For example if you used code:

$t = new QuestionType;
$t->name = 'Another type';

$t2 = new QuestionType;
$t2->name = 'Another type 2';

$q = new Question;
$q->type = $t; // what here - $t2 or $t ?
$q->description = 'This is a multiple-choice question';
$q->save();

what ORM should decide? Question isn't connected to any of $t or $t2 so ORM won't decide it for you. You need to tell ORM what's the type.

I'm not an ORM/Eloquent expert but I think you expect too much from ORM. ORM shouldn't guess what you want to do. It help you manage relations or objects but it won't associate objects if you don't tell it to do so.

You could however try with mutator. You could add to your Question model:

public function setTypeAttribute($value)
{
    $value->save();
    $this->attributes['type_id'] = $value->id
}

and now it should be possible to use $q->type = $t; (However I haven't tested it)