how to use a model function in scope in laravel? - 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();

Related

How to use where in laravel relationships?

I have a relationship on Orders and Customers.
a row from customers table :
customer_id
customer_name
customer_state
customer_city
1
Amin
Yazd
Yazd
a row from orders table :
order_id
customer_id
product_id
factor_id
1
1
3
4
Now I want order rows where customer_state is yazd.
Order.php
public function customer()
{
return $this->belongsToMany(Customer::class, 'orders', 'order_id', 'customer_id');
}
OrdersController.php
$state = "Yazd";
$reportItem = Order::whereHas("customer", function ($query) use ($state) {
$query->where('customer_state', $state)->get();
});
It doesn't work. How I can handle this?
What I understand from the given input you're not using the proper relation here:
The belongsToMany relation is used for many-to-many relations using an allocation table. But you are actually using a one-to-many relation => one customer can have many orders.
In this case you should use a belongsTo relation on Order Model or/and a hasMany relation on Customer Model:
// Order.php
public function customer() {
return $this->belongsTo(Customer::class)
}
// Customer.php
public function orders() {
return $this->hasMany(Order::class)
}
You have put get() at the wrong place. Change this
$reportItem = Order::whereHas("customer", function ($query) use ($state) {
$query->where('customer_state', $state)->get();
});
To
$reportItem = Order::whereHas("customer", function ($query) use ($state) {
$query->where('customer_state', $state);
})->get();
Learn more about laravel relations

Laravel apply where clause to belongsToMany query using with() method

I have a products table with brand_id, and a category_products table with, product_id and category_id.
My Products model :
class Product extends Model
{
public function categories() {
return $this->belongsToMany(Category::class)->withTimestamps();
}
}
My Brands Model :
class Brand extends Model
{
public function products() {
return $this->hasMany(Product::class)->with(['categories']);
}
}
My question is, How can I fetch the products from a Brand instance that belongs to certain category?
$brand = App\Brand::find(1);
/*
I want to be able to do something in the likes of :
*/
$brand->products_which_are_under_this_category
Remove with(['categories']) method from inside products() and write a query like that.
$brand->products()->with(['categories' => function ($query) {
$query->where('category_id', CATEGORY_ID);
}])->get();
hope this can help.

belongsToMany with multiple forieng key in Laravel

By default, laravel support two foreing_key in a pivot table. And both attach and detach methods work properly. But in case we have triple foreign key in a pivot table, what is the proper way to retrieve and insert data to the pivot table.
Tables
Orders:
id bill_number
Products
id name
Colors
id name
order_details:
id order_id product_id color_id price
My Models:
class Order extends Model
{
public function client() {
return $this->belongsTo('App\Client');
}
public function items() {
return $this->belongsToMany('App\Product', 'order_items');
}
}
class Product extends Model
{
//
public function orders() {
return $this->belongsToMany('App\Order', 'order_items');
}
}
class Color extends Model
{
//
public function orders() {
return $this->belongsToMany('App\Order', 'order_items');
}
}
Now I want to get or insert order_items with the following attribute:
order_id product_id color_id price
1 1 1 800
Products:
Shirt
Pant
Jacket
Color:
Gray
White
Black
Now I want to list all items of order with product name, color and price;
If I want to insert an order of shirt with black color and price of 800, how should I figure it out? And Also If I list all order items with color and prices which method should I use?
You could use an intermediate table model. Then you can define your relationship with color on that model.
A little bit "hacky" but you can use withPivot and wherePivot.
class Product extends Model
{
public function orders() {
return $this->belongsToMany('App\Order', 'order_items')->withPivot('color_id', 'price');
}
}
Then you may insert a new record like this:
$product->orders()->attach($orderId, ['color_id' => 1, 'price' => 800]);
And query it with:
$products = Product::whereHas('orders', function ($query) {
$query->where('color_id', '=', 1);
})->get();
I would use a double pivot table using a same order_id for order_product and order_color. Another solution is using a Polymorphic Relationship https://laravel.com/docs/5.8/eloquent-relationships#polymorphic-relationships
I finally made it with sql join as bellow:
class Order extends Model
{
public function items() {
return $this->belongsToMany('App\Product', 'order_details')->withPivot('id', 'price')
->join('colors', 'order_details.color_id', '=', 'colors.id')
->select('products.*', 'colors.name as color_name');
}
}

hasMany relation not working in 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.

Laravel 4 eloquent

Is there any way I can do this with eloquent?
$orders = Customer::with('orders','orders.shop')->where('orders.shop.location','=','Japan')->get()
Customers, orders and shop are tables where 1 customer has many orders and each order has one shop only.
Location is a column in the shop table
I keep getting an error stating orders.shop.location is a column not found.
Anyone can help? Thanks in advance.
You need to defined relationship in your model classes.
Customer model:
public function orders()
{
return $this->hasMany('Order');
}
Order model:
public function customer()
{
return $this->belongsTo('Customer');
}
Then if you want orders of a special customer you just have to do :
$orders = Customer::find($id)->orders;
Or find the user attatched to an order:
$user = Order::find($id)->user;
You can also use the same kind of relation between your Shop and Order model and do something like this:
$orders = Order::with(array('shop' => function($query)
{
$query->where('location', '=', 'japan');
}))->get();
Which should give you all orders for a shop located in japan.
More informations about this type of request:
http://laravel.com/docs/eloquent#eager-loading
in CostumerModel you need set a relationship (One To Many):
public function order()
{
return $this->hasMany('OrderModel', 'foreign_key_in_orderTable');
}
in OrderModel too:
public function costumer()
{
return $this->belongsTo('CostumerModel', 'foreign_key_in_orderTable');
}
then in OrderModel one more relationship with Shop (One To One):
public function shop()
{
return $this->hasOne('ShopModel', 'foreign_key');
}
Now in ShopModel (One To One):
public function order()
{
return $this->belongsTo('OrderModel', 'local_key');
}
query:
$orders = Customer::with('costumer', 'shop')->where('location','=','Japan')->get();

Resources