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.
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 :)
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.
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 want to customize the format of flashed error message, that is recieved after i do something like $this->validator($request->all())->validate();
For an ajax request, it responds with:
{
"name": [
"The name field is required."
],
"email": [
"The email field is required."
],
}
But i want it to look like
{
"status": "fail",
"errors": {
"name": [
"The name field is required."
],
"email": [
"The email field is required."
],
}
}
I read the documentation under Customizing The Flashed Error Format and added formatValidationErrors method in my Controller class, but it's not making any difference.
public function formatValidationErrors(Validator $validator)
{
return ['status' => 'fail', 'errors' => $validator->errors()->getMessages()];
}
It's not changing even if i change formatValidationErrors method in the original Illuminate\Foundation\Validation\ValidatesRequests trait
I'm forced have to build this format in all request calls and so there's code-duplication. It would be nice if i could just call $this->validator($request->all())->validate() and it automatically formats as per my requirement.
The way to change it is to create a class that extends Validator and override the addError method. Here's a sample code:
<?php
namespace App\Validators;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\Validator;
class RestValidator extends Validator {
/**
* Add an error message to the validator's collection of messages.
*
* #param string $attribute
* #param string $rule
* #param array $parameters
* #return void
*/
protected function addError($attribute, $rule, $parameters)
{
$message = $this->getMessage($attribute, $rule);
$message = $this->doReplacements($message, $attribute, $rule, $parameters);
$customMessage = new MessageBag();
$customMessage->merge(['code' => strtolower($rule.'_rule_error')]);
$customMessage->merge(['message' => $message]);
$this->messages->add($attribute, $customMessage);
}
}
Now you can structure the validation in your controllers like so:
$rules = [
// Your rules here
];
$attributes = [
// The attributes you're checking here
];
$validator = Validator::make($attributes, $rules);
if ($validator->fails()) {
$errorMessage = [
'status' => 'fail',
'errors' => $validator->errors()
];
return $errorMessage;
}
// The rest of your code goes here
Try this in your model,
public function response(array $errors)
{
if (($this->ajax() && !$this->pjax()) || $this->wantsJson()) {
$errors = array('status' => 'fail', 'errors' => $errors);
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
It will result as you expected,
{
"status": "fail",
"error": {
"name": [
"The name field is required."
],
"email": [
"The email field is required"
]
}
}