0
votes

Currently working on a very basic three table/model problem in Laravel and the proper Eloquent setup has my brain leaking.

COLLECTION -> coll_id, ...

ITEM -> item_id, coll_id, type_id, ...

TYPE -> type_id, ...

I can phrase it a thousand ways but cannot begin to conceptualize the appropriate Eloquent relationships:

COLLECTIONS have many ITEMS, each ITEM has a TYPE. Or, an ITEM belongs to a single COLLECTION, TYPES are associated with many ITEMS.

To me COLLECTION and ITEM are key, while TYPE is really additional reference data on a given ITEM; basically a category for the item.

My objective is to build a solution where users create COLLECTIONS, add ITEMS to those COLLECTIONS, and those ITEMS are associated with one of many TYPES (which they will also define).

I know this example isn't far from other Author, Book, Category; or City, State, Country examples but implementing those model frameworks doesn't build the full connection from COLLECTION<->ITEM<->TYPE and most obviously fails when trying to associate TYPES within a COLLECTION.

Models:

COLLECTION

class Collection extends Model
{
    protected $guarded = [];

    public function items()
    {
        return $this->hasMany(Item::class);
    }

    public function types()
    {
        return $this->hasManyThrough(Type::class, Item::class);
    }

}

ITEM

class Item extends Model
{
    public function collection()
    {
        return $this->belongsTo(Collection::class);
    }

    public function type()
    {
        return $this->belongsTo(Type::class);
    }
}

TYPE

class Type extends Model
{
    public function item()
    {
        return $this->hasMany(Item::class);
    }

}

These models have COLLECTION<->ITEM in both directions working well, but the wheels fall off when I try to integrate TYPE. Depending on the 1000 variations I've tried I either get NULL in Tinker or it's looking for an ITEM_ID in TYPE which doesn't and shouldn't exist.

What would a solution be to update the model to accurately reflect ITEMS having one TYPE and the eventual set of TYPES within a COLLECTION?

Thanks in advance!


UPDATE: Working nested solution - the primary response solution was showing 2 relations at COLLECTION, no nesting:

class Collection extends Model
{
    protected $guarded = [];

    public function items()
    {
        return $this->hasMany(Item::class);
    }
class Item extends Model
{
    public function collection()
    {
        return $this->hasOne(Collection::class);
    }

    public function type()
    {
        return $this->belongsto(Type::class);
    }
}
class Type extends Model
{
    public function items()
    {
        return $this->hasMany(Item::class);
    }
}
$collection->load('items.type');

        dd($collection);
Collection {#280 ▼
  #guarded: []
  #connection: "mysql"
  #table: "collections"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:5 [▶]
  #original: array:5 [▶]
  #changes: []
  #casts: []
  #dates: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  **#relations: array:1** [▼
    "items" => Collection {#285 ▼
      #items: array:2 [▼
        0 => Item {#288 ▼
          #connection: "mysql"
          #table: "items"
          #primaryKey: "id"
          #keyType: "int"
          +incrementing: true
          #with: []
          #withCount: []
          #perPage: 15
          +exists: true
          +wasRecentlyCreated: false
          #attributes: array:13 [▶]
          #original: array:13 [▶]
          #changes: []
          #casts: []
          #dates: []
          #dateFormat: null
          #appends: []
          #dispatchesEvents: []
          #observables: []
          **#relations: array:1** [▼
            "type" => Type {#296 ▶}
          ]
          #touches: []
          +timestamps: true
          #hidden: []
          #visible: []
          #fillable: []
          #guarded: array:1 [▶]
        }
        1 => Item {#289 ▶}
      ]
    }
  ]
2
You seem to have the wrong relationship on the Item model for collection(). A HasOne would mean the Collection table has a foreign key to the Item table. Please check my updated answer.newUserName02

2 Answers

2
votes

HasManyThrough isn't the right relationship between Collection and Type. HasManyThrough is kind of like chaining 2 HasMany relationships together:

Country -> State -> City

Notice how the arrows all go one way. That's because City has a foreign key to State, and State has a foreign key to Country.

Your relationship looks like this:

Collection -> Item <- Type

So Item has foreign keys to both Collection and Type. In this case, the relationship between Collection and Type is actually BelongsToMany, and the Item table is the join table.

https://laravel.com/docs/5.8/eloquent-relationships#many-to-many

class Collection extends Model
{
    protected $guarded = [];

    public function items()
    {
        return $this->hasMany(Item::class);
    }

    public function types()
    {
        // Substitute your table and key names here
        return $this->belongsToMany(Type::class, 'items_table', 'coll_id', 'type_id');
    }

}

class Type extends Model
{
    public function items()
    {
        return $this->hasMany(Item::class);
    }

    public function collections()
    {
        // Substitute your table and key names here
        return $this->belongsToMany(Collection::class, 'items_table', 'type_id', 'coll_id');
    }

}

class Item extends Model
{
    public function collection()
    {
        return $this->belongsto(Collection::class);
    }

    public function type()
    {
        return $this->belongsto(Type::class);
    }
}

If you want to get collections with their items, and the corresponding type under each item, you can do:

Collection::with(['items.type'])->get();
0
votes

You can call the item first then get the item type through your relationship.

public function getCollectionOfItemsBaseOnType($type){

$collections = Collection::all();
$items = [];
  foreach($collections as $collection){
   if($collection->item->type == $type){
       array_push($items, $collection->item);    
   }
  }
}
return $items;