Request class
class LoginRequest extends Request
{
public function authorize() {
return true;
}
public function rules() {
return [
'EmailAddress' => 'required',
'Password' => 'required',
];
}
public function messages() {
return [
"EmailAddress.required" => trans("login.RequiredEmailAddress"),
"Password.required" => trans("login.RequiredPassword")
];
}
}
Route
Route::post('/AuthenticateUser',
array(
'uses' => 'API\Login\apiLoginController#AuthenticateUser',
'as' => 'AuthenticateUser'
)
);
Controller Action Method
I have a controller, I did so far for request class only to validate the input parameters. below is the action method
public function AuthenticateUser(LoginRequest $request) {
dd("Hello");
}
Url
localhost:85/Laravel/public/api/v1/AuthenticateUser
I am using Postman Chrome extension to test the Url. So, as we can see that in the Request class both Email Address and the password are required parameters.
When I pass both parameters value. there is not issue and everything works. When I keep the Email Address value empty...I got 404 error and here is the screenshot.
Am I missing something to get rid of 404 error when Email address is not given? I am expecting an error message to enter Email Address
Below is the working state when I pass both email and password
Solution 1:
I managed to get rid of the 404 and return a 422 by adding the following header in the request:
accept:application/json
This is not really a bug in Laravel as Taylor pointed out but a way to differentiate if it is an AJAX/API request or not.
Solution 2:
Alternatively, if you don't want the client to specify that header, you can create a middleware that will add the header accept:application/json on every API requests. Here's how:
Create a new middleware: app/Http/Middleware/ForceJsonResponse.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
In /app/Http/Kernel.php, inside $middlewareGroups.api, specify the namespace to your newly created middleware:
protected $middlewareGroups = [
'web' => [...],
'api' => [
[...]
\App\Http\Middleware\ForceJsonResponse::class,
],
];
Finally got it working by changing the request class like below.
class LoginRequest extends Request
{
public function wantsJson() {
return true;
}
public function authorize() {
return true;
}
public function rules() {
return [
'EmailAddress' => 'required',
'Password' => 'required',
];
}
public function messages() {
return [
"EmailAddress.required" => trans("login.RequiredEmailAddress"),
"Password.required" => trans("login.RequiredPassword")
];
}
}
just added below code.
public function wantsJson() {
return true;
}
It is because you are validating directly on route handling and not matching throughs NotFoundException. You need to pass the Request to your Controller as is and do:
$this->validate($request, [
'EmailAddress' => 'required|email',
'Password' => 'required',
]);
Related
I am using Laravel v9.2.1 + Laravel Sanctum v2.14.1
I got a route
DELETE /api/v1/auth/tokens/{token}
for example (the token is an uuid)
DELETE http://example.com/api/v1/auth/tokens/5fcfa274-81d8-4e9f-8feb-207db77531af
And I am sure it works as expected via php artisan route:list
Before handling by the Controller, it should be validated by a FormRequest
app/Http/Controllers/V1/Auth/TokensController.php
namespace App\Http\Controllers\V1\Auth;
use App\Http\Requests\V1\Auth\Tokens\{
DestroyRequest,
};
class TokensController extends Controller
{
public function destroy(DestroyRequest $request) {
$request->user()->tokens()->where('id', $request->token)->first()->delete();
return response()->noContent();
}
}
app/Http/Requests/V1/Auth/Tokens/DestroyRequest.php
class DestroyRequest extends FormRequest
{
public function rules()
{
return [
'token' => [
'required',
'string',
'regex:/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
Rule::exists('personal_access_tokens')->where(function ($query) {
return $query->where('tokenable_id', $this->user()->id);
}),
]
];
}
}
But what I only got is The token field is required
I had already pass the token, why the 'required' rule still working?
What I tried
Only if I pass the token parameter like below, it will work
DELETE /api/auth/tokens/something?token=test_regex_is_working
I try to dd($this->token) in app/Http/Requests/V1/Auth/Tokens/DestroyRequest.php, it works as expected.
i might try going about it differently as the token isn't really user input
In the routes file:
Route::delete('/api/v1/auth/tokens/{token}', [TokensController::class, 'destroy'])->whereUuid('token');
In the FormRequest something maybe like this:
public function authorize()
{
return DB::table('personal_access_tokens')
->where('tokenable_id', Auth::id())
->where('token', $this->route('token'))
->exists()
}
You might need to add the following in the FormRequest class:
protected function prepareForValidation()
{
$this->merge(['token' => $this->route('token')]);
}
I believe URL parameters are not included in the request directly.
With the help of both #RawSlugs and #Aaron T, thank them a lot!
app/Http/Requests/V1/Auth/Tokens/DestroyRequest.php
protected function prepareForValidation() {
$this->merge(['token' => $this->route('token')]);
}
public function authorize() {
return $this->user()->tokens()->where('id', $this->token)->exists();
}
// But since the authorize() will validate the request before rules(), this will be useless
public function rules() {
return [
'token' => [
'required',
'string',
'regex:/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
]
];
}
I'm using Laravel Sanctum.
Is it possible to create a dynamic FormRequest validation in my function? See sample code below.
public function store(Request $request)
{
Model::create($request->all());
return redirect(url('/'));
}
What I mean is that I will change the "Request" parameter to the variable $formRequest.
My goal is that I would like to create different validation rules for a dynamic set of data of a single model.
If I could achieve this with other ways, please let me know. Thank you!
Edit:
Sample scenario:
I have a form that has fields of First Name, Middle Name and Last Name.
First Rule:
public function rules()
{
return [
'firstname' => 'required',
'middlename' => 'required',
'lastname' => 'required'
];
}
Second Rule:
public function rules()
{
return [
'firstname' => 'required',
'lastname' => 'required'
];
}
Where in the second rule only requires first and last name.
I just want to know if there are other ways of doing this rather than creating multiple store methods and adding more routes.
Skipping FormRequest and using the validate method on the $request instance can achieve this. Laracasts even has a lesson on it.
public function store(Request $request) {
$rules = [/*...*/];
$attributes = $request->validate($rules);
Model::create($attributes);
return redirect(url('/'));
}
You can create a custom request:
php artisan make:request CustomRequest
This will generate this class:
class CustomRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
The authorize() method will determine if the request can be validated in the first place.
The rules() method will return the validation rules for the current request.
And then in your controller function:
public function yourfunction(CustomRequest $request)
In the validation rules you can simply add the "sometimes" rule. You can find it here https://laravel.com/docs/5.7/validation#conditionally-adding-rules
public function rules()
{
return [
'firstname' => 'required',
'middlename' => 'sometimes|required',
'lastname' => 'required'
];
}
I use laravel custom form request with command php artisan make:request AddressBookRequest
And use that request in my controller like :
public function add_address_book($lang,$user_id,AddressBookRequest $request){
dd($request);
}
And when i run api route laravel shows :
NotFoundHttpException in RouteCollection.php line 161:
But when i change that AddressBookRequest to Request like :
public function add_address_book($lang,$user_id,Request $request){
dd($request);
}
Api works fine
AddressBookRequest :
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class AddressBookRequest 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()
{
return [
'title' => 'required',
'address' => 'required',
'latitude' => 'required',
'longitude' => 'required'
];
}
public function messages()
{
return [
'title.required' => trans('address_book.title_required'),
'address.required' => trans('address_book.address_required'),
'latitude.required' => trans('address_book.latitude_required'),
'longitude.required' => trans('address_book.longitude_required'),
];
}
}
AddressBookController usecases:
<?php namespace App\Http\Aggregate\Address_book\Controller\v1_0;
use App\Http\Requests\AddressBookRequest;
use Illuminate\Routing\Controller as BaseController;
use EventHomes\Api\ApiController;
use JWTAuth;
class AddressBookController extends BaseController
{
And route :
Route::group(['namespace' => 'Aggregate\Address_book\Controller\v1_0', 'middleware' => 'jwt.auth', 'prefix' => 'api/v1.0/{lang}'], function () {
Route::post('customer/{id}/address_book', 'AddressBookController#add_address_book');
});
How can i fix it to use custom request?
Any help will be appreciated
You should add this line to the top of the controller:
use App\Http\Requests\AddressBookRequest;
Also, make sure authorize() method inside custom request class returns true:
public function authorize()
{
return true;
}
I fix it by adding :
use Illuminate\Foundation\Http\FormRequest;
use EventHomes\Api\ApiController;
abstract class Request extends FormRequest
{
use ApiController;
public function response(array $errors)
{
foreach($errors as $key=>$error)
{
return $this->respondUnprocessable(1004,'validation',$errors[$key][0]);
}
}
}
In requst.php
I have a very simple Rule method in request class like below.
public function rules()
{
return [
'Subject' => 'required|max:50',
'Description' => 'required|max:500',
'DepartmentID' => 'required|integer|min:1',
'PriorityID' => 'required|integer|min:1'
];
}
Inside Controller Action method, below is the code.
private function SaveChanges(\App\Http\Requests\TicketRequest $request) {
$v = \Validator::make($request->all(), [
]);
$DepartmentAdmins = $this->getDepartmentAdmins();
//Check if department admin missing then no need to add the record
if($DepartmentAdmins == null || count($DepartmentAdmins) == 0) {
$v->errors()->add('MissingAdmins', 'Department admin missing.');
return redirect()->back()->withErrors($v->errors());
}
}
Question:
As we can see in the rule method there are 4 form fields. Is there any way to shift the check for Department Admin existence from Controller Action method to request class?
Laravel's Request has after hook that can be run after normal validation completes. This is how you can use it in your case:
namespace App\Http\Requests;
use App\Http\Requests\Request;
use App\Models\Property;
use Illuminate\Validation\Validator;
class SomeRequest extends Request
{
/**
* Get the validator instance for the request.
*
* #return Validator
*/
protected function getValidatorInstance()
{
$instance = parent::getValidatorInstance();
$instance->after(function ($validator) {
$this->validateDepartmentAdmins($validator);
});
return $instance;
}
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'Subject' => 'required|max:50',
'Description' => 'required|max:500',
'DepartmentID' => 'required|integer|min:1',
'PriorityID' => 'required|integer|min:1'
];
}
/**
* #param Validator $validator
*/
public function validateDepartmentAdmins(Validator $validator)
{
$DepartmentAdmins = $this->getDepartmentAdmins();
//Check if department admin missing then no need to add the record
if($DepartmentAdmins == null || count($DepartmentAdmins) == 0) {
$validator->errors()->add('MissingAdmins', 'Department admin missing.');
}
}
That way you won't have to do any validation in your SaveChanges controller method.
This code is used in Laravel 5.1, but I believe it will work the same in 5.2.
The Form Request Class basically has two methods. "authorize" and "rules". the best way to shift the check for Department Admin existense is to add your own custom validator(for example named "adminCountValidator") and implement your logic for checking the number of administrators there. Then use yoir newly defined validator in "rules" method like this:
public function rules()
{
return [
'Subject' => 'required|max:50',
'Description' => 'required|max:500',
'DepartmentID' => 'required|integer|min:1|adminCountValidator',
'PriorityID' => 'required|integer|min:1'
];
}
if you define a custome validation rule, you can also define the associated error message and your controller action will be much more cleaner. here is the link for defining your own custom validator
custom-validation-rules
here is a sample code for adding a custom validator within a service provider
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Validator::extend('adminCountValidator', function($attribute, $value, $parameters, $validator) {
/*
implement your getDepartmentAdmins()
function here and return true or false
*/
});
}
I am creating Rest Full Api for mobile application, I am validating request it redirects me to the login page with errors.
Here is my ApiController (I have created for all api):
use App\User as UserModel;
use App\Fb_friend as FbFriendsModel;
use App\Http\Requests\UserRequest;
class ApiController extends Controller
{
/**
* Create a new movie model instance.
*
* #return void
*/
public function __construct(UserModel $user, FbFriendsModel $fb_friends){
$this->user = $user;
$this->fb_friends = $fb_friends;
}
public function createUser (UserRequest $request) {
// some code here
}
Route:
Route::post('createUser', ['as' => 'createUser', 'uses' => 'ApiController#createUser']);
UserRequest.php:
public function rules()
{
return [
'fb_id' => 'required|unique:users',
'username' => 'required|unique:users',
'email' => 'required|unique:users',
'image' => 'required',
'device_id' => 'required',
'status' => 'required',
];
}
I have override a function Request.php for error formatting:
abstract class Request extends FormRequest
{
protected function formatErrors(Validator $validator)
{
return [$validator->messages()->toJson()];
}
}
When I try to call service via postman, it returns me error in json format but it also print the login page, I m not getting why?
If you are using Postman for testing API's, it is not necessary to override the response() in Request class, One can follow the following steps,
make return type in authorize() in your custom Request as true,
public function authorize()
{
//make it true
return true;
}
Go to headers section in your Postman and define Accept type,
Accept:application/json
Now hit the endpoint of your API and bam..working fine for me.
It has been done by override the response method in app/Http/Requests/Request.php
public function response(array $errors) {
if ($this->ajax() || $this->wantsJson() || Request::isJson()) {
$newError = [];
$newError['result'] = false;
$newError['errors'] = $errors;
// in the above three lines I have customize my errors array.
return new JsonResponse($newError, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors);
}
We also need to use JsonResponse class at the top
use Illuminate\Http\JsonResponse;
Source: https://laracasts.com/discuss/channels/general-discussion/laravel-5-validation-formrequest