Laravel Resource and Resource Collections - laravel

I'm learning the Laravel Resource API and have setup my controller to pass the data to my Resource and my Resource Collection.
This is the list of servers (index method) with the show method showing the individual server
Controller
Index method
return new DedicatedServerResourceCollection($product->where('parent_id', 1)->with('dedicatedServers')->get());
Show Method
return new DedicatedServerResource(DedicatedServer::findOrfail($id));
I need to format my collection and resource differently. How can I get my Resource Collection to loop through each item and format the changes accordingly?
Resource Collection
return [
'productTypes' => $this->map(function($data){
return [
'id' => $data->id,
'title' => $data->title,
'tagline' => $data->tagline,
'slug' => $data->slug,
'dedicatedServers' => DedicatedServerResource::collection($this->resource)
// I need to pass 'dedicatedServers' === $this->dedicated_servers
];
})
];
Resource
return [
'id' => $this->id,
'productId' => $this->product_id,
'type' => $this->type,
'price' => $this->price,
'config' => [
'processorLine1' => $this->processor_line_1,
'processorLine2' => $this->processor_line_2,
'memory' => $this->memory,
'storageLine1' => $this->storage_line_1,
'storageLine2' => $this->storage_line_2,
'data' => $this->data,
'benchmark' => [
'benchmark' => $this->benchmark,
'benchmarkPercentage' => $this->benchmark_percentage
]
]
];

You might want to create a different resource class for the DedicatedServer model, like SecondDedicatedServerResource.

You should use either "resource file" or "$this->map(...)", not both.
Index Method
return ProductCollection::make(Product::with('dedicatedServers')->get());
ProductCollection
class ProductCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'productTypes' => $this->collection,
];
}
}
ProductResource
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'tagline' => $this->tagline,
'slug' => $this->slug,
'dedicated_servers' => $this->whenLoaded('dedicatedServers'),
];
}
}
DedicatedServerResource
class DedicatedServerResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'product_id' => $this->product_id,
'type' => $this->type,
'processor_line_1' => $this->processor_line_1,
'processor_line_2' => $this->processor_line_2,
'memory' => $this->memory,
'storage_line_1' => $this->storage_line_1,
'storage_line_2' => $this->storage_line_2,
'data' => $this->data,
'benchmark' => $this->benchmark,
'product' => ProductResource::make($this->whenLoaded('product')),
];
}
}
Note: Prefer to use $this->whenLoaded('dedicatedServers') instead of $this->dedicatedServers to avoid n+1 problem.

Related

when i update two entities in postman i have null in laravel api

**when i update two entities in postman i have null in laravel api and
I have two entities one is for employees and the other is for personalDetails
this is my model employee:
class Employee extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
protected $guarded = [];
protected $casts = [
];
public function personalDetails()
{
return $this->hasOne(PersonalDetails::class,'employee_id');
}
and this is the personalDetails model
class PersonalDetails extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
protected $guarded = [];
protected $casts = [
'Date_of_birth' => 'date',
'joining_Date' => 'date',
];
public function employee()
{
return $this->belongsTo(Employee::class,'employee_id');
}
the controller is :
public function update(UpdateEmployeeRequest $request,Employee $employee)
{
$employee->update($request->validated());
return new EmployeeResource($employee);
the rout:
Route::middleware('auth:sanctum')->post('employee', [EmployeeController::class, 'store']);
and the UpdateEmployeeRequest is :
class UpdateEmployeeRequest extends FormRequest
{
public function rules()
{
return [
'Name_kanji' => ['required'],
'Name_katakana' => ['required'],
'Name_family_kanji' => ['required'],
'Name_family_katakana' => ['required'],
'employee_number' => ['required','numeric'],
'image' => ['required'],
'Employee_state' => ['required', Rule::in([EmployeeState::Employed, EmployeeState::OnLeave, EmployeeState::Resigned])],
'Employee_type' => ['required', Rule::in([EmployeeType::FullTime, EmployeeType::PartTime, EmployeeType::Director])],
'Department' => ['required', Rule::in([Department::Design, Department::Management, Department::Sales])],
'gender' => ['required', Rule::in([gender::male, gender::female])],
'Current_age' => ['required'],
'Company_day_based_age' => ['required'],
'jop_title' => ['required'],
'Daily_travel_expenses' => ['required'],
'office' => ['required'],
'Years_of_service' => ['required'],
'Email_address' => ['required'],
'My_number' => ['required','numeric'],
'address_id' => ['required'],
'domicile' => ['required'],
'Contact_number' => ['required'],
'Emergency_contact' => ['required'],
'Emergency_contact_relation' => ['required'],
'Need_work_Instruction' => ['required'],
'Date_of_birth*' => ['required|array'],
'Date_of_birth.*day' => ['required'],
'Date_of_birth.*year' => ['required'],
'Date_of_birth.*month' => ['required'],
'joining_Date*' => ['required|array'],
'joining_Date.*day' => ['required'],
'joining_Date.*year' => ['required'],
'joining_Date.*month' => ['required'],
];
}
public function validated($key = null, $default = null)
{
return [
'Name_kanji' => $this->Name_kanji,
'Name_katakana' => $this->Name_katakana,
'Name_family_katakana' => $this->Name_family_katakana,
'Name_family_kanji' => $this->Name_family_kanji,
'employee_number' => $this->employee_number,
'image' => $this->image,
'Employee_state' => $this->Employee_state,
'Employee_type' => $this->Employee_type,
'Department' => $this->Department,
'gender' => $this->gender,
'Current_age' => $this->Current_age,
'Company_day_based_age' => $this->Company_day_based_age,
'jop_title' => $this->jop_title,
'Daily_travel_expenses' => $this->Daily_travel_expenses,
'office' => $this->office,
'Years_of_service' => $this->Years_of_service,
'Email_address' => $this->Email_address,
'My_number' => $this->My_number,
'address_id' => $this->address_id,
'domicile' => $this->domicile,
'Contact_number' => $this->Contact_number,
'Emergency_contact' => $this->Emergency_contact,
'Emergency_contact_relation' => $this->Emergency_contact_relation,
'Need_work_Instruction' => $this->Need_work_Instruction,
'Date_of_birth' => Carbon::create(
$this->Date_of_birth['year'],
$this->Date_of_birth['month'],
$this->Date_of_birth['day'])->format('Y-m-d'),
'joining_Date' => Carbon::create(
$this->joining_Date['year'],
$this->joining_Date['month'],
$this->joining_Date['day'])->format('Y-m-d'),
];
}
}
and the EmployeeResource:
class EmployeeResource extends JsonResource
{
public function toArray($request)
{
return
[ 'id' => $this->id,
'Name_kanji' => $this->Name_kanji,
'Name_katakana' => $this->Name_katakana,
'Name_family_kanji' => $this->Name_family_kanji,
'Name_family_katakana' => $this->Name_family_katakana,
'employee_number' => $this->employee_number,
'image' => $this->personalDetails?->getFirstMediaUrl('PersonalDetails'),
'Employee_state' => $this->personalDetails?->Employee_state,
'My_number' => $this->personalDetails?->My_number,
'Employee_type' => $this->personalDetails?->Employee_type,
'Daily_travel_expenses' => $this->personalDetails?->Daily_travel_expenses,
'Current_age' => $this->personalDetails?->Current_age,
'Company_day_based_age' => $this->personalDetails?->Company_day_based_age,
'department' => $this->personalDetails?->department,
'office' => $this->personalDetails?->office,
'Gender' => $this->personalDetails?->Gender,
'Date_of_birth' =>[
'month' => $this->personalDetails?->Date_of_birth->month,
'day' => $this->personalDetails?->Date_of_birth->day,
'year' => $this->personalDetails?->Date_of_birth->year,
],
'joining_Date' =>[
'month' => $this->personalDetails?->joining_Date->month,
'day' => $this->personalDetails?->joining_Date->day,
'year' => $this->personalDetails?->joining_Date->year,
],
'Years_of_service' => $this->personalDetails?->Years_of_service,
'Email_address' => $this->personalDetails?->Email_address,
'address_id' => $this->personalDetails?->address_id,
'jop_title' => $this->personalDetails?->jop_title,
'domicile' => $this->personalDetails?->domicile,
'Contact_number' => $this->personalDetails?->Contact_number,
'Emergency_contact' => $this->personalDetails?->Emergency_contact,
'Emergency_contact_relation' => $this->personalDetails?->Emergency_contact_relation,
'Need_work_Instruction' => $this->personalDetails?->Need_work_Instruction,
and When I run the code in postman it shows empty values ​​and it doesn't store what's the problem with that
You have no route parameter named employee. When you type-hint a variable on your route action (Controller method signature in this case) you are getting Dependency injection, not Implicit Route Model Binding; there is no parameter to bind. You are getting an empty Employee model instance. This instance has no attributes so you are getting nulls for all the attributes you are trying to access.
The call to update is instantly just returning since the Model instance is non-existing.
What you have is the functional equivalent of this:
return new EmployeeResource(new Employee());
You would have to have a route parameter defined on your route and be passing some identifier for this parameter in the URL you are sending the request to:
Route::post('employee/{employee}', ...);
http://yoursite.com/employee/1

Codeigniter 4 - Store validation rules in separate files

I have all my rules in the Validation config file, like the documentation suggest:
https://codeigniter4.github.io/userguide/libraries/validation.html#saving-sets-of-validation-rules-to-the-config-file
For example:
public $userCreate = [
'first_name' => [
'label' => 'First Name',
'rules' => 'required|string|max_length[60]'
],
'last_name' => [
'label' => 'Last Name',
'rules' => 'required|string|max_length[60]',
],
'email' => [
'label' => 'Auth.email',
'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
],
];
In my controllers I can access my validation groups like this:
$validation = \Config\Services::validation();
$rules = $validation->getRuleGroup('userCreate');
As my app gets bigger, I need more and more validation rules, so the question is, is there a way to organize them in separate files and not to have all of them in a single config file? Something like the custom rules, which are loaded in the config file and stored separately.
Steps
Create a custom directory for storing your validation rules. I.e app/Validation.
Create a class under that directory for your 'User' rules. I.e: app/Validation/UserRules.php
<?php
namespace App\Validation;
class UserRules
{
public function create()
{
return [
'first_name' => [
'label' => 'First Name',
'rules' => 'required|string|max_length[60]'
],
'last_name' => [
'label' => 'Last Name',
'rules' => 'required|string|max_length[60]',
],
'email' => [
'label' => 'Auth.email',
'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
],
];
}
public function update()
{
return [
// Add 'User' update rules here.
];
}
}
In the \Config\Validation config file, set the relevant 'User' validation rules in the constructor. I.e:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
// ...
class Validation extends BaseConfig
{
// --------------------------------------------------------------------
// Setup
// --------------------------------------------------------------------
public $userCreate = [];
public $userUpdate = [];
public function __construct()
{
$this->userCreate = ($userRules = new \App\Validation\UserRules())->create();
$this->userUpdate = $userRules->update();
}
// ...
}
In your Controllers, you may access validation groups as usual.
Thanks to #steven7mwesigwa I came up with a solution that suits me the most.
First I created separate classes inside the App/Validation folder. For example these 2 classes:
App\Validation\Auth.php
<?php
namespace App\Validation;
class Auth {
public $login = [
'email' => [
'label' => 'E-mail',
'rules' => 'required|max_length[254]|valid_email',
],
'password' => [
'label' => 'Password',
'rules' => 'required',
],
'remember' => [
'label' => 'Remember me',
'rules' => 'if_exist|permit_empty|integer',
]
];
}
App\Validation\User.php
<?php
namespace App\Validation;
class User {
public $userCreate = [
'first_name' => [
'label' => 'First Name',
'rules' => 'required|string|max_length[60]',
],
'last_name' => [
'label' => 'Last Name',
'rules' => 'required|string|max_length[60]',
],
'email' => [
'label' => 'E-mail',
'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
],
];
}
The next step is to add a construct method to the existing validation config file:
App\Config\Validation.php
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Validation extends BaseConfig {
...
// --------------------------------------------------------------------
// Rules
// --------------------------------------------------------------------
public function __construct() {
$ruleGroups = [
new \App\Validation\Auth(),
new \App\Validation\User(),
];
foreach ($ruleGroups as $ruleGroupClass) {
foreach ((array) $ruleGroupClass as $groupName => $rules) {
$this->{$groupName} = $rules;
}
}
}
}

Laravel Resource collection showing null field

I'm developing an API with Laravel. In one of the endpoint I'm accessing, some fields are showing a null value, but it should have some information.
Note the "addicionais_descricao" and "valor" fields, both always come with null values when I include them in the attributeitems array, but if I leave it at the initial level, the data is presented, but it doesn't solve my case, because I need this information with the attribute items:
enter image description here
This is where the endpoint calls, I make the query in the "Attribute" table, which has a relationship with the "Attributeitems" table, while the "attributeitems" table is linked to "Attribute" and "product".
public function show($id)
{
$atributos = Atributo::query('atributo')
->select(
'atributo.id',
'atributo.atrdescricao',
'atributoitens.atributo_id',
'atributoitens.produto_id',
'produto.prodescricao',
'produto.provalor'
)
->leftJoin('atributoitens', 'atributo.id', '=', 'atributoitens.atributo_id')
->leftJoin('produto', 'produto.id', '=', 'atributoitens.produto_id')
->where('atributo.id', '=', $id)
->get()->unique('id');
return AtributoResource::collection($atributos);
}
Resource Atributo:
public function toArray($request)
{
return [
'id' => $this->id,
'descricao' => $this->atrdescricao,
'atributoitens' => AtributoitensResource::collection($this->atributoitens),
];
}
Resource Atributo Itens:
public function toArray($request)
{
return [
'id' => $this->id,
'atributo' => $this->atributo_id,
'produtos' => $this->produto_id,
'adicionais_descricao' => $this->prodescricao,
'valor' => $this->provalor
];
}
What is the correct procedure for this situation?
Take this example as a reference :
Controller
$data = $shop->products()
->whereStatus(true)
->where('product_shop.active', true)
->where('product_shop.quantity', '>=', $this->min_product_qty)
->paginate(50);
return (new ProductCollection($data))
->response()
->setStatusCode(200);
ProductCollection
public function toArray($request)
{
return [
'data' => $this->collection
->map(function($product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
'brand' => $this->when($request->brand, $request->brand)
];
}
ProductResource
public function toArray($request)
{
return [
'type' => 'product',
'id' => (string) $this->id,
'attributes' => [
'uuid' => $this->uuid,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'thumb_path' => $this->thumb_path,
'cover_path' => $this->cover_path,
],
'relationships' => [
'brand' => $this->brand
]
];
}
Something like this should help you do what you want. I cant exactly do it for you. by the way why you are not using Eloquent, something like
Attribute::where(...)->with(['relation_1', 'products'])->get();
public function toArray($request)
{
return [
'id' => $this->id,
'attributes' => [...],
'products' => $this->collection
->map(function($this->product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
];
}

Laravel the new attributes not exists in the returned API resource when it called inside the controller

The resource collection:
public function toArray($request)
{
return [
'test' => 55,
'id' => $this->id,
'name_en' => $this->name_en,
'name_ar' => $this->name_ar,
'slug' => $this->slug,
'details' => $this->details,
'currency' => $this->currency,
'offer' => $this->offer->first(),
'brand' => $this->brand,
'category' => $this->category,
'specifications' => SpesificationResource::collection($this->specifications),
'merchant' => $this->merchant,
'images' => count($this->images) > 0 ? ImageResource::collection($this->images) : asset("/images/default.png"),
"price" => $this->price,
"finalPrice" => $this->offer->first() ? $this->price - ($this->price * $this->offer->first()->discount / 100) : $this->price,
'quantity' => $this->quantity,
'inStock' => $this->inStock(),
'status' => $this->status,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
The controller:
public function show($id)
{
try {
$product = new ProductResource(Product::findOrFail($id));
return $product->test;
// return JsonResponse::respondSuccess(trans(JsonResponse::MSG_SUCCESS), $product);
} catch (\Exception $e) {
return JsonResponse::respondError($e->getMessage());
}
}
when I use the test value inside the controller it's not returned, although it returned when I call it from Postman.
What the problem here?
$product is an instance of ProductResource And of course there is no test property on that class. And it does not implement the magic __get method.
So what you can do is either use $product['test'] cause it implements ArrayAccess or first you can do $product = (new ProductResource(Product::findOrFail($id)))->toArray($request); Then again you can use $product['test'] to get the test value.

How can I add condition in validator interface laravel?

I see from here : https://github.com/andersao/laravel-validator
For example the code like this :
use \Prettus\Validator\LaravelValidator;
class PostValidator extends LaravelValidator {
protected $rules = [
ValidatorInterface::RULE_CREATE => [
'title' => 'required',
'text' => 'min:3',
'author'=> 'required'
],
ValidatorInterface::RULE_UPDATE => [
'title' => 'required'
]
];
}
I want to add condition if user is member(if(Auth::id())) then the field author not required
So the validator to be like this :
ValidatorInterface::RULE_CREATE => [
'title' => 'required',
'text' => 'min:3'
],
The author required if the user is guest(no login)
Whether it can be done?
the Laravel documentation has a section on custom validators
Since you cannot execute instructions in a class properties, you can try to override the create method of your Repository instance, in order to modify the $rules parameter before the create actually takes place.
So in your Repository class, override the method create:
public function create(array $attributes)
{
$oldRule = $this->rules[ValidatorInterface::RULE_CREATE]['author'];
if(Auth::guest()){ // or use the preferred check
unset($this->rules[ValidatorInterface::RULE_CREATE]['author']);
}
$this->makeValidator();
$res = parent::create($attributes);
$this->rules[ValidatorInterface::RULE_CREATE]['author'] = $oldRule;
return $res;
}
EDIT
Another method could be specifying custom validation logic in your PostValidator as follows:
use \Prettus\Validator\LaravelValidator;
class PostValidator extends LaravelValidator {
const RULE_CREATE_FOR_MEMBER = 'RULE_CREATE_FOR_MEMBER';
protected $rules = [
ValidatorInterface::RULE_CREATE => [
'title' => 'required',
'text' => 'min:3',
'author'=> 'required'
],
self::RULE_CREATE_FOR_MEMBER => [
'title' => 'required',
'text' => 'min:3'
],
ValidatorInterface::RULE_UPDATE => [
'title' => 'required'
]
];
public function passes($action = null)
{
if($action == ValidatorInterface::RULE_CREATE && \Auth::id()) {
$action = self::RULE_CREATE_FOR_MEMBER;
}
return parent::passes($action);
}
}
But again, you need to override the standard behavior, it's up to you to decide which is the simplest solution for your needs.

Resources