4

I am currently running on Laravel 5.1.19 and am observing the following issue:

Assume the following models (Students and Teachers as example):

class Teacher extends \Illuminate\Database\Eloquent\Model {
    public function rel_students() {
        return $this->hasMany(Student::class);
    }
}

class Student extends \Illuminate\Database\Eloquent\Model {
    public function rel_teacher() {
        return $this->belongsTo(Teacher::class);
    }
}

If you then query an instance of Teacher and (lazy) eager load its Students, the ->rel_teacher magic member of all students triggers a fresh query against that teacher:

$teacher = Teacher::with('rel_students')->find(1);
// or this, the effect is the same
$teacher = Teacher::find(1); $teacher->load('rel_students');

foreach ($teacher->rel_students as $student)
    echo $student->teacher->name . "<br>";

Query log for the above code:

SELECT * FROM teachers WHERE id = 1
SELECT * FROM students WHERE teacher_id IN (1)
SELECT * FROM teachers WHERE id = 1
SELECT * FROM teachers WHERE id = 1 # what is going on here?!
SELECT * FROM teachers WHERE id = 1
... and so forth for every student ...

The issue: Eloquent has an instance of Teacher #1 when find(1) finishes. I expect eloquent to pass a reference to this very PHP-Objet to the eager loaded Students so that $student->teacher returns that reference instead of triggering another query.

another, negative sideffect: even if the queries were no performance issue (which they are!) i'd have hundrets of instances for Teacher #1 fyling around and that is very contrary to the unit-of-work pattern.

Question: What can i do to make eloquent behave as i expect it to? if not possible: which of the PHP-ORMs are "intelligent" enough and do this simple trick?

UPDATE 1: I did try to remove the underscore from the relation name - same result.

UPDATE 2: As mentioned in an answer, with/load with both relations does a almost-perfect job: $teacher->load('rel_students.rel_teacher'). This reduces the queries for the teacher to 2, but that still leaves the issue that $student->rel_teacher !== $teacher. This is fine until someone modifies either object and then it starts getting hairy.

marstato
  • 363
  • 2
  • 12
  • Eloquent just doesn't hold references between related models. Why would it be an issue anyway? – Jarek Tkaczyk Nov 13 '15 at 17:48
  • Code in two different parts of the application modifies the teacher; the one using the `$teacher` reference/object, the other using the `$student->rel_teacher` reference/object. In that case changes of either code will be lost. Depending on which of both calls `save()` first, and depending on whether `save()` is called on `$teacher` or `$student` or `$student->rel_teacher`. This is extremely error-prone which is why i want to avoid it. – marstato Nov 14 '15 at 18:24
  • You should use doctrine maybe? Active record is more of `fetch, process, update` way of handling things. You make changes here and save here, you make changes there and save there. – Jarek Tkaczyk Nov 14 '15 at 19:25
  • I did take a look at doctrine, too. It seems to be what i want. Since migrating to Doctrine for the particular project is not an option i will have to adopt to that style of coding, then. But i`ll give doctrine a try in a future project; thnx @JarekTkaczyk – marstato Nov 15 '15 at 03:37

1 Answers1

2

If you want to reference the teacher from students you need to lazy load them as well for each student so with('rel_students.rel_teacher') and then echo $student->rel_teacher->name OR use the teacher model you are looping on echo $teacher->name

EDIT

$teacher = Teacher::with('rel_students.rel_teacher')->find(1);

foreach($teacher->rel_students as $student)
{
    if($teacher->id != $student->rel_teacher->id)
    {
        dd('error');
    }
    echo $student->rel_teacher->name . "<br>";
}
Pawel Bieszczad
  • 12,925
  • 3
  • 37
  • 40
  • Thanks for your answer. The `$student->teacher->name` thing is actually really stupid, it was just an example of using the `->teacher`. Your suggestion is alomost working. But still then, `$student->teacher !== $teacher`, which will sooner or later lead to strange effects, i believe :/ – marstato Nov 12 '15 at 15:14
  • see my edit. do you see the error? This should work correctly – Pawel Bieszczad Nov 12 '15 at 15:21
  • The issue is not that the IDs of the teachers are different but that there are two _PHP-Objects_ pointing to the same database row. Modyfing `$teacher`does not modify `$student->teacher` – marstato Nov 12 '15 at 15:45
  • What are you trying to accomplish? The objects are a representation of the data from the database. If you change one object and save it to the DB you have to reload the other for it to reflect the changes. – Pawel Bieszczad Nov 12 '15 at 15:51
  • That is the core problem: i want one PHP-Object per Request per Database Row. Consider this: Some code modifies `$teacher` and other code modifies `$student->teacher`. There is no way to persist both changes without reloading a model before reading/writing its data - and that is very impractical. – marstato Nov 12 '15 at 16:28
  • I don't think that's possible. I still don't understand how the two would ever cause a problem. If you are updating the teacher, you can just update him and if you want to return the object with students just fetch it after. Can you explain what are you trying to do where this would cause a problem? Maybe I can help with that? – Pawel Bieszczad Nov 12 '15 at 16:47
  • Lets say the controller modifies `$teacher->name` and then calls `save()` on it. Then it passes a selected `$student` to other code that, for example, does `$student->rel_teacher->favouriteStudent = $student->name` and also calls `save()`. Now the favouriteStudent column is correct, but the other change got lost. Finding such errors can be extremely annoying, i want to avoid that. – marstato Nov 12 '15 at 16:59