Joseph
Joseph

Reputation: 6269

Get first image for each product

I try to get latest 10 products with its images but only the first image so that is what i try

$newProducts = \App\Product::latest()->with(['images', function($el){

   $el->first();

}])->with('category')->take(10)->get();

but it gives me this error

mb_strpos() expects parameter 1 to be string, object given

it has a morph relation between product and image

Product Model

class Product extends Model {
    public function images()
    {
        return $this->morphMany(Image::class, 'imageable');
    }
}

Image Model

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

Upvotes: 1

Views: 1699

Answers (3)

softroyal
softroyal

Reputation: 1

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

public function firstImage()
{
    return $this->images()->first();
}

Simply create a function that defines relationship between product and its images. Then create a function that gets the first image

Upvotes: 0

Kevin Bui
Kevin Bui

Reputation: 3035

The above solutions are all good. I personally prefer a different solution that I think is gonna be ideal.

I am gonna define a different relationship for a product:

class Product extends Model {
    public function images()
    {
        return $this->morphMany(Image::class, 'imageable');
    }

    public function firstImage()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

So you can access the first image directly or eager load the relationship:

$product->firstImage;

$product->load('firstImage');

Product::with('firstImage');

Just FYI, I learnt about this and other useful database tricks from Jonathan Reinink in Laracon Online 2018.

Upvotes: 2

mrhn
mrhn

Reputation: 18916

When using with as a key value array, the $el parameter to the closure will be a query builder that has not executed yet.

The way to limit query builders of results is to use take(). Therefor your code should look like this.

->with(['images', function($el) {
    $el->take(1);
}])

Edit To make this solution work, you will need an extra package. Using the following trait should make it work and using limit instead. See the following post.

use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;

->with(['images', function($el) {
    $el->limit(1);
}])

Alternatively Laravel solution is to use transformation like properties, where you can create your own custom properties, in the function naming starting with get and ending with attribute.

class Product {
    protected $appends = ['first_image'];

    public function getFirstImageAttribute() {
        return $this->images->first();
    }
}

Now if you use standard Laravel serialization all products will have an first_image field and in your code you can access it like so.

$product->first_image;

To avoid performance hits, include images using with('images').

Upvotes: 2

Related Questions