26

I have the following relations set up:

class Page {
    public function comments() {
        return $this->hasMany('Comment');
    }
}

class Comment {
    public function page() {
        return $this->belongsTo('Page');
    }
}

Pretty bog standard. One page can have many comments, and one comment belongs to a single page.

I'd like to be able to create a new page:

$page = new Page;

and a comment

$comment = new Comment;

and attach the comment to the page, without saving any of it

$page->comments->associate($comment);

I've tried the following:

// These are for one-to-many from the MANY side (eg. $comment->page->associate...)
$page->comments->associate($comment);   // Call to undefined method Illuminate\Database\Eloquent\Collection::associate()
$page->comments()->associate($comment); // Call to undefined method Illuminate\Database\Query\Builder::associate()

// These 2 are for many-to-many relations, so don't work
$page->comments->attach($comment);      // Call to undefined method Illuminate\Database\Eloquent\Collection::attach()
$page->comments()->attach($comment);    // Call to undefined method Illuminate\Database\Query\Builder::attach()

// These 2 will (if successful) save to the DB, which I don't want
$page->comments->save($comment);        // Call to undefined method Illuminate\Database\Eloquent\Collection::save()
$page->comments()->save($comment);      // Integrity constraint violation: 1048 Column 'page_id' cannot be null

The really odd thing is that doing the opposite (attaching the page to the comment) works correctly:

$comment->page()->associate($page);

The relevant docs are here but they don't make any mention of attaching to the ONE side of a one-to-many. Is it even possible? (I feel like it should be)

Joe
  • 15,669
  • 4
  • 48
  • 83

3 Answers3

38

It sounds like you just want to add the new comment object to the page's comments collection - you can do that easily, using the basic colection add method:

$page = new Page;
$comment = new Comment;
$page->comments->add($comment);
Benubird
  • 18,551
  • 27
  • 90
  • 141
  • How do you save the comments afterwards? – Boedy Oct 06 '14 at 21:06
  • 1
    @Boedy That's a separate question - this is just talking about objects that are not linked to the database. If you want to save the Comment, you need to save the Page first, and then use the >save() method to link them. What I'm doing will not actually connect them in the database, because they don't have ids; it's just adding the comment object to a collection. – Benubird Oct 07 '14 at 14:18
  • Stating the obvious here... you should keep in mind that calling the `comments` property in the `$page` will trigger a query in the database, so it might be slower than what you'd expect.. – edpaez Nov 24 '14 at 21:12
  • 2
    You should also be able to use $page->push() to save the page and all related comments, rather than save each one separately. – rich remer Feb 25 '15 at 22:51
  • @rich-remer But when I do $page->push(), it returns error that the foreign key for $comment (which is page_id) is missing. What to do about that? Apparantly, I cannot supply it because I don't have it. – Huzaib Shafi Jul 20 '16 at 15:21
  • One issue with this is that you can't insert additional data into the pivot table – Jeremy Belolo Sep 12 '22 at 16:37
10

You can't do since there are no ids to link.

So first you need to save the parent ($page) then save the child model:

// $page is existing model, $comment don't need to be
$page->comments()->save($comment); // saves the comment

or the other way around, this time without saving:

// again $page exists, $comment don't need to
$comment->page()->associate($page); // doesn't save the comment yet
$comment->save();
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • This makes sense, but if I do `$comment->page()->associate($page)` then it attaches it correctly, despite the page not having an ID either – Joe May 30 '14 at 10:32
  • No, it doesn't throw any error, but it doesn't associate it correctly either. In fact it sets `page_id` to `null`, since, like you said, there is no id on the `$page` object. – Jarek Tkaczyk May 30 '14 at 10:38
  • 1
    No, it associates correctly. The `page_id` is indeed null, but the relations array is populated - `dd($comment)` prints this: http://grab.by/xksA – Joe May 30 '14 at 10:41
  • I wouldn't call it correct if it fails. In fact there should be some kind of error, but Eloquent doesn't provide any on this occasion. – Jarek Tkaczyk May 30 '14 at 10:52
  • It doesn't fail :) The ID of the page is `null` so it sets the `$background->page_id` to null. It then adds the `Page` into the relation. That all makes sense and is correct, not failing – Joe May 30 '14 at 12:19
  • I can't agree, this method is used in the context of a relation, always. As opposed to the accepted answer it doesn't make sense setting FK to null. – Jarek Tkaczyk May 30 '14 at 14:32
  • The context is a "create page" section for the admin. "Comment" is actually "Background", and a page should have one (or more) backgrounds. When I create a page I want it to automatically have a background added, so the rest of the application doesn't need to worry about the logic of the relationship. – Joe May 30 '14 at 14:44
  • That's perfectly OK, I don't argue with your case in no way. What I mean is that `associate` is used in the context of a relation, thus with null FK doesn't make sense for me. – Jarek Tkaczyk May 30 '14 at 15:03
  • Ah, now I can see where you're coming from. I think there's plenty of ways to argue for and against that, and it's a whole can of worms. Interesting worms, but still worms :P Thanks for your help, because it's been useful to just think critically about it and discuss – Joe May 30 '14 at 15:07
  • FYI Redbean doesn't have this limitation – malhal Sep 09 '14 at 10:50
6

according to Benubird I just wanted to add something as I stumbled over this today:

You can call the add method on a collection like Benubird stated. To consider the concerns of edpaaz (additional fired query) I did this:

$collection = $page->comments()->getEager(); // Will return the eager collection
$collection->add($comment) // Add comment to collection

As far as I can see this will prevent the additional query as we only use the relation-object.

In my case, one of the entities were persistent while the first (in your case page) was not (and to be created). As I had to process a few things and wanted to handle this in a object manner, I wanted to add a persistent entity object to a non persistent. Should work with both non persistent, too though.

Thank you Benubird for pointing me to the right direction. Hope my addition helps someone as it did for me.

Please have in mind that this is my first stackoverflow post, so please leave your feedback with a bit concern.

Julius Blatt
  • 91
  • 1
  • 4