Laravel action when form validation generates error - laravel

I am working with a form request file like this:
ProjectCreateRequest.php
public function rules()
{
$project_name = $this->project_name;
$meta_activity = $this->meta_activity;
return [
'project_name' => 'required|max:255|unique:projects',
'customer_name' => 'required|max:255',
'otl_project_code' => 'sometimes|max:255|unique:projects,otl_project_code,NULL,id,meta_activity,'.$meta_activity,
'estimated_start_date' => 'date',
'estimated_end_date' => 'date',
'LoE_onshore' => 'numeric',
'LoE_nearshore' => 'numeric',
'LoE_offshore' => 'numeric',
'LoE_contractor' => 'numeric',
'revenue' => 'numeric',
'win_ratio' => 'integer'
];
}
There is the otl_project_code that must be unique with the meta_activity.
In case someone enters a pair of otl_project_code and meta_activity that already exists, it goes back to the create page with the error written below.
I would like to get instead that in the controller, I can catch this information, do something on the database then redirect to an update url.
Because I am working with a form validation request file, everything is entered in my controller like this:
public function postFormCreate(ProjectCreateRequest $request)
and I don't know how to catch this specific error in my controller to execute some actions with all the fields I submitted and not go back to the create page. Of course, this needs to happen only when there is the specific error I mentionned above.

Override the FormRequest response function in your ProjectCreateRequest:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->expectsJson()) {
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
That's the public response on the FormRequest class so you can write your own logic to perform DB queries and redirect where needed.

Related

Find data before validate form request laravel

I want to update the data using the request form validation with a unique email role, everything works normally.
Assume I have 3 data from id 1-3 with url:
127.0.0.1:8000/api/user/update/3
Controller:
use App\Http\Requests\Simak\User\Update;
...
public function update(Update $request, $id)
{
try {
// UPDATE DATA
return resp(200, trans('general.message.200'), true);
} catch (\Exception $e) {
// Ambil error
return $e;
}
}
FormRequest "Update":
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->id,
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
but if the updated id is not found eg:
127.0.0.1:8000/api/user/update/4
The response gets The email has already been taken.
What is the solution so that the return of the data is not found instead of validation first?
The code looks like it should work fine, sharing a few things below that may help.
Solution 1: Check if $this->id contains the id you are updating for.
Solution 2: Try using the following changes, try to get the id from the URL segment.
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->segment(4),
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
Sharing one more thing that may help you.
Some person uses Request keyword at the end of the request name. The Update sounds generic and the same as the method name you are using the request for. You can use UpdateRequest for more code readability.
What I understand from your question is, you need a way to check if the record really exists or not in the form request. If that's the case create a custom rule that will check if the record exists or not and use that rule inside your request.
CheckRecordRule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class CheckRecordRule implements Rule
{
protected $recordId;
public function __construct($id)
{
$this->recordId = $id;
}
public function passes($attribute, $value)
{
// this will check and return true/false
return User::where('id', $this->recordId)->exists();
}
public function message()
{
return 'Record not found.';
}
}
Update form request
public function rules()
{
return [
'email' => 'required|email|unique:users,email,' . $this->id.'|'. new CheckRecordRule($this->id),
];
}
So when checking for duplicate it will also check if the record really exists or not and then redirect back with the proper message.

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

Form Request, how to add additional error messages after performing a validation using an if- condition?

Adding error messages inside form requests using certain conditions.
I am making a form request in order to handle my validations. In my form, I have a couple of fields but the important ones are "Month" "Day" or "Year" pulldown. They are all set to required, and if one of them invalidates the required rule, the form request would handle it by creating another error inside the $validator.
The logic goes something like this.
If (month = fail || day = fail || year = fail) {
$validator->errors()->add('date', 'please fill out the whole date)
}
I'd like to do this somewhere in my form request after the validation.
This is my rules for the form request field.
public function rules()
{
return [
'name' => 'nullable',
'sex' => 'nullable',
'phone_number' => 'nullable',
'email' => 'nullable',
'month' => 'bail|required',
'day' => 'bail|required',
'year' => 'bail|required',
];
}
And on my view (twig template) under the select fields of month, day, year, I have this to supposedly show my error message:
{{errors.first('date')}}
So far I don't know how to write the condition and add a custom error message.
Edit: I found an answer, but I'm not sure if this is good practice. Maybe someone can find a better answer than this.
protected function failedValidation(Validator $validator) {
if (array_key_exists('month', $validator->failed()) ||
array_key_exists('day', $validator->failed()) ||
array_key_exists('year', $validator->failed()) ) {
$validator->getMessageBag()->add('date', 'sample error message');
}
}
I think it would be best to use an After Validation Hook.
You would typically add this to your Form Request.
/**
* Configure the validator instance.
*
* #param \Illuminate\Validation\Validator $validator
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
if(
$validator->errors()->has('month')
|| $validator->errors()->has('day')
|| $validator->errors()->has('year')
) {
$validator->errors()->add('date', 'Please fill out the whole date!');
}
});
}
You can check the documentation for more information.

Laravel Custom Message

Hi I am working with custom validation with laravel I am new in this right now my controller code is mention this is how I am validating the upload file.
Everything is working but I have issue displaying custom message on view page
it shows the default message not my custom message Please have a look and let me know if I am doing something wrong.
$this->validate(
$request, [
'project_file.*' => 'required|size:2048',
],
[
'project_file.required' => 'Upload File Field Is Required',
'project_file.max' => 'Upload File Field Must Be 2MB',
]
);
$messages = [
'required' => 'The File should not be more then 2Mb',
'size' => 'The must be exactly Mb.',
];
$validator = Validator::make($input, $rules, $messages);
if($validator->fails()) {
return Redirect::back()->withErrors($validator);
}
My View code to display error is:
#if ($errors->any())
<div class="alert alert-danger">
<strong>{!! implode('', $errors->all('<div>:message</div>')) !!}</strong>
</div>
#endif
I feel the issue might be because you have not passed the field name placeholder in the messages array.
You can add custom error messages for your validations. You can pass a third parameter Validator::make method.
$messages = [ 'required' => 'The :field should not be more then 2Mb' ];
$validator = Validator::make($input, $rules, $messages);
The :field place-holder will be replaced by the field name
Adding custom error messages only for a specific field using dot operation
$messages = [
'file.required' => 'The image should not be more then 2Mb',
];
Hope this helps.
It's better approach not to write validation logic in the controller because it results fat controller and quite messy controller so you can use separate request class to do in better way.
At first, use following command in console to create custom validation request class like this,
php artisan make:request PostRequest
Now, a file named PostRequest.php will be created at the app/Http/Requests/ in that file you should make validation like following.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PostRequest 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 [
//
'project_file.*' => 'required|max:2048',
];
}
public function messages()
{
return [
'required' => 'Upload File Field Is Required',
'max' => 'The File should not be more then 2Mb',
];
}
}
Now, function in which form request is passed you have to change Request to PostRequest like this so that validation is performed automatically.
public function post(PostRequest $request){
//
}

TimestampBehavior does not work because of failing validation

I have the following class with a TimestampBehaviour:
/**
* #property int $id
* #property string $name
* #property int $created_at
*/
class Workspace extends yii\db\ActiveRecord {
public static function tableName() {
return 'workspace';
}
public function behaviors() {
return [
[
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'created_at',
ActiveRecord::EVENT_BEFORE_UPDATE => false,
],
'value' => date('Y-m-d H:i:s')
],
];
}
...
}
For some reason the behavior does not populate the property. It is always empty when I try to save the model ($workspace->save()). I cannot save it since validation fails ("created_at cannot be blank"). There is nothing special with this class. Nothing is overridden. What could be the problem?
It turned out that the validation rules caused the troubles. Unexpected, since I thought all is correct. These were my rules:
public function rules() {
return [
[['id', 'name', 'created_at'], 'required'],
[['id'], 'int'],
[['name'], 'string', 'max' => 100],
[['created_at' ], 'datetime'],
];
}
created_at must not be required - that was the problem.
It is even documented:
Because attribute values will be set automatically by this behavior,
they are usually not user input and should therefore not be validated,
i.e. created_at and updated_at should not appear in the rules() method
of the model.
When $workspace->save() gets executed then the first step is the validation. And only after that step the EVENT_BEFORE_INSERT/EVENT_BEFORE_UPDATE gets triggered which causes TimestampBehaviour to populate the specified fields. And this happens only if the validation was successful! (if you var_dump you will indeed see an empty created_at.) Too late, validation has taken place already and I've got the validation error.
Recommended solution is to remove created_at from the required rule. Other approaches are also possible, of course (e.g. turn off validation or pass the properties that should be validated when save() gets called).
Add behaviour like bellow
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'create_time',
'updatedAtAttribute' => 'update_time',
'value' => new Expression('NOW()'),
],
];
}
and add it to safe records in your model class.
public function rules()
{
return array(
array('create_time,update_time', 'safe'),
);
}

Resources