I validate requests using FormRequest. This is a great tool, but I ran into a problem. Making standard identical methods for controllers, I decided to put these methods in a basic abstract class. But in this case, I can't validate requests using FormRequest, since they are transmitted using DI. I found the following solution that partially solved this problem:
/** #var FormRequest $customForRequest */
$customForRequest = $requestModel::createFrom($this->request);
return Validator::make($this->request->all(), $customForRequest->rules())->validate();
But the withValidator method, which in the standard scenario works as an after hook, is not called in this situating. How can this method be forced to be executed, leaving the validation logic in a separate class?
My FormRequest:
class MassUpdateDocumentAttribute 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 [
'filter' => 'required|array',
'filter.id.*' => [
'sometimes',
'integer',
Rule::exists(DocumentAttribute::class, 'id')
],
'fields' => 'required|array',
'fields.document_id' => [
'sometimes',
Rule::exists(Document::class, 'id'),
],
'fields.document_attr_type_id' => [
'required_with:fields.value',
Rule::exists(DocumentAttrType::class, 'id'),
],
'fields.value' => [
'sometimes',
'bail',
]
];
}
public function withValidator(Validator $validator)
{
if ($validator->fails()) return;
$validator->after(function ($validator) use ($documentAttributeRepository, $documentAttrTypeRepository) {
$data = $validator->getData();
// some large logic
});
}
}
You should be able to instantiate your FormRequest class by simply calling app(YourRequest::class) inside the method. It will automatically validate. This way you can simply pass the YourRequest::class part as a variable to desired FormRequest and it all should be working.
$request = app(YourRequest::class);
$data = $request->validated();
Related
I would like to ignore one of the inputs inside a FormRequest class. Previously, I've already done it on my previous Laravel project using the exact same code (The previous Laravel project was Laravel Version 7). Here is the code of my ignore validation:
class UserRequest extends FormRequest
{
protected $user;
public function __construct(Request $request)
{
$this->user = $request->route()->client;
}
public function rules()
{
return [
'email' => ['nullable', 'string', 'email', 'max:100', 'unique:users,email,'.$this->user.',user_id'],
];
}
The line code "$request->route()->client" is to get the submitted form request data (client is one of the route inside the web.php file).
But when I try this code inside the current project (Current project is Laravel 8). The results was the email was not ignored.
I guess there are something new in Laravel 8 about FormRequest? How can I solve this?
UPDATE
Here is my route (web.php) code:
Route::middleware('auth')->group(function () {
Route::resource('users', UserController::class);
And here is my users table structure:
I used the resource Controller so the route is simple.
try following way
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
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<string, mixed>
*/
public function rules()
{
return [
'email' => [
'nullable', 'string', 'email', 'max:100',
Rule::unique('users','email')->ignore($this->route()->client,'user_id'),
],
];
}
}
and ignore method accept two params
ignore($id, $idColumn = null)
for the second column default, it treats as the id column as the primary key.if not then we should specify the column name.
so there is no need to use a constructor and all
I want to validate one request item which can be phone number or email address. Is there any way in Laravel validation rules or other options to handle this?
<?php
namespace App\Http\Requests\UserAuthRequests;
use Illuminate\Foundation\Http\FormRequest;
class AuthenticationUser 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 [
'credentials' => ['required'],
'g-recaptcha-response' => ['required'], ];
}
}
My proposition is to write custom validation rule, where you will be conditionally check if it is a valid email or phone number.
Validator::extend('email_or_phone', function ($attribute, $value, $parameters, $validator) {
return
$validator->validateDigitsBetween($attribute, $value, [8, 10]) ||
$validator->validateEmail($attribute, $value, []);
});
Write this code in boot method in for example AppServiceProvider. I also strongly recommend you to use dedicated classes for custom rules as described here: https://laravel.com/docs/9.x/validation#custom-validation-rules
In both ways you just end up with code like this:
public function rules()
{
return [
'credentials' => ['email_or_phone'], // HERE WE USE OUR CUSTOM RULE
'g-recaptcha-response' => ['required'],
];
}
The standard way to validating incoming requests in Laravel is something like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Show the form to create a new blog post.
*
* #return Response
*/
public function create()
{
return view('post.create');
}
/**
* Store a new blog post.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// The blog post is valid...
}
}
There’s nothing wrong with validating requests in controllers, But how could I write the validation logic out of the controller to keep it clean and not break Single Responsibility Principle?
You could make your own form Request.
First create a request with php artisan make:request StorePostRequest
Create your own rule in this class like:
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
Update your controller function
public function store(StorePostRequest $request)
{
// do something
}
For more info:
https://laravel.com/docs/5.7/validation#creating-form-requests
Use the form requests provided by Laravel:
https://laravel.com/docs/5.7/validation#creating-form-requests
and make sure your controller uses the ValidatesRequests trait.
Form requests are validated before the controller actions are executed and contain validation rules and authorization logics.
I have generated new form Request for the controller, but I do not know how to filter data before there will handle in the validator and so on.
Are there some native solutions in Laravel for this case?
class TestRequest 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 [
"title" => "required|string",
"order" => "required|integer"
];
}
}
class TestController extends Controller
{
public function store(TestRequest $request, $chapterId)
{
// some business logic
}
}
There is some solution in Laravel 5.5 but in this example author uses
validate for filtering data from request, but I need to use filter inside TestRequest
$data = $this->validate(request(), [
//...
]); // I can't use this inside TestRequest
You can use my package: https://github.com/mikicaivosevic/laravel-filters
It's allows you to filter request values before validation...
<?php
class LoginRequest extends FormRequest {
//Filter
public function filters()
{
return [
'name' => 'lower',
'id' => 'int',
];
}
//...
}
Convert $request->name value into lowercase.
Conert $request->id value into integer.
I want to use Form Requests to validate Model so i started by create php artisan make:request TaskRequest and after i add in TaskRequest class `
public function rules()
{
return [
'name' => 'required|min:5',
];
}
public function messages()
{
return [
'name.required' => 'A title is required',
];
}
`
and in My logic
Route::post('/tasks',function (\App\Http\Requests\TaskRequest $request){
$task = new \App\Task();
$task->name = $request->input("name");
$task->save();
return response()->json(['task was created',$task], http_response_code());
});
So when i try to add a task i get error HttpException, This action is unauthorized.,AuthorizationException ...
It was work for me without Validation. So how can i fix this issue ?
Every (self-created) request has an authorize function that determines if the user is allowed to send the request. This can be useful to check for admin privileges or similar.
In you case, you can simply return true. More information can be found in the corresponding docs
Your authorize function in your TaskRequest would look like this:
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
In your custom request class for "form request" which contains the validation logic pass return:true; instead of return:false; and then it will work like a charm.
The code will look like something as following,
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class portfolioValidate 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 [
'title'=> 'required',
'content'=> 'required'
];
}
}
as you can use middleware to make authentication for the page which contains this form... so we don't need that authorization in the FormRequest class. So returning true will make it(validation) authorized for all cases.
I think now it is clear to everyone now.