Request data
{
"exam_id": 10,
"exam_scores": [
{
"student_id": 1,
"subject_id": 1,
"marks": 50,
},
{
"student_id": 1,
"subject_id": 2,
"marks": 70,
},
{
"student_id": 1,
"subject_id": 3,
"marks": 80,
}
],
}
Where student_id and subject_id is a unique composite key,
how to make validator for composite key with Rule Method that accepts array of data ()
I tried but it does not work as expected.
$validator = Validator::make(request()->all(), [
"exam_id"=> "required|integer",
"exam_scores"=> "required|array",
'exam_scores.*.student_id' => [
Rule::unique('results')->where(function ($query) {
return $query
->whereStudent_idAndSubject_id(request()->get('exam_scores.*.student_id'),request()->get('exam_scores.*.subject_id'))
})
],
]);
below request should not validate data. but it validates successfully.
{
"exam_id": 10,
"exam_scores": [
{
"student_id": 1,
"subject_id": 1,
"marks": 50,
},
{
"student_id": 1,
"subject_id": 1,
"marks": 70,
}
],
}
Below request successfully validate data with single object of exam_scores that is expected.
{
"exam_id": 10,
"exam_scores": {
"student_id": 1,
"subject_id": 1,
"marks": 50,
}
}
$validator = Validator::make(request()->all(), [
"exam_id"=> "required|integer",
"exam_scores"=> "required|array",
'exam_scores.student_id' => [
Rule::unique('results')->where(function ($query) {
return $query
->whereStudent_idAndSubject_id(request()->get('exam_scores.student_id'),request()->get('exam_scores.subject_id'))
})
],
]);
After searching lots of Blogs, Tutorials & of course laravel Documentation, I got something that is solved my problem here is the blog link. that was not what I exactly wanted but it clears concept of what I have to do.
this guy saves my day.
Validate Dynamic request Values
Here is an example.
namespace App\Http\Requests;
use App\Http\Requests\Request;
class OrderRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = [
'name' => 'required|max:255',
];
foreach ($this->request->get('items') as $key => $val) {
$rules['items.' . $key] = 'required|max:10';
}
return $rules;
}
public function messages()
{
$messages = [];
foreach ($this->request->get('items') as $key => $val) {
$messages['items.' . $key . '.max'] = 'The field labeled "Book Title ' . $key . '" must be less than :max characters.';
}
return $messages;
}
}
The solution were pretty much simple and easy.
Related
I used laravel resources for my api responses and added paginate method. Upon using paginate method I always get a result like this where laravel by default gives three keys namely "data", "links" and "meta". But I want to change the resource to my own need.
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28#example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort#example.com",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
But I want a result like this
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28#example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort#example.com",
}
],
"metadata": {
"pagination": {
"offset": 50,
"limit": 25,
"previousOffset": 25,
"nextOffset": 75,
"currentPage": 3,
"pageCount": 40,
"totalCount": 1000
}
}
}
How can I be able to achieve this. I am using Laravel 7.*
My controller code:
public function index(Request $request)
{
return DiscussionResource::collection($this->discussion->getDiscussionList($request));
}
My Model method looks like this:
public function getDiscussionList($request){
return $this->ofSearch($request)
->orderBy('created_at', config('settings.pagination.order_by'))
->paginate(config('settings.pagination.per_page'));
}
My resource looks like this:
class DiscussionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'question_id' => $this->question_id,
'user_id' => $this->user_id,
'user_image' => $this->user->userProfile->image,
'user_role' => $this->user->type,
'comment' => $this->comment,
'is_pinned' => $this->is_pinned,
'created_at' => $this->created_at->toDateString()
];
}
}
There are so many ways to do that in laravel and here you go 2 ways of it:
First Way: you can create a custom PaginatedResourceResponse and override the default paginationLinks. for example like below:
use Illuminate\Http\Resources\Json\PaginatedResourceResponse;
class CustomPaginatedResourceResponse extends PaginatedResourceResponse
{
protected function paginationLinks($paginated)
{
return [
'prev' => $paginated['prev_page_url'] ?? null,
'next' => $paginated['next_page_url'] ?? null,
];
}
protected function meta($paginated)
{
$metaData = parent::meta($paginated);
return [
'current_page' => $metaData['current_page'] ?? null,
'total_items' => $metaData['total'] ?? null,
'per_page' => $metaData['per_page'] ?? null,
'total_pages' => $metaData['total'] ?? null,
];
}
}
then override toResponse method(Actually, the toResponse method converts resource collection to responses)
public function toResponse($request)
{
return $this->resource instanceof AbstractPaginator
? (new CustomPaginatedResourceResponse($this))->toResponse($request)
: parent::toResponse($request);
}
You may consider overriding other methods if you want to customize your response furthermore.
Second Way: you can just override the toResponse method in the ResourceCollection and make it as you wish!
More Ways HERE!
If you want to customize the metadata you can take the help of the with() method that comes with laravel for collections.
// in DiscussionResource file
public function with($request)
{
return [
'meta' => [
'key' => 'value',
],
];
}
If you want to customize it from controller you may do something like this
return (DiscussionResource::collection($this->discussion->getDiscussionList($request)))
->additional(['meta' => [
'key' => 'value',
]]);
And in case you want it for a single resource you can modify it at the toArray() method
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
For more details you can check this out https://laravel.com/docs/7.x/eloquent-resources#adding-meta-data
I am trying to rename my data wrapper for the resource I am fetching using Laravel resource. I read in the documentation here how you are supposed to do it, so I did:
ScanResource.php
class ScanResource extends JsonResource
{
public static $wrap = 'scan';
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
//return parent::toArray($request);
return ['id' => $this->id,
'rftag' => $this->rftag,
'jc_number' => $this->jc_number,
'station' => $this->station,
'agent' => $this->agent,
'created_at' => $this->created_at->format('d/m/Y H:i:s'),
'updated_at' => $this->updated_at->format('d/m/Y'),];
}
}
AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
//Paginator::useBootstrapThree();
Schema::defaultStringLength(191);
//JsonResource::withoutWrapping();
//Resource::withoutWrapping();
ScanResource::withoutWrapping();
}
public function register()
{
//
}
}
This is how I am trying to fetch the resource in my controller:
public function show($id)
{
$product = ScanDetail::find($id);
if (is_null($product)) {
return $this->sendError('Scan details not found.');
}
return $this->sendResponse(new ScanResource($product), 'Scan info retrieved successfully.');
}
currently I am getting the following JSON with Postman:
{
"success": true,
"data": {
"id": 1,
"rftag": "E200203204205212165166",
"jc_number": "15",
"station": "Repairing",
"agent": "kbailey",
"created_at": "11/06/2020 01:29:53",
"updated_at": "11/06/2020"
},
"message": "Scan info retrieved successfully."
}
But I want:
{
"success": true,
"scan": {
"id": 1,
"rftag": "E200203204205212165166",
"jc_number": "15",
"station": "Repairing",
"agent": "kbailey",
"created_at": "11/06/2020 01:29:53",
"updated_at": "11/06/2020"
},
"message": "Scan info retrieved successfully."
}
I tried this, this, this, this and this. This is not what I am using, so I did not think it would work. I also tried modifying my toArray to:
return [
'scan'=>['id' => $this->id,
'rftag' => $this->rftag,
'jc_number' => $this->jc_number,
'station' => $this->station,
'agent' => $this->agent,
'created_at' => $this->created_at->format('d/m/Y H:i:s'),
'updated_at' => $this->updated_at->format('d/m/Y'),]
];
but its giving the following JSON:
{
"success": true,
"data": {
"scan": {
"id": 1,
"rftag": "E200203204205212165166",
"jc_number": "15",
"station": "Repairing",
"agent": "kbailey",
"created_at": "11/06/2020 01:29:53",
"updated_at": "11/06/2020"
}
},
"message": "Scan info retrieved successfully."
}
Again, not what I want since I will be fetching different resources from the database using api calls. So I want to customize the outer wrapper. Any assistance is/will be greatly appreciated. Thanks in advance.
You don't need to do anything in your AppServiceProvider.php
You just need to create ScanResource.php as below:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ScanResource extends JsonResource
{
/**
* The "data" wrapper that should be applied.
*
* #var string
*/
public static $wrap = 'scan';
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'rftag' => $this->rftag,
'jc_number' => $this->jc_number,
'station' => $this->station,
'agent' => $this->agent,
'created_at' => $this->created_at->format('d/m/Y H:i:s'),
'updated_at' => $this->updated_at->format('d/m/Y')
];
}
public function with($request)
{
return [
"success" => true,
"message": "Scan info retrieved successfully."
];
}
}
And you need to use that ScanResource.php in the ScanController.php as below
// use the namespace at the top
use App\Http\Resources\UserResource;
// add the below action in the controller.
public function show($id)
{
$scan= Scan::find($id);
return new ScanResource($scan);
}
I have tested the code in my postman and I got below output
{
"scan": {
"id" : 7,
"rftag" : "abcde",
"jc_number" :"AB123FG" ,
"station" : "abc",
"agent" : "Stanton Satterfield",
"created_at": "29/10/2021 05:18:42",
"updated_at": "29/10/2021 05:18:42"
},
"success" : true,
"message": "Scan info retrieved successfully."
}
in your controller change to this
return ['scan' => YourResource::collection(YourModel::get())];
and in your AppServiceProvider.php file add this
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource; // this
class AppServiceProvider extends ServiceProvider
{
public function register()
{
JsonResource::withoutWrapping(); // and this
}
}
hope it's work for you :)
I have a database table structure like the following (in laravel):
user 1-1 profile
partner 1-1 profile
user 1-N department
I want to send a save request (post) and have the user validated in UserRequest and have this class call a ProfileRequest.
Is this possible to do?
Is there any way to perform validations of related models?
Class of User request example:
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|string',
'lastname' => 'required|string',
'user' => [
'required',
Rule::unique('users')->ignore($this),
],
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
'headquarter_id' => 'required'
//Validation of profile
];
}
}
Example of controller User
public function store(AdAszaUserRequest $request)
{
$input = $request->all();
$validated = $request->validated();
$input['password'] = \Hash::make($request['password']);
//
$departmentidList = array_column($input['departments'], 'id');
$AszaUser = AdAszaUser::create($input);
$models = [];
foreach ($input['departments'] as $model) {
$models[] = new AdDepartment($model);
}
///important: this line add departments without validation
$AszaUser->departments()->saveMany($models);
$AszaUser->departments()->sync($departmentidList);
return response($AszaUser, 201);
}
And Request Of deparment:
<?php
namespace App\Http\Requests\AD;
use Illuminate\Foundation\Http\FormRequest;
class AdDepartmentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|string|unique:ad_departments',
'internal_name' => 'required|string|unique:ad_departments'
];
}
}
Example of Json send in post:
{
"id":2,
"name": "Admin2",
"email": "test#gmail.com",
"lastname": "test",
"user": "test",
"password": "test",
"password_confirmation": "test",
"headquarter_id": 1,
"lastname":"test",
"remember_token": "1",
"email_verified_at": "test",
"headquarter": {
"id": 1,
"name": "ASZA ZARAGOZA",
"description": "Sede en Zaragoza",
},
"departments": [
{
"id": 1,
"name": "Intérpretes",
"internal_name": "Interprete",
"description": "Departamento de Intérpretes",
"display_id": "01",
"pivot": {
"user_id": 1,
"department_id": 1
}
},
{
"id": 10,
"name": "Psicología"
}
]
}
Can I call the DepartmentRequest to validate the elements passed in the department array?
UPDATE: 1
I don't think it is necessary, but of course it is possible
public function store(AdAszaUserRequest $request)
{
$input = $request->all();
$validated = $request->validated();
$input['password'] = \Hash::make($request['password']);
//
$departmentidList = array_column($input['departments'], 'id');
$AszaUser = AdAszaUser::create($input);
$models = [];
foreach ($input['departments'] as $model) {
/** To check validation for single item */
$validator = Validator::make($model, (new StoreEventRequest)->rules());
if (!$validator->fails()) {
$models[] = new AdDepartment($model);
} else {
/** Something wrong */
/** $errors = $validator->errors(); */
}
}
/** To check validation for array of data
$validator = Validator::make($request->only(['departments']), collect(array_map(function ($rules, $field): array {
return ['departments.*.' . $field => $rules];
}, (new StoreEventRequest)->rules()))
->collapse()
->toArray()); */
/**
* And then do what you want to do with this object
* $errors = $validator->errors();
*
if ($validator->fails()) {
return redirect('some_url')
->withErrors($validator);
} */
$AszaUser->departments()->saveMany($models);
$AszaUser->departments()->sync($departmentidList);
return response($AszaUser, 201);
}
For more information see documentation https://laravel.com/docs/6.x/validation#manually-creating-validators
UPDATE: 2
If you need to separate your request classes, you also can do it like so
Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return array_merge([
'name' => 'required|string',
'lastname' => 'required|string',
'user' => [
'required',
Rule::unique('users')->ignore($this),
],
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
'headquarter_id' => 'required'
//Validation of profile
/** Validate of departments */
'departments' => 'nullable|array',
], collect(array_map(function ($rules, $field): array {
return ['departments.*.' . $field => $rules];
}, (new StoreEventRequest)->rules()))
->collapse()
->toArray())
->toArray();
}
}
Yes you can do it like this
public function rules() {
return [
'name' => 'required|string',
'lastname' => 'required|string',
'user' => [ 'required', Rule::unique('users')->ignore($this), ],
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
'headquarter_id' => 'required',
//Validation of profile
'profile.some_field' => 'required',
//For array of objects
'profile.*.some_field' => 'required',
];
}
I only want the data that pass the validation. And do not want any errors or redirection using laravel-validation. So far I've been able to stop the redirection using a laravel-request but the validation does not seem to work properly if I leave the failedValidation method to not throw any exception.
I have array of data from user who can fill the rows or leave them empty. If they leave the required fields empty in a row then I want to exclude them from the final data after validation.
ExpenditureExtry.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class ExpenditureEntry extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'date' => 'required',
'description.*' => 'nullable',
'account_head.*' => 'required',
'amount.*' => 'required_with:account_head.*'
];
}
/**
* Failed validation disable redirect
*
* #param Validator $validator
*/
protected function failedValidation(Validator $validator)
{
//throw new HttpResponseException(response()->json($validator->errors(), 422));
}
}
Inside a controller
public function store(ExpenditureEntry $req)
{
return $req->validated();
}
I want this to return only those data that passed the validation. At the moment it returns all the data.
Expected output
// http://127.0.0.1:8000/expenditure/add
{
"date": "03 Sep 2018",
"description": {
"1": null,
},
"account_head": {
"1": "5",
},
"amount": {
"1": "5000",
}
}
Actual output
// http://127.0.0.1:8000/expenditure/add
{
"date": "03 Sep 2018",
"description": {
"0": null,
"1": null,
"2": null,
"3": "sas asd adas",
"null": null
},
"account_head": {
"0": "3",
"1": "5",
"2": null,
"3": null,
"null": null
},
"amount": {
"0": null,
"1": "5000",
"2": "5000",
"3": null,
"null": null
}
}
I also tried using a Validator::make in the controller but it also doesnot work as expected:
Inside Controller
public function store(Request $req)
{
$validator = Validator::make($req->except(['account_head.null', 'description.null', 'amount.null']), [
'date' => 'required',
'account_head.*' => 'required',
'description.*' => 'nullable',
'amount.*' => 'required_with:account_head.*'
]);
return $validator->valid();
}
Output
// http://127.0.0.1:8000/expenditure/add
{
"_token": "EmiRpnXhPHUPl3HIzuYLrtuLaGj9Ruv8AySe11Bp",
"date": "03 Sep 2018",
"description": [
null,
null,
null,
"sas asd adas"
]
}
PS: I want a laravel based solution if possible. If no such solution are possible, I actually have a solution to just manually validate them.
My Solution
Went through every data and manually validated them. My problem with this approach is now I can't use other validation functions from laravel and when my fields increases, I don't think writing if condition will help me.
public function store(Request $req)
{
$datas = $req->except(['_token', 'account_head.null', 'description.null', 'amount.null']);
$countR = count($datas['account_head']);
$validated = [];
$cv = 0;
for ($i=0; $i < $countR; $i++) {
if($datas['account_head'][$i] == null) continue;
else if($datas['amount'][$i] == null) continue;
$validated['account_head'][$cv] = $datas['account_head'][$i];
$validated['description'][$cv] = $datas['description'][$i];
$validated['amount'][$cv] = $datas['amount'][$i];
$cv++;
}
return $validated;
}
Output
// http://127.0.0.1:8000/expenditure/add
{
"account_head": [
"5"
],
"description": [
null
],
"amount": [
"5000"
]
}
As far as i understand your problem, the following code will solve your issue.
public function store(Request $req)
{
$validator = Validator::make($req->except(['account_head.null', 'description.null', 'amount.null']), [
'date' => 'required',
'account_head.*' => 'required',
'description.*' => 'nullable',
'amount.*' => 'required_with:account_head.*'
]);
if ($validator->fails()) {
// Do Whatever you wants here.
// return redirect()->back()->withErrors($validator)->withInput();
}
}
Instead of redirecting back with errors you can do whatever task you want, without any redirection.
To initialize my app I have the following route:
/initialize
This returns Taxonomies, Enumerables and a couple of other taxonomy like collections. This saves multiple HTTP requests.
Although with Dingo / Fractal, I cannot see how I can respond with multiple collections?
e.g.
return [
'taxonomies' => $this->response->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->response->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->response->collection($otherStuff, new OtherStuffTransformer);
];
return response()->json([
'data' => [
'taxonomies' => $this->fractal->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->fractal->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->fractal->collection($otherStuff, new OtherStuffTransformer);
]
], 200);
This should return the JSON in the format you are looking for.
I have the same issue ,and I found the solution from How to use Transformer in one to many relationship. #1054.
Here is the collection I want to return with the transfomer of dingo in my controller.
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
DepartmentTransformer
class DepartmentTransformer extends TransformerAbstract
{
public function transform($department)
{
return [
'id' => $department['id'],
'name' => $department['name'],
'level' => $department['level'],
'parent_id' => $department['parent_id']
];
}
}
RolesTransformer
class RolesTransformer extends TransformerAbstract
{
public function transform($role)
{
return [
'name' => $role['name'],
'slug' => $role['slug'],
'description' => $role['description'],
'level' => $role['level']
];
}
}
UserTransformer
class UserTransformer extends TransformerAbstract
{
protected $defaultIncludes = ['departments','roles'];
public function transform($user)
{
return [
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'phone' => $user['phone'],
];
}
public function includeDepartments(User $user)
{
$dept = $user->departments;
return $this->collection($dept, new DepartmentTransformer());
}
public function includeRoles(User $user)
{
$rl = $user->roles;
return $this->collection($rl, new RolesTransformer());
}
}
In my controller
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
return $this->response->collection($user, new UserTransformer());
And I got the result
"data": {
{
"id": 43,
"name": "test7",
"email": "test7#foxmail.com",
"phone": "186********",
"departments": {
"data": {
{
"id": 1,
"name": "业务一部",
"level": 1,
"parent_id": 0
}
}
},
"roles": {
"data": {
{
"name": "agent",
"slug": "agent",
"description": "业务员",
"level": 1
}
}
}
}
}
Please take note of the usage of $defaultIncludes and includeXXX() methonds in the UserTransformer.You can get more detail info from Fractal Doc.