I am using a FormRequest to validate a delete request, but I do not need to access the request inside of my controller. I just need it for validation.
I find the unused variable annoying, but I can't see anything in the docs that suggests I can call anything on the class to validate it.
Form request:
class DeleteList extends FormRequest
{
protected $errorBag = 'delete_list';
/**
* 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 [
'confirm' => 'required|accepted'
];
}
}
Controller:
public function postDelete(DeleteList $request, ListService $listService, $site, $list)
{
$listService->delete($site, $list);
return redirect()->route('site.lists', ['site' => $site]);
}
You could do this:
public function postDelete(ListService $listService, $site, $list)
{
$this->validate(request(), ['confirm'=>'required|accepted']);
$listService->delete($site, $list);
return redirect()->route('site.lists', ['site' => $site]);
}
But personally I like your solution better, even if it means having an unused variable.
Related
Merry Christmass team!
I have a problem trying to figure out how to pass a method with my own constraints in a controller that is bound to request patter paradigm:
Sample Controller Code:
class SampleController
{
protected $model = SampleModel::class;
protected $indexRequest = IndexRequest::class;
}
Request Class
class IndexRequest 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 [];
}
Assume I have a method that I want to do something different.Say I want to fetch some data based on some column constraints.
Whats the approach?
I created my request in which I validate the data. Some of this data I need to convert to JSON.
For this, I decided to create middleware. But when I try to get a request in the controller, it doesn't have anything I added in the middleware.
This seems to be because it is not my own request 'MyRequest $request' that gets into the middleware. How can this be resolved?
middlevare
class TransformData
{
/**
* #param $request
* #param Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$next($request);
$request->merge(['user_id' => \Auth::user()->id]);
$request->merge(['select_products' => json_encode($request->select_products)]);
return $request;
}
}
my request is called OfferRequest, there are just validation rules
controller
class BaseController extends Controller
{
public function __construct()
{
$this->middleware('transform.offer.data')->only('store');
}
}
public function store(OfferRequest $request)
{
$all = $request->all();
dd($all); // there is nothing here that I added in the middleware
}
I added the middleware to the kernel - protected $routeMiddleware
Update your Middleware like the following :
TransformData.php
class TransformData
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->merge([
'user_id' => \Auth::user()->id,
'select_products' => json_encode($request->select_products)
]);
return $next($request);
}
}
The above would work, but I would suggest you to use prepareForValidation() method link in form request class for update request data.
Using prepareForValidation() method you could add or update your request parameters.
OfferRequest.php
class AccountFilterRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
//your rules
}
/**
* Modify the input.
*/
public function prepareForValidation()
{
$this->merge([
'user_id' => \Auth::user()->id,
'select_products' => json_encode($request->select_products)
]);
}
}
prepareForValidation() method is executed before validation so you can add new data or update data and validate those.
I'm implementing an API in Laravel using JSON:API specification.
In it I have a resource, let's call it Ponds, with many-to-many relationships with another resource, let's call it Ducks.
According to JSON:API specs in order to remove such relationship i should use DELETE /ponds/{id}/relationships/ducks endpoint, with request of following body:
{
"data": [
{ "type": "ducks", "id": "123" },
{ "type": "ducks", "id": "987" }
]
}
This is handled by PondRemoveDucksRequest, which looks as follows:
<?php
...
class PondRemoveDucksRequest extends FormRequest
{
public function authorize()
{
return $this->allDucksAreRemovableByUser();
}
public function rules()
{
return [
"data.*.type" => "required|in:ducks",
"data.*.id" => "required|string|min:1"
];
}
protected function allDucksAreRemovableByUser(): bool
{
// Here goes the somewhat complex logic determining if the user is authorized
// to remove each and every relationship passed in the data array.
}
}
The problem is that if I send a body such as:
{
"data": [
{ "type": "ducks", "id": "123" },
{ "type": "ducks" }
]
}
, I get a 500, because the authorization check is triggered first and it relies on ids being present in each item of the array. Ideally I'd like to get a 422 error with a standard message from the rules validation.
Quick fix I see is to add the id presence check in the allDucksAreRemovableByUser() method, but this seems somewhat hacky.
Is there any better way to have the validation rules checked first, and only then proceed to authorization part?
Thanks in advance!
1 - Create abstract class called "FormRequest" inside App\Requests directory and override the
validateResolved() method:
<?php
namespace App\Http\Requests;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
abstract class FormRequest extends BaseFormRequest
{
/**
* Validate the class instance.
*
* #return void
* #throws AuthorizationException
* #throws ValidationException
*/
public function validateResolved()
{
$validator = $this->getValidatorInstance();
if ($validator->fails())
{
$this->failedValidation($validator);
}
if (!$this->passesAuthorization())
{
$this->failedAuthorization();
}
}
}
2 - Extend your FormRequests with custom FormRequest
<?php
namespace App\Http\Requests\Orders;
use App\Http\Requests\FormRequest;
class StoreOrderRequest extends FormRequest
{
}
add $this->getValidatorInstance()->validate(); at beggining of authorize() method
The most cleanest solution I found to solve it was by creating a small trait for the FormRequest and use it anytime you want to run validation before the authorization, Check the example bellow:
<?php
namespace App\Http\Requests\Traits;
/**
* This trait to run the authorize after a valid validation
*/
trait AuthorizesAfterValidation
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Set the logic after the validation
*
* #param $validator
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
if (! $validator->failed() && ! $this->authorizeValidated()) {
$this->failedAuthorization();
}
});
}
/**
* Define the abstract method to run the logic.
*
* #return void
*/
abstract public function authorizeValidated();
}
Then in your request class:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\Traits\AuthorizesAfterValidation;
class SomeKindOfRequest extends FormRequest
{
use AuthorizesAfterValidation;
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorizeValidated()
{
return true; // <---- Set your authorization logic here
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
Source https://github.com/laravel/framework/issues/27808#issuecomment-470394076
Here is a slightly different approach than what you are attempting, but it may accomplish the desired outcome for you.
If you are trying to validate whether the given duck id belongs to the user, this can be done in the rule itself as follows:
"data.*.id" => "exists:ducks,id,user_id,".Auth::user()->id
This rule asks if a record exists in the ducks table which matches the id and where the user_id is the current logged in user_id.
If you chain it to your existing rules (required|string|min:1), using 'bail', then it wouldn't run the query unless it had passed the other three rules first:
"data.*.id" => "bail|required|string|min:1|exists:ducks,id,user_id,".Auth::user()->id
I'm using laravel 5.5
I have a Request that I've built but the required rule is not working correctly.
Route
Route::get('v1/learning_centre/user/{userId}/course/list', 'API\LearningCentre#userCourses');
Controller
public function userCourses(GetUserCourses $request)
{
$courses = User::findOrFail($request->userId)
->courses()
->get();
return new CourseResourceCollection($courses);
}
Request
namespace App\Http\Requests\LearningCentre;
use Illuminate\Foundation\Http\FormRequest;
class GetUserCourses 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 [
'userId' => 'required|integer'
];
}
/**
* Get the error messages for the defined validation rules.
*
* #return array
*/
public function messages()
{
return [
'userId.required' => 'A User is required',
];
} }
If I turn off the required rule I can get to the controller. If I have the required rule in the request I get a 302. I am passing in a valid userId in phpunit. Without the request rules my code works as intended.
Any ideas?
You should be using route model binding to validate a required GET parameter in this situation, not a FormRequest class, which, as the name should indicate, are intended for form requests.
Your route:
Route::get('v1/learning_centre/user/{user}/course/list', 'API\LearningCentre#userCourses');
Your controller:
public function userCourses(User $user) {
If a user ID is missing (or an invalid one used), your controller will automatically throw a ModelNotFoundException, which Laravel by default returns as a 404.
I don't think that my validator extentions are working, but I can't fathom why.
Service Provider.
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Validator::extend('fail', function ($attribute, $value, $parameters, $validator) {
return false;
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}
I know the Service Provider's boot is firing because if I dd(); inside the boot method I get output. If I add a dd(); to the extend closure function, I do not get any output.
Request
class SaveOrder 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()
{
$rules = [
'customer_id' => 'in:' . Customer::getImplodedCurrentTeamKeys(),
'pin' => 'fail'
];
return $rules;
}
}
I know the request is validating correctly because if I change the rule to 'pin' => 'required' and don't give in put I get a fail.
Why is my custom validation rule not working?
I found my solution at the very bottom of the Laravel Validation docs page: (https://laravel.com/docs/5.4/validation)
For a rule to run even when an attribute is empty, the rule must imply that the attribute is required. To create such an "implicit" extension, use the Validator::extendImplicit() method:
By changing the method from extend to extendImplicit , my problem was solved.
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Validator::extendImplicit('fail', function ($attribute, $value, $parameters, $validator) {
return false;
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}