Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

FutureWeb's avatar

foreach on with toarray

Hiya LaraPeeps,

I have a collection with relationships ->toArray() that looks something like this:

 $phone = Phone::with('reviews','specifications','retailers')->first()->toArray();

Now if there is only one review it breaks my foreach in the view why is this?

  @foreach($phone['reviews'] as $r)
            // html
   @endforeach

I don't want to have to do a condition as it would mean repeating loads of html

@if(count($phone['reviews'] >=0)
   // print out single review
   //html
 @else
    // loop through
    @foreach($phone['reviews'] as $r)
      // html 
@endforeach
   @endif 

seems a little silly any ideas why the foreach won't work with only 1 element in the $phone['reviews'] array?

thanks in advance

0 likes
29 replies
phildawson's avatar

Well you have an array of the first Phone?!

$phone = [
    'reviews'  => 'example'
];
$phone['reviews']; // example

Remove the first() if you don't want just the first, and the whole Collection to an array.

 $phones = Phone::with('reviews','specifications','retailers')->get()->toArray();

As a Collection implements Arrayable/ArrayAccess you can loop as normal with the objects.

$phones = Phone::with('reviews','specifications','retailers')->get();
foreach($phones as $phone)
{
    foreach($phone->reviews as $review)
    {
        $review; //
    }
}

or if a phone has many reviews

$phone = Phone::with('reviews','specifications','retailers')->first();
foreach($phone->reviews as $review)
{
    $review; //
}

bobbybouwmann's avatar

Are you sure it's still an array? Did you try a dd() before your foreach loop?

// without spaces
{ { dd($phone) } } 

@foreach($phone['reviews'] as $r)

This will show you the output of the object, if it is indeed an array it should work!

Also may I ask why you pass it through as an array? Laravel can handle the collection as well! That works even better and has a nice syntax as well

@foreach($phone->reviews as $review)
FutureWeb's avatar

I cache a load of stuff into a $collection array so its really $collection['phone']['reviews']

 Array
 (
    [phone] => Array
     (
        [id] => 2582
        [make] => Apple
        [name] => iPhone 5S 16GB
        [base_name] => iPhone 5S
        [image] => /img/phone/apple-iphone-5s-16gb.jpg
        [side_image] => 
        [back_image] => 
        [thumb] => /img/phone/thumb/apple-iphone-5s-16gb.jpg
        [created] => 2013-09-20
        [blurb] => 
        [colour] => Black
        [colour_name] => Black
        [os] => IOS
        [type] => Phone
        [popularity] => 442
        [created_at] => 2015-08-23 10:07:46
        [updated_at] => 2015-08-23 10:07:46
        [slug] => apple-iphone-5s-16gb
        [make_id] => 2
        [make_slug] => 
        [reviews] => Array
            (
                [id] => 678
                [device_id] => 2582
                [yourname] => terry knight
                [location] => cv9 3af
                [review] => battery needs to have something done to make it last longer and there is a certain fear of the phones fragility among many iphone users apps are good there is lots the phone can do and it does a lot of things very well however itunes and paying for ringtones doesnt go down to well with many 
                [design] => 7
                [features] => 9
                [usability] => 6
                [approved] => 1
                [youremail] => xxxxx
                [reviewdate] => 2014-09-20
            )
           
bobbybouwmann's avatar

Have you tried not turning it into an array and just use the collection? Or am I missing something?

phildawson's avatar

I cache a load of stuff into a $collection array so its really

That doesn't make any sense to me. reviews should always be an array, I can't see how it could be either 1 or an array?

FutureWeb's avatar

I am being a little lazy because I am converting my old vanilla php site https://shopmobilephones.co.uk to laravel and so matching the vars up in my old pages in the new views my controller looks like this:

         $collection = Cache::remember('phone_'.$id,7200,function() use($id){

        $phone = Phone::with('reviews','specs','phoneContractNetwork','upgradeNetwork','phonePaygNetwork')->whereSlug($id)->get()->toArray();
        $phone = $phone[0];
        $related = Phone::where('base_name', '=',$phone['base_name'])->where('name','!=',$phone['name'])->get()->toArray();
        $reviewAvg = Review::selectRaw('count(id) as revcount,sum(design) as destotal,sum(features) as fettotal, sum(usability) as usatotal')->where('device_id', '=', $phone['id'])->first()->toArray();

        $meta = Meta::where('slug','=','/mobiles/'.$id)->get();

        if(!$meta->isEmpty())
        {
            $phone['meta'] = $meta->toArray();
        }
        else{

            $meta = [];
            $meta['title'] =  $phone['make'].' '. $phone['name']. ' Reviews, News & Specifications';
            $meta['meta_keyw'] = $phone['make'].' '.$phone['name'].' Reviews,'.$phone['make'].' '.$phone['name'].' News,'.$phone['make'].' '.$phone['name'].' Specifications';
            $meta['meta_descr'] = $phone['make'].' '.$phone['name']. ' News, Reviews and full Specifications Find out if the '.$phone['make'].' '.$phone['name'].' is the right handset for you.';
            $meta['h1_tag'] = $phone['make'].' '.$phone['name']. ' Reviews, News & Specifications';

        }

        $collection = [];
        $collection['phone'] = $phone;
        $collection['reviewAvg'] = $reviewAvg;
        $collection['related'] = $related;
        $collection['meta'] = $meta;

        return $collection;
    });
bobbybouwmann's avatar

This looks really messy.. Just use the collection and save a thousand headaches ;)

infernobass7's avatar

What is the exact error code that you are getting?

It looks like from the example that you posted above that instead of looping through each instance of a review it is trying to loop through each attribute of the review.

You are trying to access it like this:

$r['id'] // or $collection['phone']['reviews']['id']

but it is trying to access it like this

$r['id'] // or $collection['phone']['reviews']['id']['id']
bimalshah72's avatar

@FutureWeb

As per your statement, it seems that you always want first phone and it's all reviews (Single or more)

I think, so when you get first(), it always returns Object of phone, instead of collection. Hence instead of getting array, it should be,

$phone = Phone::with('reviews','specifications','retailers')->first(); 

and then

 @foreach($phone->reviews as $r) //this will take care whether it is single or more records, even I think no record at all.
            // html - 
   @endforeach
FutureWeb's avatar

@bobbybouwmann

But if I use the collection would that mean I would either have to create a new object to store all the other objects in or cache each object individually ?

the networks and retailers relationships hit a table with nearly 2 million records in so caching is a must.

FutureWeb's avatar

@bimalshah72 I have tried first()->toArray() as well but like I say above I really want to cache it all together rather than creating lots of different caches for the same page.

phildawson's avatar

@FutureWeb you can cache the results as an eg.

    $collection = Cache::remember('example', 30, function() {

        return collect([1,2,3,4]);
    });

    dd($collection);

Edit ninja'd yet again :|

I'm still puzzled as to the OP, how reviews can be either a single object or array? The only thing I can think of is whilst coding and testing it cached the reviews when it had the wrong query, have you tried clearing the cache?

class Phone extends Model 
{
    function reviews()
    {
        return $this->hasMany(Review::class);
    }
}

dd(Phone::with('reviews')->first());
FutureWeb's avatar

@infernobass7 I get:

 array:11 [▼
 "id" => 678
 "device_id" => 2582
 "yourname" => "terry knight"
 "location" => "cv9 3af"
 "review" => "battery needs to have something done to make it last longer and there is a certain fear of the phones fragility among many iphone users apps are good there is lots the phone can do and it does a lot of things very well however itunes and paying for ringtones doesnt go down to well with many "
"design" => 7
"features" => 9
"usability" => 6
"approved" => 1
"youremail" => "xxxxx@gmail.com"
"reviewdate" => "2014-09-20"
]
bobbybouwmann's avatar

So this is one object? Are you sure you relation is setup correctly? This seems to return only one result instead of an array of multiple results

FutureWeb's avatar

there is only 1 review for this phone but yes it does return multiple results if there are more reviews.

phildawson's avatar
Level 26

@FutureWeb

I assume this?

phones
id ...

reviews 
id phone_id ....
class Phone extends Model 
{
    function reviews()
    {
        return $this->hasMany(Review::class);
    }
}
1 like
FutureWeb's avatar

yeah not far off:

phones id

tablets id

// etc reviews id device_id

and belongsTo('App\Phone','reviews','id,'device_id');

thomaskim's avatar

So if I'm reading your code correctly...

@if(count($phone['reviews'] >=0)
   // print out single review
   //html
@else
    // loop through
    @foreach($phone['reviews'] as $r)
        // html 
    @endforeach
@endif

The first if statement would be true for a single review, no reviews, all reviews. When would that else statement ever hit?

FutureWeb's avatar

if I dd($r) inside the loop it just gives me the first item value in the array

thomaskim's avatar

dd($r) should only give you the first item because once it hits, it exits the application. It sounds like it's working to me. What does your HTML look like?

FutureWeb's avatar

Changing the relationship from belongsTo to hasMany did the trick

phildawson's avatar

The review ($r) is just one review though. The $phone['reviews'] will be the array.

Going way back the OP

Now if there is only one review it breaks my foreach in the view why is this?

If it has one review the $phone['reviews'] should be an array of one review. There should be no issue at all looping it.

Edit: Just seen the latest post, happy days.

FutureWeb's avatar

thanks all stay tuned for the next helping of spaghetti :)

Please or to participate in this conversation.