Search code examples
phplaraveleloquentmany-to-manyrelationship

Laravel Eloquent: Complex Many-to-Many Relationship with Default Values for Missing Relations


I'm facing a complex problem in Laravel concerning Eloquent relationships. I'd appreciate any assistance you can provide.

Here's the structure of my tables:

Tables:

  • albums: id, name
  • photos: id, name
  • layouts: id, name
  • album_photo: album_id, photo_id
  • album_photo_layout: album_id, photo_id, layout_id, visibility

Relationships:

  • An album can have multiple photos (many-to-many relation between albums and photos with a pivot table album_photo).
  • A photo can belong to multiple albums.
  • Each photo in each album can have multiple layouts with unique visibility settings (three-way many-to-many relation between albums, photos, and layouts with a pivot table album_photo_layout).

What I'm trying to achieve is to get all albums, with all photos in each album, and all possible layouts for each photo. If a certain layout is not linked with a photo for a specific album, I want it to be added with a default visibility value.

Here is my current approach:

$album->load([
    'photos.layouts' => fn($query) => $query->where('album_id', $album->id)
]);

But this doesn't include layouts that haven't been linked with a photo for a specific album. These missing layouts should be included with a default visibility value.

I've tried to address this using whenLoaded() in the resource, but without success. It returns null for missing layouts and doesn't meet my requirements.

Ideally, I want a solution that adheres to the Eloquent ORM principles, without resorting to raw SQL queries.


Solution

  • You might want to change things up a bit.

    Album:
    - id
    - name
    - hasMany(AlbumPhoto)
    
    Photo:
    - id
    - name
    - hasMany(AlbumPhoto)
    
    Layout
    - id
    - name
    - hasMany(AlbumPhotoLayout)
    
    AlbumPhoto
    - id
    - belongsTo(Photo)
    - belongsTo(Album)
    - hasMany(AlbumPhotoLayout)
    
    AlbumPhotoLayout
    - id
    - belongsTo(AlbumPhoto)
    - belongsTo(Layout)
    

    What you are looking for is Album::with('albumPhoto.albumPhotoLayout');

    I find it useful in many cases to model pivots as models. Also, you can find a middle ground by defining Pivot classes. Actually, AlbumPhoto and AlbumPhotoLayouts can extend Pivot instead of Model.