1

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();
    }
}
Joseph
  • 5,644
  • 3
  • 18
  • 44

3 Answers3

2

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').

mrhn
  • 17,961
  • 4
  • 27
  • 46
  • the `mutations` way works fine thanks, but as you said To avoid performance hits, include images using `with('images')` i try it but it get the same error – Joseph Jan 08 '20 at 22:09
  • here it is what it try `$newProducts = \App\Product::latest()->with(['images', function($el){ $el->take(1); }])->get();` – Joseph Jan 08 '20 at 22:09
  • You can't use `take()` inside of a `with` constraint. The way eager loading works is it loads all the parent records, then loads all the children with a `where parent_id in ([previously loaded parent ids])`. When you do `take()` it essentially tells it to load a single child TOTAL, regardless of how many parents there are – jfadich Jan 08 '20 at 22:13
  • @jfadich could you tell me how to fix it ? – Joseph Jan 08 '20 at 22:18
  • Using @mrhn's "Alternatively Laravel solution" should work. The one caveat is if you use `protected $appends = ['first_image'];` you should also use `protected $with = ['images']` – jfadich Jan 08 '20 at 22:20
  • Oh i actually thaught the first solution would work, i guess using https://github.com/staudenmeir/eloquent-eager-limit should be the other option – mrhn Jan 08 '20 at 22:21
  • but which one would be optimized [best solution] for this problem ? – Joseph Jan 08 '20 at 22:25
  • I made an edit to an package that seems to solve the problem. In general i would not be that afraid of performance unless you are dealing with high load systems. – mrhn Jan 08 '20 at 22:26
  • @mrhn you awesome thanks a lot sir it works perfectly – Joseph Jan 08 '20 at 22:34
  • @mrhn your answer in awesome sir but it need for an external package and i try to save resources as i could – Joseph Jan 09 '20 at 00:50
2

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.

Kevin Bui
  • 2,754
  • 1
  • 14
  • 15
0
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