Search code examples
phplaravelmany-to-many

Laravel 5.6 Many to Many relationship throwing errors during display


**** UPDATE *******************************************************

I'm not sure why, but moving everything into the controllers index() method rather than having it in the show() method solved the problem. Don't know why it works, but it does.


Original Question:

Cant figure out what is wrong here, i have followed the docs, tried variations, etc... still no luck making this work correctly.

Model A: (Slide)

public function carousels() {
    return $this->belongsToMany(Carousel::class)->withTimestamp()
} 

Model B: (Carousel)

public function slides() {
    return $this->belongsToMany(Slide::class)->withTimestamp()
} 

Pivot Table

--------------------------------------------------
|carousel_id | slide_id | created_at | updated_at|
--------------------------------------------------

From Controller:

public function show(Carosuel $carousels) {
    $carousels = $carousel->with('slides)->get();

    return view('myView', compact(['carousels']));
}

In the following dump, the "slides" are not in the attributes object of the collection; however, they are in the relations object. Im not sure if this is normal behavior or if the slides array should be in the attributes object. If the latter is the case, then how does one go about making that happen?

$caousels dump:

Collection {#1320 ▼
  #items: array:20 [▼
    0 => Carousel {#798 ▼
      #fillable: array:4 [▶]
      #connection: "mysql"
      #table: null
      #primaryKey: "id"
      #keyType: "int"
      +incrementing: true
      #with: []
      #withCount: []
      #perPage: 15
      +exists: true
      +wasRecentlyCreated: false
      #attributes: array:7 [▼
        "id" => 1
        "name" => "Prof. Andre Gerlach"
        "category" => "Dr."
        "active" => 0
        "deleted_at" => null
        "created_at" => "2018-04-07 21:10:52"
        "updated_at" => "2018-04-07 21:10:52"
      ]
      #original: array:7 [▶]
      #changes: []
      #casts: []
      #dates: []
      #dateFormat: null
      #appends: []
      #dispatchesEvents: []
      #observables: []
      #relations: array:1 [▼
        "slides" => Collection {#1300 ▼
          #items: array:5 [▼
            0 => Slide {#1023 ▶}
            1 => Slide {#1024 ▶}
            2 => Slide {#1025 ▶}
            3 => Slide {#1026 ▶}
            4 => Slide {#1027 ▶}
          ]
        }
      ]
      #touches: []
      +timestamps: true
      #hidden: []
      #visible: []
      #guarded: array:1 [▶]
    }
    1 => Carousel {#799 ▶}
    2 => Carousel {#800 ▶}

In the View:

foreach($carousels as $carousel){

    echo $carousel->name;

    foreach($carousel->slides as $slide){
         echo $slide->tag;
    }
}  

This throws the E_ERROR Invalid argument supplied for foreach() in view

If, however, I cast this to an array, it works. Problem is that i also need to paginate the results. Once i do, the slides array is again inaccessible.

public function show(Carosuel $carousels) {
    // This works - slides are available to me in the view
    $carousels = $carousel->with('slides)->get()->toarray();

    // This DOESN'T work - slides are not available to me in the view
    $carousels = $carousel->with('slides)->paginate(6)->toarray();

    return view('myView', compact(['carousels']));
}

dump as array:

  array:12 [▼
    "current_page" => 1
    "data" => array:6 [▼
      0 => array:8 [▼
        "id" => 1
        "name" => "Prof. Andre Gerlach"
        "category" => "Dr."
        "active" => 0
        "deleted_at" => null
        "created_at" => "Apr 07 2018"
        "updated_at" => "2018-04-07 21:10:52"
        "slides" => array:5 [▼
          0 => array:11 [▶]
          1 => array:11 [▶]
          2 => array:11 [▶]
          3 => array:11 [▶]
          4 => array:11 [▶]
        ]
      ]
      1 => array:8 [▶]
      2 => array:8 [▶]
      3 => array:8 [▶]
      4 => array:8 [▶]
      5 => array:8 [▶]
    ]
    "first_page_url" => "//localhost:3000/admin/carousel/show?page=1"
    "from" => 1
    "last_page" => 4
    "last_page_url" => "//localhost:3000/admin/carousel/show?page=4"
    "next_page_url" => "//localhost:3000/admin/carousel/show?page=2"
    "path" => "//localhost:3000/admin/carousel/show"
    "per_page" => 6
    "prev_page_url" => null
    "to" => 6
    "total" => 20

The problem here is that, in the view, the slides key is no longer accessible. Throws following error (only if I use ->paginate()).

E_ERROR Trying to get property 'slides' of non-object

Everything I have read in the docs and from other sources show this to be a pretty basic operation. For the live of me, i cant figure out why its causing such an issue. I should be able to access this in the view by simply nesting a foreach.

Any help with this would be greatly appreciated.


Solution

  • In the following dump, the "slides" are not in the attributes object of the collection; however, they are in the relations object.

    Yes this is normal.

    This is where you have a problem:

    public function show(Carosuel $carousels) {
        // ...
        return view('myView', compact(['carousels']));
    }
    

    Typehinting a model in a function is reserved for model binding. Which is not what you're doing here since you don't have an {id} or some other parameter in your route.

    Remove the typehinting and call the model instead:

    public function show() {
        $carousels = Carousel::with('slides')->get();
        return view('myView', compact(['carousels']));
    }
    

    If you'd like to remove the dependency from the method, you should do it in the constructor instead.

    Edit:

    While I don't see any other problem and the above solution should work, if it's true that ->toArray() works for you (but you still need pagination), you could just convert the underlying collection:

    public function show() {
        $carousels = Carousel::with('slides')->paginate();
        $carousels->setCollection(collect($carousels->getCollection()->toArray()));
        return view('myView', compact(['carousels']));
    }