hasMany relation not working in laravel - laravel

My cart model has:
public function product() {
return $this->hasMany('App\Product');
}
My Product Model has:
public function cart() {
return $this->belongsTo('App\Cart');
}
My database looks like:
Cart Table:
Product Table:
My cart migration contains:
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
My controller contains:
public function index()
{
if(Auth::check())
{
$user = Auth::user();
$cart = Cart::where('user_id',$user->id)->get();
return view('products.cart')->withCart($cart);
}
else {
return view('products.cart');
}
}
It checks if user is logged in then it get details of all cart where user id matches with current user. This is working.
But when I go to product.cart page I try:
#foreach($cart->product as $p)
{{dd($p->name)}}
#endforeach
I get error:
Undefined property: Illuminate\Database\Eloquent\Collection::$product (View: D:\wamp64\www\ecommerce\resources\views\products\cart.blade.php)
Can you tell me how to get value from products table with this relationship.
Thanks!
Edit:
After trying different method I got:
Cart Model:
public function product() {
return $this->hasMany('App\Product','id','product_id');
}
Cart Controller:
public function index()
{
if(Auth::check())
{
$user = Auth::user();
$cart = Cart::where('user_id',$user->id)->first();
return view('products.cart')->withCart($cart);
}
else {
return view('products.cart');
}
}
Now I am able to get the first product that matches with id.
Is there a way to get all product that matches

You use $...->get() returns collection. Use first() instead.
$cart = Cart::where('user_id',$user->id)->first();
if($cart) {
return view('products.cart')->withCart($cart);
} else {
abort(404);
}

Go to User's model:
public function products()
{
return $this->hasManyThrough('App\Product', 'App\Cart');
}
Now, in your controller you'll fetch the logged user's cart. And pass it to the view. e.g
return view('cart.show', ['cart' => Auth::user()->cart]);
Then your foreach will work like expected.
PS: You may have to specify ids on the hasManyThrough relation if you're not using the convention naming for database tables. i.e Model singular (Product) to pluralized tables (products).

Try out the following code.
In your Cart Model
public function product(){
return $this->belongsTo('App\Product','id','product_id')
}
In your Controller
$cart = Cart::where('user_id',$user->id)->first();

In your Cart model you need to specify relation for products:
public function products(){
//return $this->hasMany('App\Product'); should be changed to
return return $this->belongsToMany('App\Product','carts_products', 'cart_id', 'product_id'); // see link to tutorial below.
}
With this relation you can enable lazy loading of products when you get Cart object for user to achieve this in your controller you need:
public function index()
{
if(Auth::check())
{
$user = Auth::user();
$cart = Cart::with('products')->where('user_id',$user->id)->first();
//we get cart via first() as we need single instance of cart not an array of carts,
//even there's only one cart per user get() will return an array of one item
// with('products') lazyloads linked products to cart as you get cart instance
return view('products.cart')->with('cart',$cart);
}
else {
return view('products.cart');
}
}
Later in your view you'll be able to iterate over lazy loaded products array like:
#foreach($cart->products as $p)
{{dd($p->name)}}
#endforeach
Think of creating relations between models in terms of how you'll access them in future, most of the time you don't need to overspecify all relations between objects, as only some of them you'll realy use in code.
Hope it'll help you.
EDIT
change relation type to belongsToMany and it should work, as your Cart has many Products. You still need to get one single Cart instance with first() method byt related Products will be loaded as you need.
Important:
Look at this tutorial https://scotch.io/tutorials/a-guide-to-using-eloquent-orm-in-laravel as you need to change your database schema to achieve this. Currently your schema allows you to have only one product per cart. Look into this example where they create picnics to bears table bears_picnics - in your case you'll have products to carts table carts_products walk through tutorial in you case cart is bear and picnics are products.
Example carts_products schema:
Schema::create('carts_products', function(Blueprint $table)
{
$table->increments('id');
$table->integer('cart_id'); // the id of cart
$table->integer('product_id'); // the id of product in cart
$table->timestamps();
});
remove product_id field from you cart schema as it's useless in such case. if you need cascade deletion specify it on carts_products table.
Happy codding! Thank you for a good question:) making us to make a research deeper.

Related

Laravel8 Model Injection in Controller with Relationship

I have 2 models: Company and Category
Company has many Categories, and Category belongs to Company
// App\Models\Company.php
public function categories()
{
return $this->hasMany(Category::class);
}
and
// App\Models\Category.php
public function company()
{
return $this->belongsTo(Company::class);
}
All ok with relationship, now I have a route
/company/{company}/category/{category}
And in the controller I have the next code:
public function foo(Company $company, Category $category)
{
///
}
And I get a 404 not found. I think it is because the relationship between Company and category fails.
If I do the next:
public function foo($company, Category $category)
{
dd(
Company::find($company)->categories()->where('id', $category->id)->first(),
$category
);
}
I get the correct Category in both way, so, the relationship in model I guess is ok. Also, I can add/remove with relationship, so again, I guess is ok.
Now, my question is, how I can inject 2 model with relationship in the controller?
Cuz all way I'm getting error 404.
Thanks!

How to get product name from pivot table using laravel?

I am trying to get product name from pivot table but unfortunately i have no idea how can i get product name from pivot table please help me thanks.
Product Model
public function category()
{
return $this->belongsToMany('App\ProductCategory', 'product_category', 'product_id', 'mf_product_category_id');
}
productcategory model
public function products()
{
return $this->belongsToMany('App\Product', 'product_category', 'mf_product_category_id', 'product_id');
}
controller
public function getproduct(Request $request)
{
// getting category Id
$categoryId = $request->category;
// getting product Id
$name = trim($request->product);
$productId = Product::where('name', $name)->pluck('id');
$getProductcategory = ProductCategoryCount::whereIn('mf_product_category_id', $categoryId)->whereIn('product_id', $productId)->get();
return $getProductcategory;
// return response()->json($getproduct);
}
As stated in laravel doc, you can retrieve pivot table extra data using withPivot on the relationship.
Define category in the Product Model
public function category()
{
return $this->belongsToMany('App\ProductCategory', 'product_category', 'product_id', 'mf_product_category_id')
->withPivot('Your_extra_data_on_pivot_table');
}
And by querying products, you can get category (which I suggest to be categories). You can retrieve it like this $products->first()->pivot->{name of_your_extra_data_on_pivot_table}
It is more optimized if you eager load this relationship.

how to use a model function in scope in laravel?

I have two tables
1- Products
2- Discounts
I want to select all discounted products, but there is a problem, in Discount table there is no product_id, there is array of product id like : ["1","4","23"] which means this discount is used for products with id of 1 or 4 or 23.
I already created a function in my product model that defines if the product has discount or not and use it like :
$product->hasDiscount(); //returns 1 or 0
what I need actually?
I need an scope for my product model like below to use in my select query to get all discounted products:
public function scopeDiscounted($query)
{
return $query->where($this->hasDiscount() , '=' , 1);
// I know this code is wrong, I just want to explain the needed code result
}
Lets start by normalizing your data, create the following table and loop in a migration.
public function up()
{
Schema::create('discount_product', function (Blueprint $table) {
$table->unsignedInteger('discount_id');
$table->unsignedInteger('product_id');
// add foreign keys if you like
});
Discount::all()->each(function (Discount $discount) {
$productIds = json_encode($discount->productIds);
foreach ($productIds as $productId) {
$discount->saveMany(Product::whereIn('id', $productIds)->get());
}
});
}
To make this migration work, you have to create the relationships before running the migration. I was lazy using models in the migration, the best approach is to use the DB facade.
class Discount {
public function products()
{
return $this->belongsToMany(Product::class);
}
}
class Product {
public function discounts()
{
return $this->belongsToMany(Discount::class);
}
}
Now you should be able to get all discounted products and you could put this in your scope.
$discountedProducts = Product::whereHas('discounts', function ($query) {
$query->where('active', true);
$query->whereDate('expire_at', '>=', now())
})->get();

When i return one product with its images in morph relation it returns all the products

I have a problem when I make a route to get one product by it's id
Route::get('product/{product}', 'Api\ProductController#show');
and my show method looks like this:
public function show(Product $product)
{
return $product->with('images')->get();
}
it should return back to me only one product instead it returns all the products
The morph relation in the Product model
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
Actually what masses it up is the use of get() that returns a collection of all the products that way.. Try this instead:
public function show(Product $product)
{
$product->load('images');
return $product;
}

Same Foreing Key Twice That Refers to One Model

I need to make an advice platform for products. User can make advices under product section. So product has many advices. Also advices belongsTo product. But on product_advices table i have product_id and product_advice_id these both refers to id on products table.
So here is the problem. I can take advices from product_advices table which refers to product_id. But how can i take the other one as product.
product->advices to show advice and user message
each advices as advice and advice->product->name to show adviced product name
I couldn't make a relationship with eloquent between them.
//Product Model
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
//ProductAdvice Model
protected $table = 'product_advices';
public function product() {
return $this->belongsTo('App\Product', 'product_id');
}
//Product Advice Table
Schema::create('product_advices', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->text('body')->nullable();
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('product_advice_id')->unsigned();
$table->foreign('product_advice_id')->references('id')->on('products')->onDelete('cascade');
$table->timestamps();
});
For example :
Iphone has many advices by users.
Iphone->advices brings the advices from product_advices that Iphone belongs to product_id column.
When User recommends Samsung Galaxy S10 to Iphone. Samsung refers to product_advice_id column on product_advices table. But how to show Samsung Galaxy S10 as a product.
$advice->product->name returns Iphone instead of Samsung Galaxy S10.
EDIT 2
After your answer, I understood what you want.
Just update your ProductAdvice model to have another relationship, like:
class ProductAdvice extends Model
{
public function product()
{
return $this->belonsTo('App\Product', 'product_id');
}
public function related()
{
return $this->belonsTo('App\Product', 'product_advice_id');
}
}
Then you can do:
$product = Apple
$product->advices->message = 'blablabla'
$product->advices->related = 'ProductX'
If you need to to the inverse order, $advice->product->related add the same relationship on your Product model.
EDIT 1
Sorry, you edited the post after my answer...
Could you explain the 'product_advice_id' necessity? You already have the product_id.
Unless you want a pivot table, like: Product -> ProductAdvice -> Advice, which is not needed in your case, since you can just put the advice information in the Advice table and link it to the product (belongsTo), removing the necessity of the ProductAdvice pivot.
I think your structure should be like this:
Product model with all the product data
ProductAdvice model with product_id and the advice information (message, rating, etc)
Then, your Product hasMany Advices and your Advice belongsTo Product:
class Product extends Model
{
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
}
class ProductAdvice extends Model
{
public function product()
{
return $this->belonsTo('App\Product', 'product_id');
}
}
Finally, you can query the advices of a specific Product to get the advice information:
$product->advices->message
$product->advices->rating
Or query the product name if you have an advice:
$advice->product->name
So, If understand well, you want and advice from product A to return the name of product B.
You can make this by creating multiple method related to the same model.
Your models will look similar at this:
class Product extends Model
{
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
public function relations()
{
return $this->hasMany('App\ProductAdvice', 'related_id');
}
}
class ProductAdvice extends Model
{
public function product()
{
return $this->belonsTo('App\Product', 'product_id');
}
public function relation()
{
return $this->belonsTo('App\Product', 'related_id');
}
}
Then you will have to create another column in your talbe name :related_id (it could be something else, just made it match in your model. You can also change relations() and relation() method to whatever name you want.)
After this it's how you store your data. You have to make your code associate the good product model into your product_id and into related_id. This way. You can have $advice->product->name === 'iPhone' && $advice->relation->name === 'Samsung S10'
Found a solution like this for now.
//ProductAdvice Model
protected $table = 'product_advices';
public function user()
{
return $this->belongsTo('App\User');
}
public function product() {
return $this->belongsTo('App\Product', 'product_id');
}
public function advicedProduct()
{
return Product::where('id', $this->product_advice_id)->first();
}
//Product Model
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
How i show it on view
#foreach($product->advices as $advice)
<li>{{ $advice->body }} - {{ $advice->advicedProduct()->name }}</li>
#endforeach

Resources