0
votes

I'm using Laravel with Eloquent. In my example i have 3 tables: image, user and post. What i want to achieve is that the image can belong to a exactly one user and/or exactly one post.

So i think the tables should be like this:

Image
------------
id
image_name

user
------------
id
name
image_id

post
------------
id
text
image_id

I tried to use the following relations:

  • image: belongsTo -> user
  • image: belongsTo -> post
  • user: hasOne -> image
  • post: hasOne -> image

But when i do $user->image()->save(someImageObject); i get the error that the column user_id is unknown on the image table which is correct. So i thought that i maybe mis-understand the relations and need to flip them so i changed the relations (even when it feels wrong now) on the models to:

  • image: hasOne -> user
  • image: hasOne -> post
  • user: belongsTo -> image
  • post: belongsTo -> image

When i do $user->image()->save(someImageObject);, i get the error that its not possible to call save() on a BelongsTo relation. When i create the relation like this: $image->user()->save(theUserObject); it works fine but this feels/looks weird in code as i want to add an image to a user and not the user to the image.

So is this simply the way to go and is my "this feels weird" feeling wrong? or is there antoher way i should define my relationship?

I also experimented with morph relationships (many-to-many) and this seems to work fine but than i cannot restrict that a user/post has exactly one image. The morph one-to-many relations sounds by name like its exactly what i need but i have no clue how this should work as it has the relation id/type on the image table. When i test around with this relation type, it replaces the type on the image table when i try to link an image to an user AND post (like i actually expected)

2
Just to clarify, 1 image can belong to a user and that same image can also belong to a post? Or 1 image can only belong to a user while another image can only belong to a post?Robert Kujawa
@Robert that's absolutely correct! I will update my post as soon as I'm behind a computer.CodeNinja
Were any of the provided answers helpful? You should upvote with ▲ all answers that were helpful if you have the reputation to do so. You should then mark accepted with ✓ the one answer that best answered your question. This will mark the question as "closed," and give you some reputation on the site. If none of the answers were satisfactory, provide feedback with comments, or edit your question to clarify the problem.miken32

2 Answers

2
votes

Your question is unclear on the relationships between these objects. But based on this:

user: hasOne -> image
post: hasOne -> image

I'm assuming that each user or post can have only a single image.


The expectation, when building these relationships, is that the "child" object will hold on to the "parent" ID in its table. This is more complicated when the "parent" can be one of two different models; this is known as a polymorphic relationship. Laravel's documentation on polymorphic relations deals specifically with your scenario

A one-to-one polymorphic relation is similar to a simple one-to-one relation; however, the target model can belong to more than one type of model on a single association. For example, a blog Post and a User may share a polymorphic relation to an Image model. Using a one-to-one polymorphic relation allows you to have a single list of unique images that are used for both blog posts and user accounts.

So your database should look like this, with the child keeping track of the parent's ID.

posts
-----
id
text

users
-----
id
name

images
------
id
image_name
imageable_id
imageable_type

And then your relationships are defined like this:

<?php
class Image extends Model
{
    public function imageable() {
        return $this->morphTo();
    }
}

class Post extends Model
{
    public function image() {
        return $this->morphOne('App\Image', 'imageable');
    }
}

class User extends Model
{
    public function image() {
        return $this->morphOne('App\Image', 'imageable');
    }
}

Now, to retrieve a user's image is as simple as getting the image property. To find out the owner of an image, you look at the imageable property. You can use various methods to see what the owner is:

$user = User::find($id);
$image = $user->image;

$image = Image::find($id);
$owner = $image->imageable;
if ($owner instanceof App\User) {
    // do stuff
} else {
    // do other stuff
}

To update the relationship, with the database setup correctly, is simple:

$user->image()->save($image);
$post->image()->save($image);
$image->imageable()->save($user);
1
votes

Your problem here lies in your data model.

I find it helps here to state my problem in English and that can often resolve how the database objects related to each other. Let's start with this problem and boil it down to a statement.

Your users, I assume, aren't going to be limited to putting up a single image through the lifetime of their account. Therefore we want the users to be able to add as many images as they like. This becomes the statement;

I want my users to have many images.

Now, we could have a column in the user table for each image the user can have. But then, we'd fast run out of columns, and have to keep changing our queries to accommodate that. This means we need to store the images separately so we can have as many as we like, and therefore means another table to contain the image. But how do we know who owns an image?

I want my images to belong to a single user.

From these two statements, we know the user isn't limited in the images they have, and that any image must belong to user (or a post, we look at that later).

users
=======
id
name

images
=======
id
name
user_id

The problem we are seeing is that this breaks your next statement that an image can belong to a user or a post, or both. But we can boil this down to the statement;

An image might belong to a user or a post, or maybe both.

From here, we need a way to tell an image, "you might have no user, or no post". This is where we introduce nullable fields on the image table.

users
=======
id
name

posts
=======
id
name

images
=======
id
name
user_id nullable
post_id nullable

Now, from here, you then start asking your questions about retrieving this data. We know the following:

  • The user can Have Many images
  • A post can Have Many images
  • An image can Belong To a user, and it can also Belong To a post

Notice my cheesy emphasis. This hints to you the relationships you might want on your models.

class User {
  public function images() {
    return $this->hasMany('Image');
  }
}

class Post {
  public function images() {
    return $this->hasMany('Image');
  }
}

class Image {
  public function user() {
    return $this->belongsTo('User');
  }

  public function post() {
    return $this->belongsTo('Post');
  }
}

Hope this helps clear things up!