Get first image for each product - php

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();
}
}

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.

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

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

Related

Laravel hasOne relation with where clause

I have a model called RealEstate, this model has a relation with another model called TokenPrice, I needed to access the oldest records of token_prices table using by a simple hasOne relation, So I did it and now my relation method is like following:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class RealEstate extends Model
{
public function firstTokenPrice(): HasOne
{
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
}
By far it's fine and no complexity. But now, I need to involve another relation into firstTokenPrice.
Let me explain a bit more:
As my project grown, the more complexity was added it, like changing firstTokenPrice using by a third table called opening_prices, so I added a new relation to RealEstate called lastOpeningPrice:
public function lastOpeningPrice(): HasOne
{
return $this->hasOne(OpeningPrice::class)->latestOfMany();
}
So the deal with simplicity of firstTokenPrice relation is now off the table, I want to do something like following every time a RealEstate object calls for its firstTokenPrice:
Check for lastOpeningPrice, if it was exists, then firstTokenPrice must returns a different record of token_price table, otherwise the firstTokenPrice must returns oldestOfMany of TokenPrice model.
I did something like following but it's not working:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class RealEstate extends Model
{
public function lastOpeningPrice(): HasOne
{
return $this->hasOne(OpeningPrice::class)->latestOfMany();
}
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount); // this is just a helper function that inserts a new token price into `token_prices` table, if there was none exists already with selected amount
return $this->hasOne(TokenPrice::class)->where('amount', $lop->amount)->oldestOfMany();
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
}
I have checked the $this->hasOne(TokenPrice::class)->where('amount', $lop->amount)->oldestOfMany() using by ->toSql() method and it returns something unusual.
I need to return a HasOne object inside of firstTokenPrice method.
You can use ofMany builder for that purpose:
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount); // this is just a helper function that inserts a new token price into `token_prices` table, if there was none exists already with selected amount
return $this->hasOne(TokenPrice::class)->ofMany([
'id' => 'min',
], function ($query) use ($lop) {
$query->where('amount', $lop->amount);
});
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
I used ->oldest() with a custom scope called amounted in TokenPrice model:
class TokenPrice extends Model
{
public function scopeAmounted(Builder $query, OpeningPrice $openingPrice): Builder
{
return $query->where('amount', $openingPrice->amount);
}
/....
}
And then changed my firstTokenPrice
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount);
return $this->hasOne(TokenPrice::class)->amounted($lop)->oldest();
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
It's working, but I don't know if it's the best answer or not

Laravel how to add a custom function in an Eloquent model?

I have a Product model
class Product extends Model
{
...
public function prices()
{
return $this->hasMany('App\Price');
}
...
}
I want to add a function which will return the lowest price, and in controller I can get the value using:
Product::find(1)->lowest;
I added this in Product model:
public function lowest()
{
return $this->prices->min('price');
}
but I got an error saying:
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
And if I use Product::find(1)->lowest();, it will work. Is it possible to get Product::find(1)->lowest; to work?
Any help would be appreciated.
When you try to access a function in the model as a variable, laravel assumes you're trying to retrieve a related model. They call them dynamic properties. What you need instead is a custom attribute.
Before Laravel 9
Laravel 6 docs: https://laravel.com/docs/6.x/eloquent-mutators
add following method to your model:
public function getLowestAttribute()
{
//do whatever you want to do
return 'lowest price';
}
Now you should be able to access it like this:
Product::find(1)->lowest;
EDIT: New in Laravel 9
Laravel 9 offers a new way of dealing with attributes:
Docs: https://laravel.com/docs/9.x/eloquent-mutators#accessors-and-mutators
// use Illuminate\Database\Eloquent\Casts\Attribute;
public function lowest(): Attribute
{
return new Attribute(
get: function( $originalValue ){
//do whatever you want to do
//return $modifiedValue;
});
/**
* Or alternatively:-
*
* return Attribute::get( function( $originalValue ){
* // do whatever you want to do
* // return $modifiedValue;
* });
*/
}
Use Eloquent accessors
public function getLowestAttribute()
{
return $this->prices->min('price');
}
Then
$product->lowest;
you can use above methods or use following method to add a function direct into existing model:
class Company extends Model
{
protected $table = 'companies';
// get detail by id
static function detail($id)
{
return self::find($id)->toArray();
}
// get list by condition
static function list($name = '')
{
if ( !empty($name) ) return self::where('name', 'LIKE', $name)->get()->toArray();
else return self::all()->toArray();
}
}
Or use Illuminate\Support\Facades\DB; inside your function. Hope this help others.
why you just dont do this? i know , it's not what you asked for specificallyand it migh be a bad practice sometimes. but in your case i guess it's good.
$product = Product::with(['prices' => function ($query) {
$query->min('price');
}])->find($id);
change follow code
public function lowest()
{
return $this->prices->min('price');
}
to
// add get as prefix and add posfix Attribute and make camel case function
public function getLowestAttribute()
{
return $this->prices->min('price');
}

Laravel get data from pivot table

I have a pivot table of users_operators.
I want to grab the operator_id of the user.
This is how i do this now, but its seems like verbose way.
if (Auth::user()->type === 'operator') {
$user = Auth::user();
// There is a better way to do this?
$operator_id = $user->operator[0]['pivot']['operator_id'];
Session::put('operatorId', $operator_id);
}
class Operator extends \Eloquent
{
public function users() {
return $this->belongsToMany('User');
}
}
class User extends \Eloquent
{
public function operator() {
return $this->belongsToMany('Operator');
}
}
I'm, battling insomnia and not functioning at 100%, but you should be able to get away with $user->operator->id based on what I'm interpreting your models to be (it looks like you had a typo when you copied them into the question).
If that doesn't work, you might want to check out the "Dynamic Properties" section in the Eloquent docs for more info, if you haven't already.

What is the best way to filter collection in Laravel?

I am thinking a best solution at the same time a lazy solution to filter collection in eloquent result in Laravel. I want to filter all my $videos collection in all my controllers. Is that possible to do without rewriting the controllers and instead put it in the model?
Here is my filter code:
$videos = $videos->filter(function( $video ){
return $video->isPublished();
});
Use query scopes. You can learn from here. And in your case, it would be something like this:
class Video extends Eloquent {
public function scopePublished($query)
{
return $query->where('published', '1');
}
}
class VideosController extends BaseController {
public function showPublishedVideos()
{
return View::make('published_videos')
->with('videos', Video::published()->take(10)->get());
}
}

Laravel Eloquent setting a default value for a model relation?

I have two models:
class Product extends Eloquent {
...
public function defaultPhoto()
{
return $this->belongsTo('Photo');
}
public function photos()
{
return $this->hasMany('Photo');
}
}
class Photo extends Eloquent {
...
public function getThumbAttribute() {
return 'products/' . $this->uri . '/thumb.jpg';
}
public function getFullAttribute() {
return 'products/' . $this->uri . '/full.jpg';
}
...
}
This works fine, I can call $product->defaultPhoto->thumb and $product->defaultPhoto->full and get the path to the related image, and get all photos using $product->photos and looping through the values.
The problem arises when the product does not have a photo, I can't seem to figure out a way to set a default value for such a scenario.
I have tried doing things such as
public function photos()
{
$photos = $this->hasMany('Photo');
if ($photos->count() === 0) {
$p = new Photo;
$p->url = 'default';
$photos->add($p);
}
return $photos;
}
I have also creating a completely new Collection to store the new Photo model in, but they both return the same error:
Call to undefined method Illuminate\Database\Eloquent\Collection::getResults()
Has anyone done anything similar to this?
Thanks in advance!
You could create an accessor on the Product model that did the check for you. Works the same if you just wanted to define it as a method, also (good for if you want to abstract some of the Eloquent calls, use an interface for your Product in case you change it later, etc.)
/**
* Create a custom thumbnail "column" accessor to retrieve this product's
* photo, or a default if it does not have one.
*
* #return string
*/
public function getThumbnailAttribute()
{
$default = $this->defaultPhoto;
return ( ! is_null($default))
? $default->thumb
: '/products/default/thumb.jpg';
}
You might also want to look into Presenters. A bit overkill for some situations, but incredibly handy to have (and abstract things like this away from your models).

Categories