Nested Many to Many Relationship in Laravel - laravel

I have the table structures as
products id
packs id
pack_products pack_id, product_id
pack_optional_products pack_id, product_id
So in the above table, pack will have 2 type of products, compulsory products and optional products, mean if customer buy the pack the products which are attached in that pack as compulsory will automatically ordered, but customer also can see if there is any optional product in the pack so customer can also buy that product too.
Now to manage orders I have the following table.
orders id, customer_id
order_pack order_id, pack_id
Here everything works fine, using attach method as $order->packs()->attach($pack["pack_id"]);
So here problem starts ( when I tried to add optional ordered products in the ordered pack), to manage optional products order, I have created the following table
order_pack_product order_id, pack_id, pack_optional_product_id
I have created a model as.
class Order extends Model
{
//
public function packs(){
return $this->belongsToMany(Pack::class)->withTimestamps()->withPivot('name');
}
public function parent() {
return $this->belongsTo(ParentCustomer::class);
}
}
class OrderPack extends Model
{
//
public function optionalProducts()
{
return $this->belongsToMany(PackOptionalProduct::class, 'order_pack_product')->withTimestamps();
}
}
And then I call the method as.
$order->packs()->optionalProducts()->attach($optionalProduct["pack_optional_product_id"]);
and I'm getting this error.
BadMethodCallException: Call to undefined method
Illuminate\Database\Eloquent\Relations\BelongsToMany::optionalProducts()
in file
/.../....../laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php
on line 50
SO complete code related to above tables will be this.
$order = new Order();
$order->parent_id = $parent->id;
$order->school_id = $schoolId;
if($order->save()){
foreach($json["packs"] as $pack){
$order->packs()->attach($pack["pack_id"], ["child_name" => $pack["child_name"]]);
foreach($pack["optional_products"] as $optionalProduct){
$order->packs()->optionalProducts()->attach($optionalProduct["pack_optional_product_id"]);
}
}
return response()->json(["status" => "ok", "order_id" => $order->id]);
}else{
return response()->json(["status" => "failed"]);
}

optionalProducts() is method of model, not of relation packs().
Method $order->packs() - returns relation object, attribute $order->packs - returns collection of pack models.
You need to iterate trough collection, to attach optionalProducts to every one pack model.
foreach($pack["optional_products"] as $optionalProduct){
foreach($order->packs as $item){
$item->optionalProducts()->attach($optionalProduct["pack_optional_product_id"])
}
}
Perhaps, this code will not correct the error, then you need to correct your code logic.

Related

Is laravel seeding as hard as I see it or I'm stumbling with something that's not that complicated?

The last few days I've been rocking my head against the wall with the seeders. I can't seem to get the hang of it.
The relationships are very simple:
A Brand hasMany products and each product belongs to a single brand.
A Category hasMany products and each product belongs to a single category.
Given that, I'm creating 5 categories at the beginning so I can retrieve a random one later.
I'm also creating 10 brands, and for each brand I'm creating 50 products and make them belong to that brand. Then I create the relationship with the product and the category, retrieving a random category for each product.
Im getting this error:
PDOException::("SQLSTATE[HY000]: General error: 1364 Field 'brand_id' doesn't have a default value")
I dont understand why I'm getting this error because I'm creating the relationship prior to creating the products:
$brand->products()->saveMany(factory(App\Product::class, 50).
public function run()
{
$users = factory(\App\User::class, 1000)->create();
$categories = factory(\App\Category::class, 5)->create();
factory(App\Brand::class, 10)->create()->each(function ($brand) {
$brand->products()->saveMany(factory(App\Product::class, 50)->make()->each(function ($product) use ($brand) {
$product->category()->associate($this->getRandomCategory());
$product->save();
}));
});
}
private function getRandomCategory() {
return \App\Category::all()->random();
}
private function getRandomUser() {
return \App\User::all()->random();
}
I don't know if I'm making a big deal out of seeding but it just seems complex to me. Maybe I'm taking a wrong approach using the factories. Is there any great tutorial for seeding out there?
Thanks in advance!
This is actually not a seeding problem. The PDO-driver already tells you about the issue:
brand_id does not a have a default value
Probably Laravel assumes, that the id column does not need a default and therefore does not insert it with an id. But the column seems to have no default definition in the database (should be smth. like AUTO_INCREMENT). And that's the reason why you receive an error from the database.
The problem was this one:
The saveMany method gets called after the each function is done, so I was kinda saving the product before it added the brand relationship. Thats why it couldnt assign the foreign_key brand_id. This is the block of code working:
public function run()
{
$users = factory(\App\User::class, 1000)->create();
$categories = factory(\App\Category::class, 5)->create();
factory(App\Brand::class, 10)->create()->each(function ($brand) {
$brand->products()->saveMany(factory(App\Product::class, 50)->make()->each(function ($product) use ($brand) {
$product->category()->associate($this->getRandomCategory());
}));
});
}
private function getRandomCategory() {
return \App\Category::all()->random();
}
private function getRandomUser() {
return \App\User::all()->random();
}

Problem in retrieving data from different tables in laravel 6

I am retrieving data from 3 tables account, product and stock. Account and product has one to many relation and product and stock has one to many relation, I am trying to retrieve data using account table, here is my query:
$account=Account::find(1);
$result=$account->product->stock;
echo($result);
I am getting result but problem is that I need some specific columns from stock table and when I write following code I am getting error property does not exist:
$result=$account->product->stock->model;
//in controller first get account data
$account=Account::find(1);
$products=$account->product;
foreach($products as $product){
$productData=$product->stock;
}
//in account model
use App\Product;//model name
function product(){
return $this->hasMany(Product::class, 'account_id');
}
//in product model
use App\Stock //model name
function stock(){
return $this->hasMany(Stock::class, 'product_id');
}

How can I link 2 existing models in my laravel code?

I have two models customer and orders. They are already fecthed separately
$customers = customer::all();
$orders = orders::all();
customerID=1 has orderID : 1, 2,4 customerID=2 has orderID : 3,5,9
They are related (hasMany, belongsTo) but the problem is inside my for a certain reason they are separated but I want to send them as response in API using toJson or ToArray as one data having the orders nested to their correct customers.
How can I achieve that linking to have at the end one variable $customersWithOrders that should be transformed to JSON ?
I am using laravel 5.5
I don't know what the context is. Defining relationships as other answers mentioned is a good solution.
In addition, I recently read a pretty good article about this specific scenario.
So you can also do something like this, if you have already retrieved customers and orders:
$customers = Customer::all();
$orders = Order::all();
return $customers->each(function ($customers) use ($orders) {
$customer->setRelation('orders', $orders->where('customer_id', $customer->id));
});
If you already have a relation you just use it. For example, in model Customer.php:
public function orders()
{
return $this->hasMany(Order::class);
}
Then you'd get customer orders by calling $customer->orders
If you already have defined relations, you can simply fetch data with eager loading
// in customer model
public function orders()
{
return $this->hasMany(orders::class, 'orderID');
}
// in controller
$customersWithOrders = customer::with('orders')->get();
return response()->json(['customersWithOrders' => $customersWithOrders]);
// in js
for (let customer in response.customersWithOrders){
let orders = customer.orders
}

Laravel whereHas ManyToMany relation but then order by a value in the third table

I have 3 tables
entry (
id,
title,
other_stuff)
entry_award (
id,
award_id,
entry_id)
award (
id,
name,
type)
I am trying to create a laravel query which lets me get all the entries that have awards, and order them by award.type ASC
$Entries = Entry::with('award')
->whereHas('award', function ($query) {
$query->orderBy('award.award_id','ASC');
})->paginate(20);
But this doesn't work.
This is the sql version of it
SELECT DISTINCT entry.*
FROM entry, award, entry_award
WHERE entry.id = entry_award.entry_id
AND award.id = entry_award.award_id
ORDER BY award.type ASC;
Now I tried to just use the raw sql for it, but the problem seems to be that laravel does not then recognize the result as Entry models/objects. And i need to get other Entry relations later on in the html via blade.
So how can I either make a query-builder query that gets me all entries that have awards and orders them by award.type value
or use the raw sql but have Laravel see it as an array of Entry
objects instead of just an array of JSON values.
class Entry extends Model {
public function entry_award(){
return $this->belongsToMany('App\Award', 'entry_award');
}
}
class Award extends Model {
public function entries() {
return $this->belongsToMany('App\Entry', 'entry_award');
}
}

Laravel returns a Collection with duplicates of the first model

I'm developing a Laravel 5.7 (API) application with a PostgreSQL database behind it. The relevant Models are: User (customers and employees), Car, and Request.
An employee User creates a Request for a Car, that belongs to a customer User.
The relationships are:
Car (as customer) : User = n:m
Car : Request = 1:n
User : Request (as employee) = 1:n
(The data design is suboptimal, to put it mildly, but anyway, it's the given reality for now.)
Now to the actual issue. I want to display all Requests of a customer User:
Request::query()
->join('user_car', 'user_car.car_id', '=', 'request.car_id')
->join('user', 'user.id', '=', 'user_car.user_id')
->where('user.id', '=', $customer->id)
->select()
->get();
The customer with the given $customer->id has n Requests. And the length of the result Collection of the call above is correct. But all these n entries are duplicates of the first one. Means: I'm getting a list with n instances of Request#1.
Why does the first call return a list of references to the same Model object? Is it a (known) bug?
ADDITIONAL INFORMATION
Relationships:
class User extends \Illuminate\Foundation\Auth\User
{
// ...
public function cars()
{
return $this->belongsToMany('App\Car', 'user_car')->withTimestamps();
}
public function requests()
{
return $this->hasMany(Request::class, 'user_id');
}
}
class Car extends Model
{
// ...
public function users()
{
return $this->belongsToMany('App\User', 'user_car')->withTimestamps();
}
public function requests()
{
return $this->hasMany(Request::class);
}
}
class Request extends Model
{
// ...
public function car()
{
return $this->belongsTo(Car::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
The query is correct.
I logged the database requests, got the generated statement
SELECT *
FROM "request"
INNER JOIN "user_car" ON "user_car"."car_id" = "request"."car_id"
INNER JOIN "user" ON "user"."id" = "user_car"."user_id"
WHERE "user"."id" = 1;
..., and executed it manually. The result table contains as expected n different entries.
NOT just references
The result Collection's entries instances references to the different objects:
$test1 = $resultCollection->first();
$test2 = $resultCollection->last();
$test3 = spl_object_hash($test1);
$test4 = spl_object_hash($test2);
Xdebug output:
$test3 = "0000000077505ccd000000007964e0a8" <-- ccd0
$test4 = "0000000077505c33000000007964e0a8" <-- c330
Workaround
I found a workaround. This call
Request::whereIn('car_id', $customer->cars()->pluck('id')->toArray())->get();
... retrieves the correct/expected set of model.
First, note that your object hashes are not actually identical, and you're likely dealing with two separate instances.
What you're likely experiencing is an issue with ambiguous column names. When you JOIN together multiple tables, any matching/duplicate column names will contain the value of the last matching column. Your SQL GUI/client usually separates these. Unfortunately Laravel doesn't have a prefixing mechanism, and just uses an associative array.
Assuming all of your tables have a primary key column of id, every Request object in your result set will likely have the same ID - the User's ID you pass in the WHERE condition.
You can fix this in your existing query by explicitly selecting the columns you need to prevent ambiguity. Use ->select(['request.*']) to limit the returned info to the Request object data.

Resources