In my blade form, I have an input field that asks for a hour value, lets say input_stage_hour
I am trying to convert this to minutes AFTER validation, but BEFORE it hits my controller ... In my form request im using the passedValidation method to convert hours to minutes, then in my controller i am filling my model using $request->validated()
The problem is, it is passing through the original value and not the converted value.
My cut down form request is below;
public function rules()
{
return [
'input_stage_hour' => ['required', 'numeric', 'integer'],
];
}
protected function passedValidation()
{
$this->replace([
'input_stage_hour' => intval($this->input_stage_hour) * 60,
]);
}
If I pass a value like 2 into the input_stage_hour and then dd($request->validated()) in my controller, it still shows the value 2 instead of 120
The data that comes from that validated method on the FormRequest is coming from the Validator not the FormRequest itself. So if you really wanted to do this with the FormRequest and be able to call that validated method you would have to adjust the data the Validator is holding or adjust the validated method.
Example attempting to adjust the data the Validator is holding:
protected function passedValidation()
{
$data = $this->validator->getData();
$this->validator->setData(
['input_stage_hour' => $data['input_stage_hour'] * 60] + $data
);
}
If you wanted to override the validated method:
public function validated()
{
$data = parent::validated();
return ['input_stage_hour' => $data['input_stage_hour'] * 60] + $data;
}
Related
For validating form validation rules I currently stored them in User Model and use it in Register Controller, User controller in admin panel, User Controller in APIs and some other places, but currently it's very hard to maintain because each controller needs a slightly different set of rules and when I change the rules in User Model other controllers will not work anymore. So how to avoid duplication in rules and still keep the code maintainable?
Approach I often use is to write a HasRules trait for my models, it looks something like this:
trait HasRules
{
public static function getValidationRules(): array
{
if (! property_exists(static::class, 'rules')) {
return [];
}
if (func_num_args() === 0) {
return static::$rules;
}
if (func_num_args() === 1 && is_string(func_get_arg(0))) {
return array_get(static::$rules, func_get_arg(0), []);
}
$attributes = func_num_args() === 1 && is_array(func_get_arg(0))
? func_get_arg(0)
: func_get_args();
return array_only(static::$rules, $attributes);
}
}
Looks messy, but what it does is allows you to retrieve your rules (from a static field if such exists) in a variety of ways. So in your model you can:
class User extends Model
{
use HasRules;
public static $rules = [
'name' => ['required'],
'age' => ['min:16']
];
...
}
Then in your validation (for example, in your FormRequest's rules() method or in your controllers when preparing rules array) you can call this getValidationRules() in variety of ways:
$allRules = User::getValidationRules(); // if called with no parameters all rules will be returned.
$onlySomeRules = [
'controller_specific_field' => ['required'],
'name' => User::getValidationRules('name'); // if called with one string parameter only rules for that attribute will be returned.
];
$multipleSomeRules = User::getValidationRules('name', 'age'); // will return array of rules for specified attributes.
// You can also call it with array as first parameter:
$multipleSomeRules2 = User::getValidationRules(['name', 'age']);
Don't be afraid to write some code for generating your custom controller specific rules. Use array_merge and other helpers, implement your own (for example, a helper that adds 'required' value to array if it's not there or removes it etc). I strongly encourage you to use FormRequest classes to encapsulate that logic though.
You can try using laravel's validation laravel documentation
it is really easy to use and maintain just follow these steps:
run artisan command: php artisan make:request StoreYourModelName
which will create a file in App/Http/Requests
in the authorize function set it to:
public function authorize()
{
return true;
}
then write your validation logic in the rules function:
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
Custom error messages add this below your rules function:
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
Lastly to use this in your controller just add it as a parameter in your function.
public function create(Request $request, StoreYourModelName $storeYourModelName)
{
//
}
and that's all you need to do this will validate on form submission if validation passes it will go to your controller, keep in mind your validation logic does not have to be like mine thought i would show you one way that it can be done..
I want to pass $params['user_id'] to $fieldValidations and check if the hour is unique for specific user_id not for all hours hour in the database table
I created a model post
class Post extends Model
{
protected $fillable = ['user_id', 'hour'];
public static $fieldValidations = [
'user_id' => 'required',
'hour' => 'required|date_format:Y-m-d H:i:s|unique:post,hour,NULL,user_id,'
];
}
and a controller post
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$params = $request->all();
$params['user_id'] = 12;
$validator = Validator::make($params, Post::$fieldValidations);
if ($validator->fails()) {
return Response::json($validator->errors()->all(), 422);
}
}
}
I don't think you can do this using the unique validation rule. From Laravel 5.7 documentation:
The field under validation must be unique in a given database table.
Note it says table and not column.
You may have to just query the database and return a JSON response error if it fails. Also, in your current code inside the validation rules, you are specifying that user_id is the primary id key column in the post table. I think that is likely an error and should be removed, even though it's irrelevant given that you can't accomplish what you want using the unique rule. Also, you ended the rule with a comma.
if (Post::where(['user_id' => $params['user_id'], 'hour' => $params['hour']])->exists()) {
return response()->json(['status' => 'error', 'msg' => 'Error', 'errors' => ['hour_error' => ['That hour already exists on the user!']]], 422);
}
Lastly, instead of using $params = $request->all(), I prefer to use the request() helper function and just inline it into the rest of the code. But, that's up to you.
I am currently doing a small project to learn some laravel validation and ran into a problem.
The API endpoint is api/test/schoolbook?start= and my validation is
'start' => ['date_format:Y-m-d H:i:s']
While this works like a charm and sorts the schoolbooks by a start year, i think my validation has some error. It validates if start is equal to the defined date format, all good. but if i now parse just ?start=without any thing, it still goes through, but doesn't throw an error message (it just returns everything without sorting)
Is there a way i can validate this better and prevent the query string parameter to be empty?
If start is not passed, it should return all the records, so i cant make it required really.
So the scenarios are:
?start=date is passed in the right format and returns all the schoolbooks by the passed date,
?start=date is not passed and returns all the records in the database
?start= should also return 'has to be in date format validation'
Thank you!
The Controller:
public function findSchoolbook(
SchoolBookRequest $request,
) : JsonResponse {
$schoolbook = $this->schoolkbool->sort($paramBag);
$response = $this->transformer()->paginator($schoolbook);
return $this->response($response);
}
The ParamBg method i use
private function getParamBag(SchoolBookRequest $request) : ParamBag
{
return ParamBag::create()
->setPage($request->get('page'))
->setPerPage($request->get('per_page'))
->setStartDate($request->get('start_at'))
}
The Request
class SchoolBookRequest extends Request
{
public function rules() : array
{
return [
'start_at' => ['date_format:Y-m-d H:i:s']
];
}
}
This should only filter in start date when present in the request:
private function getParamBag(SchoolBookRequest $request) : ParamBag
{
$parambag = ParamBag::create()
->setPage($request->get('page'))
->setPerPage($request->get('per_page'))
if ($request->has('start_at')) {
$parambag = $parambag->setStartDate($request->get('start_at'));
}
return $parambag;
}
In a form request class I use a method like this to validate input data.
class SignupRequest extends FormRequest
{
...
public function rules()
{
return [
'user.email' => 'required_with:user|email',
'user.domain_name' => 'required_with:user|string',
'user.password' => 'required_with:user|string|min:8',
'user.username' => 'required_with:user',
];
}
...
}
Later in a controller I use something like this
$data = $request->get('user', []);
return $this->response($this->userService->create($data, false), 201);
I want somehow to write to my SignupRequest which fields it should allow to be passed. So when later I get $data = $request->get('user', []); I'm sure there are only allowed fields in it.
Is this possible inside the FormRequest?
P.S. I'm aware of $request->only(['field1', 'field2', 'field3']) way, but if I want to limit the fields in SignupRequest extends FormRequest. Because if I use $request->only([...]) in my code several times, I would have to change it several times later. I want to keep it in one place.
You wouldn't need to do this with the request.
One option would be to do something like:
$user = $request->input('user', []);
$data = array_only($user, ['email', 'domain_name', 'password', 'username']);
Or you could even inline it:
$data = array_only($request->input('user', []), ['email', 'domain_name', 'password', 'username']);
Hope this helps!
FormRequest is meant to validate your request data, not control them. You could always extract the inputs you need by doing so.
$data = $request->only(['user.name', 'user.password']);
Edit : Based on your comment, you can do something like this. This allows you to store all the field names within a single request to keep them organised and easier to update.
Add this to your SignupRequest
public function loginData()
{
return array_only($this->input('user', []), ['username', 'password']);
}
Use it in the controller like so
$request->loginData();
return $this->response($this->userService->create($request->loginData(), false), 201);
When I'm adding or editing an entry to my database table websites I load the instance of the website to be modified (or a blank one for creating a website). This works great, this is my controller:
<?php namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Request;
use App\Models\User;
use App\Models\Status;
use App\Models\Website;
class WebsitesController extends Controller {
/**
* Show / Process the create website form to the user.
*
* #return Response
*/
public function create()
{
$statuses = Status::all();
$users = User::all();
$website = Website::find(0);
return view('admin/websites/create', [
'statuses' => $statuses,
'users' => $users,
'website' => $website
]);
}
public function update($id)
{
$statuses = Status::all();
$users = User::all();
$website = Website::findOrFail($id);
return view('admin/websites/update', [
'statuses' => $statuses,
'users' => $users,
'website' => $website
]);
}
}
The problem is when I submit the form and there is an error. The user is returned to the page and the errors displayed. I also pass the users input back so I can repopulate the form with what they entered. But how can I replace the values in website with the values from input if it's present without actually saving to the database? I've been playing around with this all day and not found a working solution.
My create method is this:
public function postCreate(Request $request)
{
$v = Validator::make($request->all(), Website::rules());
if ($v->fails())
{
return redirect()->back()->withInput()->withErrors($v);
}
$website = Website::create($request->all());
return redirect()->action('Admin\HomeController#index')->with('messages', [['text' => 'Website created', 'class' => 'alert-success']]);
}
I'm passing the input back to the original form, but the form populates its values from the Website Eloquent model. **How can I get the input from $request->all() into $website?
I've tried using fill(), but I just get Call to a member function fill() on null when using it in the create function.
The create method attempts to insert a record to the database and returns an instance of the model if it is successful. If you use create() with invalid values, the insert will fail. I think this is why there is a null instead of an instance of the model, which causes your error:
Call to a member function fill() on null
Instead of using create() You could create the website model without the database insert using
$website = new Website;
$website->fill($request->all());
before you run the validation. If the validation passes, then you can insert to your database with $website->save();, otherwise, it will not try to save, but the model should be available for you to use in your form.