So I have a Laravel Application, which has many Controllers to handle various aspects of the applications.
Now each controller has various methods. Most of the methods have validations rules defined such as:
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray);
if ($validator->fails()){
return Response::json(['response'=>implode(', ',$validator->messages()->all())],422);
}
Now the following line:
return Response::json(['response'=>implode(', ',$validator->messages()->all())],422);
actually returns whatever is wrong with the validation rules.
My question is: Is there any way to get all possible error messages programmatically?
Of course, one way to do it is going around the rule by rule and make a list manually but there are hundreds of the methods scattered over various controllers.
So, if anyone could point me in the direction of taking all the error messages in some easier way, would be much appreciated.
Thank you in advance!
UPDATE
So to clear further I need a list of all possible errors, like for above code the list will be like:
['id is required', 'id must be an integer', 'status is required', 'status must be an string']
UPDATE 2
Please keep in mind that there are hundreds of methods and also I do not want to change the final response of the method but to have some sort of external script which can help me getting the error messages without interfering with the controllers much.
In order to do that you have to extend Validator class and write a method that will iterate all rules and explicitly add error messages as if they failed.
First, create a new file app\Http\Custom\Validator.php:
<?php
namespace App\Http\Custom;
use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationRuleParser;
use Illuminate\Validation\Validator as BaseValidator;
class Validator extends BaseValidator {
/** #var MessageBag */
protected $errorMessages;
/** #var array */
protected $hasExplicitFileErrorMessage;
protected $explicitFileRules = [
'File', 'Image', 'Mimes', 'Mimetypes', 'Dimensions',
];
function availableErrors()
{
$this->errorMessages = new MessageBag();
$this->hasExplicitFileErrorMessage = [];
foreach($this->rules as $attribute => $rules) {
$attribute = str_replace('\.', '->', $attribute);
foreach($rules as $rule) {
[$rule, $parameters] = ValidationRuleParser::parse($rule);
if($rule == '') {
continue;
}
if(($keys = $this->getExplicitKeys($attribute)) &&
$this->dependsOnOtherFields($rule)) {
$parameters = $this->replaceAsterisksInParameters($parameters, $keys);
}
// explicitly add "failed to upload" error
if($this->hasRule($attribute, $this->explicitFileRules) && !in_array($attribute, $this->hasExplicitFileErrorMessage)) {
$this->addFailureMessage($attribute, 'uploaded', []);
$this->hasExplicitFileErrorMessage[] = $attribute;
}
if($rule instanceof RuleContract) {
$messages = $rule->message() ? (array)$rule->message() : [get_class($rule)];
foreach($messages as $message) {
$this->addFailureMessage($attribute, get_class($rule), [], $message);
}
} else {
$this->addFailureMessage($attribute, $rule, $parameters);
}
}
}
return $this->errorMessages->all();
}
function addFailureMessage($attribute, $rule, $parameters = [], $rawMessage = null)
{
$this->errorMessages->add($attribute, $this->makeReplacements(
$rawMessage ?? $this->getMessage($attribute, $rule), $attribute, $rule, $parameters
));
}
// we have to override this method since file-type errors depends on data value rather than rule type
protected function getAttributeType($attribute)
{
if($this->hasRule($attribute, $this->explicitFileRules)) {
return 'file';
}
return parent::getAttributeType($attribute);
}
}
Next, let's register this class in Validation factory:
<?php
namespace App\Providers;
use App\Http\Custom\Validator; // <-- our custom validator
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
public function boot()
{
app('validator')->resolver(function ($translator, $data, $rules, $messages) {
return new Validator($translator, $data, $rules, $messages);
});
}
}
And... that's all. Let's test it:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class HomeController extends Controller {
function index(Request $request)
{
$rules = [
'id' => 'required|int|between:2,10',
'status' => 'required_with:nonexisting|string|email',
'avatar' => 'required|file|mimes:png|max:1000',
'company' => 'required_without:id|unique:companies,id'
];
$validator = Validator::make([], $rules);
dump($validator->availableErrors());
}
}
array:13 [▼
0 => "The id field is required."
1 => "The id must be an integer."
2 => "The id must be between 2 and 10."
3 => "The status field is required when nonexisting is present."
4 => "The status must be a string."
5 => "The status must be a valid email address."
6 => "The avatar failed to upload."
7 => "The avatar field is required."
8 => "The avatar must be a file."
9 => "The avatar must be a file of type: png."
10 => "The avatar may not be greater than 1000 kilobytes."
11 => "The company field is required when id is not present."
12 => "The company has already been taken."
]
It isn't pretty but here's my shot:
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(), $validationArray);
if ($validator->fails()) {
$messages = [];
$invalid_fields = array_keys($validator->messages()->toArray());
$rules = $v->getRules();
foreach($invalid_fields as $invalid_field) {
foreach($rules[$invalid_field] as $rule) {
if(str_contains($rule, ':') {
// complex rules that have parameters (min, between, size, format)
// are more difficult to work with. I haven't figured out how to do them yet
// but you should get the idea.
continue;
} else {
$messages[] = str_replace(':attribute', $invalid_field, $validator->getTranslator()->get("validation.$rule"));
}
}
}
return Response::json(['response' => implode(', ', $messages)], 422);
}
Number 1: Like I mentioned in my comment under the question, what you're trying to achieve may be done in simpler way.
Number 2: Since you do not want to change your already written code where you got ->messages() then you could do the following. I will list the steps and provide an example code.
We need to override Laravel's validator, (Validation) Factory, and ValidationService provider classes.
In App\Services folder you can create two classes Validator and ValidationFactory
in App\Providers create a class ValidationServiceProvider
Go into config/app.php file and under providers replace Illuminate\Validation\ValidationServiceProvider::class with App\Providers\ValidationServiceProvider::class
Validator class looks like so:
namespace App\Services;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationRuleParser;
use Illuminate\Contracts\Translation\Translator;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Illuminate\Contracts\Validation\Rule as RuleContract;
class Validator extends \Illuminate\Validation\Validator
{
/**
* #var MessageBag $all_messages
*/
protected $all_messages;
public function __construct(Translator $translator, array $data, array $rules, array $messages = [], array $customAttributes = [])
{
parent::__construct($translator, $data, $rules, $messages, $customAttributes);
$this->all_messages = new MessageBag;
$this->getAllFormattedMessages();
}
public function makeAllRulesMessages($attribute, $rule, $parameters)
{
$this->all_messages->add($attribute, $this->makeReplacements(
$this->getMessage($attribute, $rule), $attribute, $rule, $parameters
));
}
public function messages(bool $validated_rules_messages = false)
{
return $validated_rules_messages
? $this->validatedMessages()
: $this->all_messages;
}
/**
* This is here in case the true validated messages are needed
*
* #return MessageBag
*/
public function validatedMessages()
{
return parent::messages();
}
public function getAllFormattedMessages()
{
// We'll spin through each rule and add all messages to it.
foreach ($this->rules as $attribute => $rules) {
$attribute = str_replace('\.', '->', $attribute);
foreach ($rules as $rule) {
// First we will get the correct keys for the given attribute in case the field is nested in
// an array. Then we determine if the given rule accepts other field names as parameters.
// If so, we will replace any asterisks found in the parameters with the correct keys.
[$rule, $parameters] = ValidationRuleParser::parse($rule);
if (($keys = $this->getExplicitKeys($attribute)) &&
$this->dependsOnOtherFields($rule)) {
$parameters = $this->replaceAsterisksInParameters($parameters, $keys);
}
$value = $this->getValue($attribute);
if ($value instanceof UploadedFile && $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules))
) {
$this->makeAllRulesMessages($attribute, 'uploaded', []);
} elseif ($rule instanceof RuleContract) {
$this->makeCustomRuleMessage($attribute, $rule);
} else {
$this->makeAllRulesMessages($attribute, $rule, $parameters);
}
}
}
}
/**
* #param $attribute
* #param \Illuminate\Contracts\Validation\Rule $rule $rule
*/
public function makeCustomRuleMessage($attribute, $rule)
{
$this->failedRules[$attribute][get_class($rule)] = [];
$messages = (array)$rule->message();
foreach ($messages as $message) {
$this->all_messages->add($attribute, $this->makeReplacements(
$message, $attribute, get_class($rule), []
));
}
}
}
This class does one thing in summary, get all the messages of the passed rules into $all_messages property of the class. It extends and allows the base validation class run, and simply overrides messages() method to make all the collected rules available for use.
ValidationFactory overrides Illuminate\Validation\Factory and it looks like so:
namespace App\Services;
use Illuminate\Validation\Factory;
class ValidationFactory extends Factory
{
/**
* Resolve a new Validator instance.
*
* #param array $data
* #param array $rules
* #param array $messages
* #param array $customAttributes
* #return \Illuminate\Validation\Validator
*/
protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
{
if (is_null($this->resolver)) {
return new \App\Services\Validator($this->translator, $data, $rules, $messages, $customAttributes);
}
return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
}
}
This class does only one thing, overrides resolve() method in this class by making use of the instance of our custom \App\Services\Validator class instead.
ValidationServiceProvider extends Illuminate\Validation\ValidationServiceProvider and overrides registerValidationFactory() method and it looks like so:
namespace App\Providers;
use App\Services\ValidationFactory;
use Illuminate\Validation\ValidationServiceProvider as BaseValidationServiceProvider;
class ValidationServiceProvider extends BaseValidationServiceProvider
{
protected function registerValidationFactory()
{
$this->app->singleton('validator', function ($app) {
$validator = new ValidationFactory($app['translator'], $app);
// The validation presence verifier is responsible for determining the existence of
// values in a given data collection which is typically a relational database or
// other persistent data stores. It is used to check for "uniqueness" as well.
if (isset($app['db'], $app['validation.presence'])) {
$validator->setPresenceVerifier($app['validation.presence']);
}
return $validator;
});
}
}
What the above class does is also to instruct the provide to make use of our App\Services\ValidationFactory whenever the app requires one.
And we are done. All validation messages will be shown even if one of our validation rules failed.
Caveats
In order to achieve this, we needed to make a lot of changes and overriding. Except really critical this may signal that something about the app's design looks wrong.
Laravel validation implementation may change in future release and therefore may become a problem maintaining these changes.
I cannot tell if there are other side effects that might happen for overriding Laravel's default validation implementation or if all the rules return the right messages.
Normally you only want to return failed validation messages to user rather than all the possible failures.
I think that functions failed() (get the failed validation rules) or errors() (get the message container for the validator) may help you. If it does not - go to https://laravel.com/api/5.8/Illuminate/Validation/Validator.html and I hope that you find needed function.
I think you are looking for a way to have custom error messages. if this is the case then the answer is like this:
$messages = [
'id.required' => 'id is required',
'id.integer' => 'id must be an integer',
'status.required' => 'status is required',
'status.string'=> 'status must be an string'
];
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray, $messages);
more info you can find here.
I hope this is what you are looking for and my answer is helping you:)
Based on the Laravel Form Validation Procedure you can write the statement by following:
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray);
if ($validator->fails()){
return Response::json(['response'=> validator->errors())],422);
}
Where errors() method return all the errors as associative array where the message will be associate with the field name accordingly and that's how you can get the errors.
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'm going a little be crazy with my simple validator.
$data = ['dam' => 1, 'sir' => 2];
$v = Validator::make(
$data,
['dam' => 'exists:individuals,id'],
['dam.exists' => 'Object not found']
);
$validateLab = $v->validate();
dd($validateLab);
In the table individuals, there is an entry for id=1, but not id=2. If I run the code as it is, I get "true". If I change 'dam' => 1 to 'dam' => 2, I get
"The given data was invalid." on line 306 of
/Applications/MAMP/htdocs/mysamples/vendor/laravel/framework/src/Illuminate/Validation/Validator.php :
...
/**
* Run the validator's rules against its data.
*
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
public function validate()
{
if ($this->fails()) {
throw new ValidationException($this);
}
}
...
Why? Why it does not return "Object not found"?
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 5 years ago.
Improve this question
I have my custom Request, which extends the Backpack CrudController.
Now I would like to override the prepareForValidation of the ValidatesWhenResolvedTrait since it looks like the right place to modify my incoming data, but I can't figure out how ...
So my first question is, can I override this method? Its protected ...
protected function prepareForValidation()
And my second question, how can I modify my input on the Request or FormRreuqest objects?
Here is my RequestClass
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Config;
class DonationsRequest extends \Backpack\CRUD\app\Http\Requests\CrudRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// only allow updates if the user is logged in
return \Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|max:255',
'email' => 'required|email',
'dob' => 'required|date',
'newsletter' => 'required|boolean',
'country' => 'sometimes|required|in:'.implode(',', Config::get('validation.countries')),
'street' => 'sometimes|required|string|max:255',
'zip' => 'sometimes|required|string|between:4,5',
'city' => 'sometimes|required|string|between:4,255',
'amount' => 'required|numeric|between:1,'.Config::get('donations.max'),
'type' => 'required|in:oo,monthly',
'provider' => 'sometimes|string|nullable',
'product_id' => 'sometimes|exists:products,id|nullable',
'campaign_id' => 'required|exists:campaigns,id',
'status' => 'sometimes|required|in:pending,canceled,success,error',
'profile' => 'sometimes|string|regex:/^profile[0-9]+$/|nullable',
];
}
/**
* Get the validation attributes that apply to the request.
*
* #return array
*/
public function attributes()
{
return [
//
];
}
/**
* Get the validation messages that apply to the request.
*
* #return array
*/
public function messages()
{
return [
//
];
}
private function prepareForValidation()
{
dd('getValidatorInstance custom');
$this->sanitizeInput();
return parent::getValidatorInstance();
}
private function sanitizeInput()
{
dd('sanitizeInput custom');
$data = $this->all();
dd($data);
// overwrite the newsletter field value to match boolean validation
$data['newsletter'] = ($data['newsletter'] == 'true' || $data['newsletter'] == '1' || $data['newsletter'] == true) ? true : false;
return $data;
}
private function validate() {
dd('validate');
}
}
As you can see, I first tried to override the getValidatorInstance method, since this looked like the common aproach to this, but it is not executed (so not overridden - protected?).
Although I didn't tried but it seems it should work you can override validationData from Illuminate\Foundation\Http\FormRequest class like.
/**
* Get data to be validated from the request.
*
* #return array
*/
protected function validationData()
{
$all = parent::validationData();
//e.g you have a field which may be json string or array
if (is_string($playerIDs = array_get($all, 'player_id')))
$playerIDs = json_decode($playerIDs, true);
$all['player_id'] = $playerIDs
return $all;
}
or you can override all method in Illuminate\Http\Concerns\InteractsWithInput trait
/**
* Get all of the input and files for the request.
*
* #return array
*/
public function all()
{
$all = parent::all();
//then do your operation
if (is_string($playerIDs = array_get($all, 'player_id')))
$playerIDs = json_decode($playerIDs, true);
$all['player_id'] = $playerIDs
return $all;
}
Could you modify the request?
$request->merge(['field' => 'new value']);
Well I am sure,this can help in modifying The input, it worked for me.[laravel 5.4]
place this
$input['url'] = $url;
$this->replace($input);
dd($input);
in listFormRequest. (use $all instead of $input, if you follow above used answer).
This only changes input,which is available even in controller. You still need to find a way to insert it into DB, or do something else to use modified input for using it in blade.
Ok I found out where the error was. I did split the Frontend Request and the Backend Request Call. Since I was working on the Backend Request the Frontend Request was not overwriting anything ... so it was my bad, no bug there, sry for the waste of time, but a big thanks to the community!
I'm using league/fractal with JsonApiSerializer,
I've got users collection for json output.
Now I want to add some filters data to this json response (like users count for current filters).
I got this:
$resource = new Collection($dataProvider->getData(), new UserTransformer());
//the only way to include some not directly linked data i found is using setMeta():
$resource->setMetaValue('projects', $dataProvider->getProjects());
$resource->setMetaValue('somes', $dataProvider->getTasks());
But! 'projects' & 'somes' collections (yes, they are collection too) also included with 'data' key in it.
So, I've got this structure:
{
'data' => [
{//user1},{//user2},...
],
'meta' => {
'projects' => {
'data' => {...}
},
'somes' => {
'data' => {...}
}
}
}
but I want something like:
{
'data' => [
{//user1},{//user2},...
],
'meta' => {
'projects' => {...}, //there is no 'data' key
'somes' => {...} //there is no 'data' key
}
}
What should I do?
This is kinda hack but works fine without refactor Scope class which hardcoded in fractal's League\Fractal\Manager::createData() and is only way to use your own Scope class realization is to overload this method in Manager's extension.
<?php
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Class EmbedSerializer
*/
class EmbedSerializer extends JsonApiSerializer
{
const RESOURCE_EMBEDDED_KEY = 'embedded';
/**
* Serialize a collection.
*
* #param string $resourceKey
* #param array $data
* #return array
*/
public function collection($resourceKey, array $data)
{
return $resourceKey === self::RESOURCE_EMBEDDED_KEY ? $data : [$resourceKey ?: 'data' => $data];
}
/**
* Serialize an item.
*
* #param string $resourceKey
* #param array $data
* #return array
*/
public function item($resourceKey, array $data)
{
return $resourceKey === self::RESOURCE_EMBEDDED_KEY ? $data : [$resourceKey ?: 'data' => [$data]];
}
}
So, now i could use it like:
/** #var $this->fractal League\Fractal\Manager */
$this->fractal->setSerializer(new EmbedSerializer());
$projectsCollection = $this->fractal->createData(
new Collection($projects, new UserProjectTransformer(), 'embedded')
)->toArray();
$resource = new Collection($users, new UserTransformer());
$resource->setMetaValue('projects', $projectsCollection);
That's all u need. Hope this will be helpful.