9

I am trying to write a CRUD for an API. However, when the validation fail, instead of redirecting the user to the home page, I want to return json based response with the errors.

I am able to do that using the following code

public function store(Request $request)
{
    try {
        $validator = $this->getValidator($request);

        if ($validator->fails()) {
            return $this->errorResponse($validator->errors()->all());
        }

        $asset = Asset::create($request->all());

        return $this->successResponse(
            'Asset was successfully added!',
            $this->transform($asset)
        );
    } catch (Exception $exception) {
        return $this->errorResponse('Unexpected error occurred while trying to process your request!');
    }
}

/**
 * Gets a new validator instance with the defined rules.
 *
 * @param Illuminate\Http\Request $request
 *
 * @return Illuminate\Support\Facades\Validator
 */
protected function getValidator(Request $request)
{
    $rules = [
        'name' => 'required|string|min:1|max:255',
        'category_id' => 'required',
        'cost' => 'required|numeric|min:-9999999.999|max:9999999.999',
        'purchased_at' => 'nullable|string|min:0|max:255',
        'notes' => 'nullable|string|min:0|max:1000',
    ];

    return Validator::make($request->all(), $rules);
}

Now, I like to extract some of my code into a form-request to clean up my controller little more. I like to change my code to something like the code below.

public function store(AssetsFormRequest $request)
{
    try {
        if ($request->fails()) {
            return $this->errorResponse($request->errors()->all());
        }            
        $asset = Asset::create($request->all());

        return $this->successResponse(
            'Asset was successfully added!',
            $this->transform($asset)
        );
    } catch (Exception $exception) {
        return $this->errorResponse('Unexpected error occurred while trying to process your request!');
    }
}

As you can probably tell that $request->fails() and $request->errors()->all() is not going to work. How can I check if the request failed and then how can I get the errors out of the form-request?

For your reference, here is how my AssetsFormRequest class look like

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class AssetsFormRequest 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 [
            'name' => 'required|string|min:1|max:255',
            'category_id' => 'required',
            'cost' => 'required|numeric|min:-9999999.999|max:9999999.999',
            'purchased_at' => 'nullable|string|min:0|max:255',
            'notes' => 'nullable|string|min:0|max:1000',
        ];
    }
}
Junior
  • 11,602
  • 27
  • 106
  • 212
  • Possible duplicate of [Unit Test Laravel's FormRequest](https://stackoverflow.com/questions/36978147/unit-test-laravels-formrequest) – Yevgeniy Afanasyev Mar 28 '19 at 02:43
  • Friends, please, make the unit-test properly, after all, it is not only rules you are testing here, the validationData and withValidator functions may be there too. [here is my answer](https://stackoverflow.com/questions/36978147/unit-test-laravels-formrequest/55389319#55389319) – Yevgeniy Afanasyev Mar 28 '19 at 02:44

3 Answers3

17

In your AssetFormRequest class, you can override failedValidation method to the following-

public $validator = null;
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
    $this->validator = $validator;
}

Then your controller method, do anything you want with your $validator object. May be something like the following-

if (isset($request->validator) && $request->validator->fails()) {
        return response()->json($request->validator->messages(), 400);
    }

You can see this link too for further details. Hope it helps :)

Sohel0415
  • 9,523
  • 21
  • 30
4

Add this function to your Request:

public function withValidator($validator)
    {
        if ($validator->fails()) {
            Session::flash('error', 'Flash error!');
        } else {

        }

    }
PHP Worm...
  • 4,109
  • 1
  • 25
  • 48
1

It's been 2 years, but maybe this will help someone.

You can override the getValidatorInstance() method in your AssetFormRequest by adding(Tested in Laravel 6.0.4):

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Contracts\Validation\Validator;

/**
 * Get the validator instance for the request.
 *
 * @return Validator
 * @throws BindingResolutionException
 */
public function getValidatorInstance()
{
    if ($this->validator) {
        return $this->validator;
    }

    $factory = $this->container->make(ValidationFactory::class);

    if (method_exists($this, 'validator')) {
        $validator = $this->container->call([$this, 'validator'], compact('factory'));
    } else {
        $validator = $this->createDefaultValidator($factory);
    }

    if (method_exists($this, 'withValidator')) {
        $this->withValidator($validator);
    }

    $this->setValidator($validator);

    return $this->validator;
}

After that, a validator becomes available to you in your controller:

public function store(AssetsFormRequest $request)
{
    $validator = $request->getValidatorInstance();

    try {
        if ($validator->fails()) {
            return $this->errorResponse($validator->errors());
        }            
        $asset = Asset::create($validator->validated());

        return $this->successResponse(
            'Asset was successfully added!',
            $this->transform($asset)
        );
    } catch (Exception $exception) {
        return $this->errorResponse('Unexpected error occurred while trying to process your request!');
    }
}
alchi
  • 11
  • 2