Failed validation returns default error message even though custom messages supplied

quinny picture quinny · Sep 16, 2017 · Viewed 19.9k times · Source

I'm not getting the response I expect.

This is the controller code for a Location web-service request:

<?php
namespace App\Http\Controllers;

use App\Location;
use Illuminate\Http\Request;


class LocationController extends Controller
{


    /**
     * Action method to add a location with the supplied Data
     * 
     * @param \Illuminate\Http\Request $p_oRequest Request
     * 
     * @return JSON
     */
    public function add(Request $p_oRequest)
    {

        try {

            $p_oRequest->validate(
                array(
                    'name' => 'required|alpha_num',
                    'user_id' => 'required|integer',
                ),
                array(
                    'name.required' => 'Name is required',
                    'name.string' => 'Name must be alphanumeric',
                    'user_id.required' => 'Curator User Id is required',
                    'user_id.required' => 'Curator User Id must be an integer',
                )
            );

        } catch (\Exception $ex) {

            $arrResponse = array(
                'result' => 0,
                'reason' => $ex->getMessage(),
                'data' => array(),
                'statusCode' => 404
            );

        } finally {

            return response()->json($arrResponse);

        }

    }

}

The request is http://mydomain/index.php/api/v1/location/add?name=@!^

The response reason I expect is: { "result": 0, "reason": "Name must be alphanumeric", "data": [], "statusCode": 404 }

The actual response I get instead is: { "result": 0, "reason": "The given data was invalid.", "data": [], "statusCode": 404 }

Please help. This is bugging me.

Answer

quinny picture quinny · Oct 14, 2017

I've finally discovered why this isn't working. It's not an issue of errors in the implementing code or Laravel, but one of either: (i). writing good PHP code to handle the self-evident result, which clearly I didn't do; (ii). insufficient documentation within Laravel on how to actually use the validation error response. Take your pick.

Laravel's validation throws a Illuminate\Validation\ValidationError. Believe it or not, this actually defaults the error message to "The given data was invalid.", so when you catch an \Exception and retrieve its $e->getMessage(), this default error-message is what you (correctly) get.

What you need to do is capture the \Illuminate\Validation\ValidationError - which I should've done originally, duh! - and then use its methods to help you distill the error messages from it.

Here's the solution I've come up with:

<?php
namespace App\Http\Controllers;

use App\Location;
use Illuminate\Http\Request;


class LocationController extends Controller
{

    /**
     * Action method to add a location with the supplied Data
     * 
     * @param \Illuminate\Http\Request $p_oRequest Request
     * 
     * @return JSON
     */
    public function add(Request $p_oRequest)
    {
        try {
        
            $arrValid = array(
                'name' => 'required|alpha_num',
                'user_id' => 'required|integer',
            );
            $p_oRequest->validate(
                $arrValid,
                array(
                    'name.required' => 'Name is missing',
                    'name.alpha_num' => 'Name must be alphanumeric',
                    'user_id.required' => 'User Id is missing',
                    'user_id.integer' => 'User Id must be an integer',
                )
            );
            
        } catch (\Illuminate\Validation\ValidationException $e ) {
        
            /**
             * Validation failed
             * Tell the end-user why
             */
            $arrError = $e->errors(); // Useful method - thank you Laravel
            /**
             * Compile a string of error-messages
             */
            foreach ($arrValid as $key=>$value ) {
                $arrImplode[] = implode( ', ', $arrError[$key] );
            }
            $message = implode(', ', $arrImplode);
            /**
             * Populate the respose array for the JSON
             */
            $arrResponse = array(
                'result' => 0,
                'reason' => $message,
                'data' => array(),
                'statusCode' => $e->status,
            );

        } catch (\Exception $ex) {

            $arrResponse = array(
                'result' => 0,
                'reason' => $ex->getMessage(),
                'data' => array(),
                'statusCode' => 404
            );

        } finally {

            return response()->json($arrResponse);

        }
        
    }
    
}

So, indeed, Laravel was supplying the correct response, and did what it said on the side of the tin, but I wasn't applying it correctly. Regardless, as a help to future me and other lost PHP-mariners at Laravel-sea, I provide the solution.

In addition, thanks to Marcin for pointing out my buggy coding, which would've caused a problem even if I had implemented the above solution.