How can I rewrite/reformat an array? - php

I am developing an API but I am having a problem connecting it to a payment platform, because they ask for the preference with the following format:
'items' => [
[
'id' => 1,
'title' => 'Product 1',
'description' => 'Some description',
'picture_url' => 'img.png',
'quantity' => 1,
'currency_id' => 'USD',
'unit_price' => 20
],
[
'id' => 2,
'title' => 'Product 2',
'description' => 'Some description',
'picture_url' => 'img.png',
'quantity' => 1,
'currency_id' => 'USD',
'unit_price' => 25
]
]
but I am receiving my data from my items in the cart like this:
'items' => json_encode(new CartItemCollection($items))
And that collection (my collection CartItemCollection) have this format:
{
"Items":[
{
"productID":1,
"inventoryID":1,
"name":"Product 1",
"Quantity":1,
"price":20,
"image":"img.png"
},
{
"productID":2,
"inventoryID":1,
"name":"Product2 "
"Quantity":1,
"price":25,
"image":"img.png"
}
],
"items_count":2,
"products_count":2
}
So I'm sending (which is wrong):
'items' => "Items":[
{
"productID":1,
"inventoryID":1,
"name":"Product 1",
"Quantity":1,
"price":20,
"image":"img.png"
},
{
"productID":2,
"inventoryID":1,
"name":"Product2 "
"Quantity":1,
"price":25,
"image":"img.png"
}
],
"items_count":2,
"products_count":2
How can I rewrite or re format that: json_encode(new CartItemCollection($items)) to get the rigth array?
I kind of need to do this:
foreach (Items) in my collection, do: ProductID (mine) rewrite as id (plataform), Quantity (mine) rewrite as quantity (plataform), price (mine) rewrite as unit_price (plataforms) etc etc..
Thank you in advance :)

According to the official documentation, a different approach could be define another Collection Resource class to customise the response.
return new CustomCartItemCollection($items);
Inside the CustomCarItemCollection.php class:
use Illuminate\Http\Resources\Json\ResourceCollection;
class CustomCarItemCollection extends ResourceCollection
{
public $collects = 'App\Http\Resources\Item';
public function toArray($request)
{
return [
'items' => $this->collection,
];
}
}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Item extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->ProductID,
'quantity' => $this->Quantity,
'unit_price' => $this->Price,
];
}
}

From the code it seems like you are using Eloquent API resources for your CartItem model.
If that's correct you shouldn't use json_encode, as it would convert your object to a string, but you can try to call directly the toArray method on the CartItemCollection:
'items' => (new CartItemCollection($items))->toArray()['Items']
This code might need a bit of tweaking as you didn't post the CartItemCollection's class code as well as other relevant code you use to generate the structure of the output you are now getting.

Related

laravel relationship not loading

I've got a Product and a Category models:
class Product extends BaseModel
{
use Uuid;
protected $fillable = [
'barcode',
'name',
'sku',
'description',
'type',
'category_id',
'wholesale_price',
'retail_price',
'base_picture',
'current_stock_level',
'active',
];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
class Category extends BaseModel
{
protected $fillable = [
'name',
'parent',
'description',
'image',
];
public function product(): HasMany
{
return $this->hasMany(Product::class, 'category_id');
}
In my controller, I'm retrieveing all products and wanted to return the category object the product belongs to in the response, so I'm doing:
class ProductsController extends Controller
{
public function index(): AnonymousResourceCollection
{
$products = Product::all();
return ProductsResource::collection($products->loadMissing('category'));
}
and my resource looks like:
class ProductsResource extends JsonResource
{
public function toArray($request) : array
{
return [
'id' => $this->id,
'type' => 'products',
'attributes' => [
'barcode' => $this->barcode,
'name' => $this->name,
'slug' => $this->slug,
'sku' => $this->sku,
'description' => $this->description,
'type' => $this->type,
// todo return category object?
'category' => new CategoriesResource($this->whenLoaded('category_id')),
'wholesale_price' => $this->wholesale_price,
'retail_price' => $this->retail_price,
'base_picture' => $this->base_picture,
'current_stock_level' => $this->current_stock_level,
'active' => $this->active,
]
];
}
}
but the response I'm getting is:
{
"data": [
{
"id": "a2102c4c-c14a-4d16-af28-e218bcc4fe39",
"type": "products",
"attributes": {
"barcode": "1010101010101",
"name": "phione",
"slug": "phione",
"sku": "w2e2r2",
"description": null,
"type": "services",
"wholesale_price": 54,
"retail_price": 34,
"base_picture": null,
"current_stock_level": 0,
"active": 1
}
}
]
}
I tried loading the relationship differently:
public function index(): AnonymousResourceCollection
{
$products = Product::with('category')->get();
return ProductsResource::collection($products);
}
but the result is the same.
It seems that the relationship is well established because if I run:
$product = Product::first();
dd($product->category);
I can see the category the product belongs to:
#attributes: array:8 [▼
"id" => 2
"name" => "Paper"
"slug" => "paper"
"parent" => 1
"description" => null
"image" => null
"created_at" => "2022-09-20 02:03:05"
"updated_at" => "2022-09-20 02:03:05"
]
what am I missing?
In controller load category relation with eager loading
$products = Product::with('category');
return ProductsResource::collection($products);
and in the ProductsResource file load, the relation category, not category_id
'category' => CategoriesResource::collection($this->whenLoaded('category')),
You don't need to use a collection here. Simply do
$products = Product::with('category')->get();
return response()->json($products);

Laravel: Get the correct value of an enum field on a resource class

I'm using PHP8.1 and Laravel 9 for a project in which I've got the following enum:
enum OrderStatuses : string
{
case New = 'new';
case Pending = 'pending';
case Canceled = 'canceled';
case Paid = 'paid';
case PaymentFailed = 'payment-failed';
public function createOrderStatus(Order $order) : OrderStatus
{
return match($this) {
OrderStatuses::Pending => new PendingOrderStatus($order),
OrderStatuses::Canceled => new CanceledOrderStatus($order),
OrderStatuses::Paid => new PaidOrderStatus($order),
OrderStatuses::PaymentFailed => new PaymentFailedOrderStatus($order),
default => new NewOrderStatus($order)
};
}
one of the classes listed in the enum looks like this:
abstract class OrderStatus
{
public function __construct(protected Order $order)
{
}
/**
* Determines whether an order can transition from one status into another
*
* #return bool
*/
abstract public function canBeChanged() : bool;
}
class PaidOrderStatus extends OrderStatus
{
public function canBeChanged(): bool
{
return false;
}
}
all others are basically the same, they just differ on the implementation of the canBeChanged method.
Now, I've got the following resource:
class OrdersResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'orders',
'attributes' => [
'status' => $this->status,
'payment_type' => $this->payment_type,
'payment_transaction_no' => $this->payment_transaction_no,
'subtotal' => $this->subtotal,
'taxes' => $this->taxes,
'total' => $this->total,
'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
}
which is called from my controller like this:
return (new OrdersResource($order))
->response()->setStatusCode(ResponseAlias::HTTP_OK);
Before implementing the enum my resource was working correctly, it returned the expected data. But after the enum, it's returning [] for the status field.
A sample return is currently looking like this:
"id" => "86b4e2da-76d4-4e66-8016-88a251513050"
"type" => "orders"
"attributes" => array:8 [
"status" => []
"payment_type" => "card"
"payment_transaction_no" => "3kaL92f5UwOG"
"subtotal" => 3005.76
"taxes" => 0
"total" => 3005.76
"created_at" => "2022-08-31T12:47:55.000000Z"
"updated_at" => "2022-08-31T12:47:55.000000Z"
]
]
notice again the value for status.
I've got a casting and a attribute in my Order's model:
protected $casts = [
'status' => OrderStatuses::class,
];
protected function status(): Attribute
{
return new Attribute(
get: fn(string $value) =>
OrderStatuses::from($value)->createOrderStatus($this),
);
}
Furthermore, if I dd the type of $this->status in the toArray method from OrdersResource it says that it is of type Domain\Order\Enums\PaidOrderStatus which is correct.
I tried adding __toString() to PaidOrderStatus class but had no luck. What am I missing?
Update
I've added a test() method to PaidOrderStatus:
class PaidOrderStatus extends OrderStatus
{
public function canBeChanged(): bool
{
return false;
}
public function test() : string
{
return OrderStatuses::Paid->value;
}
}
and then did:
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'orders',
'attributes' => [
'status' => ($this->status)->test(),
'payment_type' => $this->payment_type,
'payment_transaction_no' => $this->payment_transaction_no,
'subtotal' => $this->subtotal,
'taxes' => $this->taxes,
'total' => $this->total,
'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
and that gave me:
[
"id" => "8a2d6024-a63f-44ba-a145-cede2ecf3aaa"
"type" => "orders"
"attributes" => array:8 [
"status" => "paid"
"payment_type" => "card"
"payment_transaction_no" => "kC9upaoGb2Nd"
"subtotal" => 657.26
"taxes" => 0
"total" => 657.26
"created_at" => "2022-08-31T13:17:25.000000Z"
"updated_at" => "2022-08-31T13:17:25.000000Z"
]
and that worked. But it's a very hacky solution and I'd like to do better.
I'm sure you've already solved this as it's been months ago, but first you don't need the Attribute mutator as you have already defined the cast which will correctly map the string value to the right enum case.
Then in the resource, you just get the value from the enum like so.
'status' => $this->status->value,

laravel elasticsearch babenkoivan/elastic-adapter model relation

I'm looking to use elastic search on a project with model relation.
For now elastic search is working, I've followed this doc who explain how to start with this package :
elasticsearch/elasticsearch
babenkoivan/elastic-migrations
babenkoivan/elastic-adapter
babenkoivan/elastic-scout-driver
The problem is I need to able to search by relation.
this is my composant elastic migration :
Index::create('composant', function(Mapping $mapping, Settings $settings){
$mapping->text('reference');
$mapping->keyword('designation');
$mapping->join('categorie');
$settings->analysis([
'analyzer' => [
'reference' => [
'type' => 'custom',
'tokenizer' => 'whitespace'
],
'designation' => [
'type' => 'custom',
'tokenizer' => 'whitespace'
]
]
]);
});
Here my categorie elastic migration :
Index::create('categorie', function(Mapping $mapping, Settings $settings){
$mapping->keyword('nom');
$settings->analysis([
'analyzer' => [
'nom' => [
'type' => 'custom',
'tokenizer' => 'whitespace'
]
]
]);
});
My composant Model :
public function categorie()
{
return $this->belongsTo('App\Model\Categorie');
}
public function toSearchableArray()
{
return [
'reference' => $this->reference,
'designation' => $this->designation,
'categorie' => $this->categorie(),
];
}
and my categorie Model :
public function toSearchableArray()
{
return [
'nom' => $this->nom,
];
}
So if you look at the composant relation, you can see that the join mapping return the categorie relation. I dont now if I do it right but what I know is that elasticsearch didn't have any relation in the object I'm looking for.
And I didn't find any doc of how to use the join mapping method of the package.
OK, I've found the solution, the problem was in the migration you must use object in order to index the belongsToMany relationship like that
Index::create('stages', function (Mapping $mapping, Settings $settings) {
$mapping->text('intitule_stage');
$mapping->text('objectifs');
$mapping->text('contenu');
$mapping->object('mots_cles');
});
and in your model :
public function toSearchableArray()
{
return [
'intitule_stage' => $this->intitule_stage,
'objectifs' => $this->objectifs,
'contenu' => $this->contenu,
'n_stage' => $this->n_stage,
'mots_cles' => $this->motsCles()->get(),
];
}
And the result is as expected now
If you want to get "nom" of categorie, write this in composant Model instead
'categorie' => $this->categorie->nom ?? null,
$this->categorie() return the relationship, not the object.
Same problem with a belontoMany relation, and I've made the same things in order to get the relation as a nested object, but when I try to populate my index the field "mots_cles" stay empty, I don't understand why.
Here is the migration :
Index::create('stages', function (Mapping $mapping, Settings $settings) {
$mapping->text('intitule_stage');
$mapping->text('objectifs');
$mapping->text('contenu');
$mapping->nested('motsCles', [
'properties' => [
'mot_cle' => [
'type' => 'keyword',
],
],
]);
});
The model :
public function toSearchableArray()
{
return [
'intitule_stage' => $this->intitule_stage,
'objectifs' => $this->objectifs,
'contenu' => $this->contenu,
'n_stage' => $this->n_stage,
'mots_cles' => $this->motsCles(),
];
}
public function motsCles()
{
return $this->belongsToMany(MotsCle::class);
}

Saving multiple records in a laravel eloquent create

I'm trying to save multiple records via
AppSettings::create(
[
'name' => 'mail_host',
'type' => $emailsettingstype->id,
'value' => '',
],
[
'name' => 'mail_port',
'type' => $emailsettingstype->id,
'value' => '',
],
[
'name' => 'mail_username',
'type' => $emailsettingstype->id,
'value' => '',
],
);
But from the above, only the first array is getting created. Where am i going wrong? Any help is appreciated.
I think this should do
AppSettings::createMany([
[
'name'=>'mail_host',
'type'=>$emailsettingstype->id,
'value'=>'',
],
[
'name'=>'mail_port',
'type'=>$emailsettingstype->id,
'value'=>'',
],
[
'name'=>'mail_username',
'type'=>$emailsettingstype->id,
'value'=>'',
],
]);
Make sure you're passing an array of arrays, not a params of array.
UPDATE, you can use Model::insert() although according to what I've read, that method doesn't create/update the timestamps.
You can just use Eloquent::insert() link as below:
AppSettings::insert([
[
'name'=>'mail_host',
'type'=>$emailsettingstype->id,
'value'=>'',
],
[
'name'=>'mail_port',
'type'=>$emailsettingstype->id,
'value'=>'',
],
[
'name'=>'mail_username',
'type'=>$emailsettingstype->id,
'value'=>'',
],
]);
The problem with above is that it won't update timestamps, find examples here
The Create many Method createMany is available on relationship check reference to this link and this documentation from laravel
so far my example look like this.
I have two models Pricing and AvailableService Model
Pricing Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Pricing extends Model
{
protected $fillable = ["name", "price"];
public function available(){
return $this->hasMany(AvailableService::class, "pricing_id", "id");
}
}
And the AvailableServiceMode look like this
namespace App;
use Illuminate\Database\Eloquent\Model;
class AvailableService extends Model
{
protected $fillable = ["pricing_id", "service_id"];
public function service(){
return $this->belongsTo(Service::class, "service_id", "id");
}
}
So createMany operation look like this
$insertMany = Pricing::create(['name'=>request('name')]);
$insertMany->available()->createMany([
['service_id'=>1],
['service_id'=>2],
['service_id'=>3],
['service_id'=>4],
['service_id'=>5],
]);
And it works for, you can give it a try too. THANKS
If you want to store multiple record in seeder use this method instead of insert because in my case I want to slug automatically created using spatie/laravel-sluggable pkg. If you used the insert or DB technique then you have to give the value for slug field also.
CategorySeeder
<?php
namespace Database\Seeders;
use App\Servcategory;
use Illuminate\Database\Seeder;
class CategorySeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$categories = [
[
'name' => 'Automotive',
// 'slug' => 'automotive',
],
[
'name' => 'Business Services',
// 'slug' => 'business-services',
],
[
'name' => 'Computer, Telecom & IT Services',
// 'slug' => 'computer-telecom-&-it-services',
],
[
'name' => 'Education & Training',
// 'slug' => 'education-&-training',
],
[
'name' => 'Finance',
// 'slug' => 'finance',
],
[
'name' => 'Hospitals, Clinic, Medical',
// 'slug' => 'hospitals-clinic-medical',
],
[
'name' => 'Real Estate, Construction, Property',
// 'slug' => 'real-estate-construction-property',
],
[
'name' => 'Travel,Toursim & Hotels',
// 'slug' => 'travel-toursim-&-hotels',
],
];
// Servcategory::insert($categories);
collect($categories)->each(function ($category) { Servcategory::create($category); });
}
}
In case some one searching for eloquent model, I used the following method:
foreach($arCategories as $v)
{
if($v>0){
$obj = new Self(); // this is to have new instance of own
$obj->page_id = $page_id;
$obj->category_id = $v;
$obj->save();
}
}
$obj = new Self(); is a must otherwise it only saves single record when $this is used.
in seeder create an array and do foreach with Model::create(). All your records will be with timestamps
protected $array = [
[...],
[...],
[...]
];
public function run()
{
foreach ($this->array as $value) {
Model::create($value);
}
}

Laravel transformer collection error

I'm using fractal in Laravel 5.2. I'm using a transformer on a collection like this:
public function allFromCompany()
{
$users = UserModel::all();
return $this->response->collection($users, new UserTransformer);
}
UserTransformer
class UserTransformer extends Fractal\TransformerAbstract
{
public function transform(UserModel $user)
{
return [
'user' => [
'id' => $user->id,
'role' =>
[
'role_id' => $user->role_id,
'name' => $user->role->name
],
'company' =>
[
'company_id' => $user->company_id,
'company' => $user->company->name,
],
'active' => $user->active,
'name' => $user->name,
'lastname' => $user->lastname,
'address' => $user->address,
'zip' => $user->zip,
'email' => $user->email
]
];
}
}
But when I do it like that I receive an error:
{
"status_code": 500,
"debug": {
"line": 10,
"file": "/home/vagrant/Code/forum/app/Src/v1/User/UserTransformer.php",
"class": "Symfony\\Component\\Debug\\Exception\\FatalThrowableError",
"trace": [
"#0 /home/vagrant/Code/forum/vendor/league/fractal/src/Scope.php(338): Src\\v1\\User\\UserTransformer->transform(Object(Src\\v1\\User\\User))",
When I try this with one item:
return $this->response->item($user, new UserTransformer);
It works.
It's pretty old question, and I ran into it :) but if anyone has similar problem, maybe you've forgot to "use Helpers" something like
class UserController extends Controller
{
use Helpers;
public function index()
{
$users = User::all();
return $this->collection($users, new UsersTransformer);
}

Categories