Laravel 8 - How to add items on list with relationship - php

I have 3 relational tables Cart, CartProducts and Products and in the query I have the low JSON result, but I want to add the product name and description information in cart_products.
Relationships:
Cart with CartProduct
public function cartProducts()
{
return $this->hasMany(CartProduct::class);
}
CartProduct with Product:
public function products()
{
return $this->belongsTo(Product::class);
}
And the query:
$data = Cart::with(['cartProducts'])->where('user_id', '=', $id)->orderBy('id')->get();
[
{
"id": 1,
"user_id": 4,
"amount": 55,
"cart_products": [
{
"id": 1,
"cart_id": 1,
"product_id": 6,
"price": 7.8,
"quantity": 1
},
{
"id": 3,
"cart_id": 1,
"product_id": 5,
"price": 66,
"quantity": 5
}
]
}
]
and this is the result I want
[
{
"id": 1,
"user_id": 4,
"amount": 55,
"cart_products": [
{
"id": 1,
"cart_id": 1,
"product_id": 6,
"name": "Product a"
"price": 7.8,
"quantity": 1
},
{
"id": 3,
"cart_id": 1,
"product_id": 5,
"name": "Product b"
"price": 66,
"quantity": 5
}
}
]
Tables
Cart: id|user_id|amount
--------------------------------------------------
CartProduct: id|cart_id|product_id|price|quantity
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/QDSRb.png

You can simply add an accessor and append it to your CartProduct model.
class CartProduct
{
protected $appends=[
'product_name'
];
public function getProductNameAttribute()
{
return $this->product->name;
}
}

Related

filter one to many relationship base on parent column in laravel

This link Laravel filter one to many relationship has a solution related to my question but I don't know why the solutions are not working for me.
I am currently selecting all the offtaker sites and the apartments the site have but I want to filter the apartment to only apartment that the apartment id is equal to the offtakers apartment_id
Below are my model setup
offtaker model
public function sites(){
return $this->belongsTo(Sites::class,'site_id','id')->select('id','name');
}
site model
public function apartment(){
return $this->hasMany(Apartment::class,'site_id','id');
}
offtaker controller
return Offtakers::select(['id','site_id','offtaker_id','company_id','apartment_id'])
->with('sites',
)->with('sites.apartment',function($query){
$query->where(function($query){
$query->whereRaw("id = (select apartment_id from offtakers where apartment_id=apartments.id)");
});
})->get();
Current out put
{
"id": 2,
"site_id": "1",
"offtaker_id": 6,
"apartment_id": "8",
"sites": {
"id": 1,
"name": "first site",
"apartment": [
{
"id": 8,
"company_id": 1,
"site_id": 1,
"project_id": null,
"apartment_name": "apartment five",
"apartment_description": "test descriptions",
"amount": 8000
},
{
"id": 55,
"company_id": 1,
"site_id": 1,
"project_id": null,
"apartment_name": "apartment eleven",
"apartment_description": "test descriptions",
"amount": 550000
}
]
}
},
Expected out put
{
"id": 2,
"site_id": "1",
"offtaker_id": 6,
"apartment_id": "8",
"sites": {
"id": 1,
"name": "first site",
"apartment": [
{
"id": 8,
"company_id": 1,
"site_id": 1,
"project_id": null,
"apartment_name": "apartment five",
"apartment_description": "test descriptions",
"amount": 8000
},
]
}
},
For you, I suggest you add this package https://github.com/staudenmeir/eloquent-has-many-deep.
This will solve the 2 deep relationships Offtaker - Site - Apartment
Add new relationship in Offtaker model
public function apartments()
{
return $this->hasManyThrough(Apartment::class, Site::class);
}
And to use it:
return Offtaker::select(['id','site_id','offtaker_id','company_id','apartment_id'])
->with([
'sites' => function ($q) {
$q->where('id', 'offtakers.site_id');
},
'apartments' => function ($q) {
$q
->where('site_id', 'offtakers.site_id')
->where('apartment_id', 'offtakers.apartment_id');
}
])
->get();
However the result might a bit different that want you want
{
"id": 2,
"site_id": 1,
"offtaker_id": 6,
"apartment_id": 8,
"sites": [
{
"id": 1,
"name": "first site",
}
],
"apartments": [
{
"id": 8,
"company_id": 1,
"site_id": 1,
"project_id": null,
"apartment_name": "apartment five",
"apartment_description": "test descriptions",
"amount": 8000
}
]
},
....
I hope this can help you out.

Why does hasOneThrough display inappropriate data?

In this example, I get a valid array, but with a nested array that has inconsistencies
{ "order_id": 1, "status": 20,
"product_id": [
{ "id": 1, "order_id": 1, "product_id": 8, "quantity": 1, "price": 141 },
{ "id": 2, "order_id": 1, "product_id": 30, "quantity": 2, "price": 509 },
{ "id": 3, "order_id": 1, "product_id": 21, "quantity": 1, "price": 399 } ],
"name": { "id": 1, "name": "Product_1", ... "laravel_through_key": 1 }
since there are three products in the first order, in the second order I already get "name": "Product_4" i.e. the interval is correct, and the order is in the order of the prods table, not in the order_products table:
{ "order_id": 2, "status": 10,
"product_id": [
{ "id": 4, "order_id": 2, "product_id": 1, "quantity": 3, "price": 320 },
{ "id": 5, "order_id": 2, "product_id": 11, "quantity": 2, "price": 953 },
{ "id": 6, "order_id": 2, "product_id": 20, "quantity": 1, "price": 911 } ],
"name": { "id": 4, "name": "Product_4", ... "laravel_through_key": 2 }
How can I make the product names correspond to product_id in order_products?
here is my table diagram:
orders
id - integer
status - integer
order_products
id - integer
order_id - integer
product_id - integer
quantity - integer
price - integer
prods
id - integer
name - string
model Order
class Order extends Model
{
public function order_product()
{
return $this->hasMany('App\Order_product');
}
public function prod()
{
return $this->hasOneThrough('App\Prod', 'App\Order_product', 'order_id', 'id', 'id' );
}
model Prod
class Prod extends Model
{
public function order()
{
return $this->hasMany('App\Order', 'order_products');
}
модель Order_product
class Order_product extends Model
{
protected $fillable = ['order_id', 'product_id', 'quantity', 'price'];
}
Resource Order
class Order extends JsonResource
{
public function toArray($request)
{
return [
'order_id' => $this->id,
'status' => $this->status,
'product_id' => $this->order_product,
'name' => $this->prod,
];
}
controller Order
class OrderController extends Controller
{
public function index()
{
$orders = Order::with(['order_product', 'prod'])->get();
return OrderResource::collection($orders);
}
}
As per your DB design, it suppose to be belongsToMany between order, and product.
Order model
public function products()
{
return $this->belongsToMany('App\Prod', 'Order_product', 'order_id','product_id');
}
Product Model
public function orders()
{
return $this->belongsToMany('App\Order', 'Order_product','product_id', 'order_id');
}

Laravel pass parameter to its distant relationship (translate children)

I have categories with id, parent_id, slug , pivot table category_language which columns are id,category_id,language_id,value
As you can see I can translate parent category, but can't send desired $lang_id to children translations, so each children having all translations
here is what I get:
{
"id": 1,
"parent_id": 0,
"slug": "personal-computers",
"created_at": "2019-12-27 15:05:31",
"updated_at": "2019-12-27 15:05:31",
"children": [
{
"id": 3,
"parent_id": 1,
"slug": "accessories-for-pc",
"created_at": "2019-12-27 15:05:32",
"updated_at": "2019-12-27 15:05:32",
"translations": [
{
"id": 1,
"code": "en",
"name": "English",
"pivot": {
"category_id": 3,
"language_id": 1,
"value": "Acc for PC",
"id": 7
}
},
{
"id": 2,
"code": "ru",
"name": "Русский",
"pivot": {
"category_id": 3,
"language_id": 2,
"value": "Аксессуары для ноутбуков и ПК",
"id": 8
}
},
{
"id": 3,
"code": "ro",
"name": "Romana",
"pivot": {
"category_id": 3,
"language_id": 3,
"value": "aksessuari-dlya-noutbukov-i-pk-ro",
"id": 9
}
}
]
}
],
"translations": [
{
"id": 1,
"code": "en",
"name": "English",
"pivot": {
"category_id": 1,
"language_id": 1,
"value": "PC",
"id": 1
}
}
]
}
Controller:
return Category::with('children')
->with(array('translations'=>function($query) use ($lang_id){
$query->where('language_id',$lang_id);
}))
->where('parent_id',0)->first();
Model
class Category extends Model
{ ..
public function translations()
{
return $this->belongsToMany('App\Models\Translation','category_language', 'category_id' ,'language_id' )->withPivot('value','id');
}
public function children()
{
return $this->hasMany( 'App\Models\Category' , 'parent_id' , 'id' )->with('translations');
}
}
you can add condition in children method
public function children()
{
return $this->hasMany( 'App\Models\Category' , 'parent_id' , 'id' )->with('translations')->where('language_id', 1);
}
Dry7 answer was close to the one I've implemented later, so I upvoted him.
Finally in model I've added: ...->where('language_id',helper_SetCorrectLangIdForQuery());
and function helper_SetCorrectLangIdForQuery is using global helper of Laravel request()->lang . If lang=enz, than it takes default language from another helper.

How to calculate the total amount for similar keys in PHP using Laravel eloquent/collections

I currently have the below json response that the API is returning. I am able to return it using Laravel Eloquent. There are several users and each user has a several receipts. A receipt has types and status. I want to try to get the total sum amount for each receipt that is related to its type and status. I was able to return the below json response using
$this->user->with('receipts')->has('receipts')->get(['id', 'name']);
I have tried using multiple laravel collections methods https://laravel.com/docs/5.8/collections#available-methods
But I am still unable to get the desired response.
{
"id": 1,
"name": "kent",
"receipts": [
{
"id": 1,
"user_id": 1,
"type_id": 1,
"status": 0,
"amount": 100
},
{
"id": 2,
"user_id": 1,
"type_id": 1,
"status": 0,
"amount": 100
},
{
"id": 3,
"user_id": 1,
"type_id": 2,
"status": 1,
"amount": 50
},
{
"id": 4,
"user_id": 1,
"type_id": 2,
"status": 0,
"amount": 30
},
{
"id": 5,
"user_id": 1,
"type_id": 2,
"status": 0,
"amount": 30
},
{
"id": 6,
"user_id": 1,
"type_id": 1,
"status": 0,
"amount": 20
},
{
"id": 7,
"user_id": 1,
"type_id": 1,
"status": 1,
"amount": 10
}
]
},
{
"id": 2,
"name": "allison",
"receipts": [
{
"id": 9,
"user_id": 2,
"type_id": 1,
"status": 0,
"amount": 20
}
]
}
]
I expect to get the below
{
"id": 1,
"name": "kent",
"receipts": [
{
"performance and deleted": 220,
"performance and not deleted": 10,
"project and deleted": 60,
"project and deleted": 50
}
]
},
{
"id": 2,
"name": "allison",
"receipts": [
{
"performance and deleted": 20,
"performance and not deleted": 0,
"project and deleted": 0,
"project and not deleted": 0
}
]
}
]
You should be able to get the sum of amount with
$this->user->with(['receipts' => function($query) {
$query->selectRaw("SUM(amount) as amount, type_id, status, user_id")->groupBy('type_id', 'status', 'user_id');
}])->has('receipts')->get(['id', 'name']);
You can use collection methods to get the desired output
$this->user->with(['receipts' => function($query) {
$query->selectRaw("SUM(amount) as amount, type_id, status, user_id")->groupBy('type_id', 'status', 'user_id');
}])->has('receipts')->get(['id', 'name'])
->each(function ($user) {
$user->setRelation(
'receipts',
$user->receipts->mapWithKeys(function ($receipt) {
return [
$receipt->type_id . ' and ' . $receipt->status => $receipt->amount // format the key as you wish
];
})
);
})
You may use foreach and other loops to make the json you want!
use This function to convert your collection to the json you want :
public function convert(Collection $collection)
{
$result_collection = $collection;
$payment_collections = [];
foreach ($result_collection->receipts as $receipt)
{
$payment["type_id${receipt->type_id} and status${$receipt->status}"] = $receipt->amount;
}
$result_collection->receipts = $payment_collections;
return $result_collection;
}
And This is same way to get total amount. Just put an foreach and add each amount to a variable that initialized with 0;
and there is other ways like change toArray function in your Resource Collection.
I wrote the script assuming your data is a PHP array so you may change some part of my code.
For example you may change:
$row['receipts']
// To
$row->receipts
anyway
// The function in your controller
function index(){
$users=User::whereHas('receipts')->with('receipts')->get()->toArray();
return convert($users);
}
// The helper function
function convert($data){
$data=collect($data);
$allTypes=[];
$allStatuses=[];
return $data->each(function($row) use (&$allTypes,&$allStatuses){
$types=collect($row['receipts'])->pluck('type_id')->toArray();
$statuses=collect($row['receipts'])->pluck('status')->toArray();
$allTypes=array_unique(array_merge($allTypes,$types));
$allStatuses=array_unique(array_merge($allStatuses,$statuses));
})->map(function ($row,$index) use (&$allTypes,&$allStatuses){
$result=[];
$receipts=collect($row['receipts']);
foreach ($allTypes as $type){
foreach ($allStatuses as $status){
$result["type_id {$type} and status {$status}: "]=$receipts->where('type_id',$type)->where('status',$status)->sum('amount');
}
}
$row['receipts']=$result;
return $row;
});
}

Laravel 5.3 - Merge array based on 2 matching keys?

Given the following two arrays, how can they be merged efficiently to result in the third array?
productData
$productData =
[
{
"product_id": 4,
"type": "electronic",
"name": "monitor",
"specs": {
"HDMI": true,
"VGA": false
}
},
{
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
},
{
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
]
products
$products =
{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"product_type": "electronic",
"product_id": 6
},
{
"id": 6,
"type": "electronic",
"product_id": 5
},
{
"id": 9,
"type": "kitchen",
"product_id": 4
}
]
}
productsFinal ($productData merged into $products - based on matching combo of product_id/product_id and type/product_type)
$productsFinal =
{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"product_type": "electronic",
"product_id": 6,
// How to merge product data and wrap with "data" key
"data": {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
},
{
"id": 6,
"type": "electronic",
"product_id": 5,
// How to merge product data and wrap in "data" key
"data": {
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
}
},
{
"id": 9,
"type": "kitchen",
"product_id": 4,
// How to merge product data and wrap in "data" key
"data": {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
}
]
}
I tried different things for the outcome in a foreach loop but still cannot get it to render as intended:
foreach($productData as $productDataItem) {
// when $productDataItem.product_id == $product.product_id && $productDataItem.type == $product.product_type
// move the matching $productDataItem object into matching $product object, wrapped in a new "data" key
}
I don't know Laravel too well. However you can join your data objects quite easily:
<?php
$productData = json_decode('[
{
"product_id": 4,
"type": "electronic",
"name": "monitor",
"specs": {
"HDMI": true,
"VGA": false
}
},
{
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
},
{
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
]');
$products = json_decode('{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"type": "electronic",
"product_id": 6
},
{
"id": 6,
"type": "electronic",
"product_id": 5
},
{
"id": 9,
"type": "kitchen",
"product_id": 4
}
]
}');
// combine both data objects
foreach($products->data As &$p) {
foreach($productData As $d) {
if(property_exists($p, "product_id") && property_exists($d, "product_id") && property_exists($p, "type") && property_exists($d, "type")) {
if($p->product_id==$d->product_id && $p->type==$d->type) {
//$p = (object) array_merge((array) $p, (array) $d);
$p->data = $d; // updated answer
continue;
}
}
}
}
echo("<pre>");
echo json_encode($products, JSON_PRETTY_PRINT);
?>
You can test the code here: http://sandbox.onlinephpfunctions.com/code/98a50c35ee32c30f0d2be1661f7afb5895174cbe
Update: http://sandbox.onlinephpfunctions.com/code/aeebfdcf4f4db5e960260e931982570cfed19e0e
I would suggest to check this package dingo/api. I assume you want to display some kind of JSON response. Take a look at Transformers. You can do something like this :
<?php
namespace App\Http\Transformers;
use App\Http\Controllers\ProductData;
use League\Fractal\TransformerAbstract;
class ProductsDataTransformer extends TransformerAbstract
{
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(ProductData $productdata)
{
return [
'id' => $productdata->id,
'product_type' => $productdata->product_type,
'product /*or data*/' => Product::find($productdata->product_id),
];
}
}
This would find the product by it's ID and look like this :
{
"id": 4,
"product_type": "electronic",
"product" {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
},
},
You can then also create a transformer for Product to take care of your specs attribute to do the same thing.

Categories