Добавление пользовательских ошибок проверки в форму Laravel

У меня есть базовая форма, позволяющая пользователю изменить свой адрес электронной почты, и я делаю следующую проверку, прежде чем изменять адрес электронной почты:

// Set up the form validation
$validator = Validator::make(
    Input::all(),
    array(
        'email' => 'email|unique:users',
        'password' => 'required'
    )
);

// If validation fails, redirect to the settings page and send the errors
if ($validator->fails())
{
    return Redirect::route('settings')->withErrors($validator)->withInput();
}

Это прекрасно работает, однако после этой базовой проверки я хотел бы проверить, предоставил ли пользователь правильный пароль. Для этого я делаю следующее с базовой библиотекой аутентификации Laravel:

// Find the user and validate their password
$user = Auth::user();

if (!Auth::validate(array('username' => $user->username, 'password' => Input::get('password'))))
{
    die("failed to authenticate");
}

Вместо того, чтобы обрабатывать логику, чтобы сообщить пользователю, что их пароль неверен сам, я бы скорее добавил ошибку формы к входу password, чтобы он отображался точно так же, как регулярная проверка формы. Что-то вроде этого:

if (!Auth::validate(array('username' => $user->username, 'password' => Input::get('password'))))
{
    $validator->addError('password', 'That password is incorrect.');
    return Redirect::route('settings')->withErrors($validator)->withInput();
}

Таким образом, неправильная ошибка пароля будет отображаться рядом с моим паролем и будет выглядеть как правильная проверка формы.

Как я могу это сделать?

Ответ 1

См. ответ Даррена Крейга.

Один из способов его реализации.

// inside if(Auth::validate)
if(User::where('email', $email)->first())
{
    $validator->getMessageBag()->add('password', 'Password wrong');
}
else
{
    $validator->getMessageBag()->add('email', 'Email not found');
}

Ответ 2

Есть одна проблема с принятым ответом (и, по моему мнению, валидатор Laravel вообще) - сам процесс проверки и проверка состояния проверки объединяются в один метод.

Если вы слепо выведете все сообщения проверки из сумки, это не имеет большого значения. Но если у вас есть дополнительная логика, которая обнаруживает, что валидатор потерпел неудачу или нет, и выполняет дополнительные действия (например, подавая международные текстовые сообщения для текущих подтвержденных полей формы), тогда у вас есть проблема.

Демонстрация:

    // let create an empty validator, assuming that we have no any errors yet
    $v = Validator::make([], []);

    // add an error
    $v->errors()->add('some_field', 'some_translated_error_key');
    $fails = $v->fails(); // false!!! why???
    $failedMessages = $v->failed(); // 0 failed messages!!! why???

Кроме того,

    $v->getMessageBag()->add('some_field', 'some_translated_error_key');

дает те же результаты. Зачем? Потому что если вы посмотрите код Laravel Validator, вы найдете следующее:

public function fails()
{
    return ! $this->passes();
}

public function passes()
{
    $this->messages = new MessageBag;

Как вы можете видеть, метод fails() по существу очищает сумку, теряющую все прикрепленные вами сообщения, и, таким образом, делает валидатор предполагаемым, что ошибок нет.

Невозможно добавить ошибки к существующему валидатору и сделать его неудачным. Вы можете создать новый валидатор с настраиваемыми ошибками, например:

    $v = Validator::make(['some_field' => null],
            ['some_field' => 'Required:some_translated_error_key']);
    $fails = $v->fails(); // true
    $failedMessages = $v->failed(); // has error for `required` rule

Если вам не нравится идея злоупотребления правилом проверки required для пользовательских добавленных ошибок, вы всегда можете расширить Laravel Validator с помощью настраиваемых правил. Я добавил общее правило failkey и сделал его обязательным следующим образом:

    // in custom Validator constructor: our enforced failure validator
    array_push($this->implicitRules, "Failkey");

    ...


/**
 * Allows to fail every passed field with custom key left as a message
 * which should later be picked up by controller
 * and resolved with correct message namespaces in validate or failValidation methods
 *
 * @param $attribute
 * @param $value
 * @param $parameters
 *
 * @return bool
 */
public function validateFailkey($attribute, $value, $parameters)
{
    return false; // always fails
}

protected function replaceFailkey($message, $attribute, $rule, $parameters)
{
    $errMsgKey = $parameters[0];

    // $parameters[0] is the message key of the failure
    if(array_key_exists($errMsgKey, $this->customMessages)){
        $msg = $this->customMessages[$parameters[0]];
    }       
    // fallback to default, if exists
    elseif(array_key_exists($errMsgKey, $this->fallbackMessages)){
        return $this->fallbackMessages[$parameters[0]];
    }
    else {
        $msg = $this->translator->trans("validation.{$errMsgKey}");
    }

    // do the replacement again, if possible
    $msg = str_replace(':attribute', "`" . $this->getAttribute($attribute) 
            . "`", $msg);

    return $msg;
}

И я могу использовать его следующим образом:

    $v = Validator::make(['some_field' => null],
            ['some_field' => 'failkey:some_translated_error_key']);
    $fails = $v->fails(); // true
    $failedMessages = $v->failed(); // has error for `Failkey` rule

Конечно, это все еще хакерский способ обойти эту проблему.

В идеале я бы пересмотрел валидатор, чтобы четко отделить его фазу проверки от обнаружения состояния (отдельные методы для validate() и passes() или лучше isValid()), а также добавить методы удобства для ручного сбоя определенного поля с определенным правилом. Хотя это также можно считать взломанным, но у нас нет другого выбора, если мы хотим использовать валидатор Laravel не только с собственными правилами валидации Laravel, но и с нашими правилами пользовательской бизнес-логики.

Ответ 3

Кроме того, было бы полезно добавить следующую функцию Redirect::back():

$validator->getMessageBag()->add('password', 'Password wrong');    
return Redirect::back()->withErrors($validator)->withInput();

Согласно

Альфа

(http://heera.it/laravel-manually-invalidate-validation#.VVt7Wfl_NBc)

Ответ 4

Альтернативный синтаксис:

$validator->errors()
          ->add('photos', 'At least one photo is required for a new listing.');

Ответ 5

Я понимаю, почему вы этого хотите, однако с практической точки зрения с плохой точки зрения можно вернуть сообщение, которое указывает, является ли имя пользователя и/или пароль неправильным. Это позволит хакеру понять, правильно ли они получили имя пользователя или пароль.

Лучше вернуть общее сообщение, например "Ваши учетные данные неверны", которые вам все равно не будут отображаться рядом с вашими полями.

Ответ 6

Я решил аналогичную проблему с проверкой и пользовательской проверкой. В моем случае мне нужно проверить, что загруженный файл с формой является допустимым изображением, а также сообщениями, поэтому мне нужно запустить проверку валидации для файла и проверки валидации для данных post. У меня возникла проблема, когда я попытался вернуть обратно свои пользовательские данные проверки, только присутствовали ошибки проверки Laravel. По сообщению @JustAMartin, я был закодирован как решение, которое показывает все ошибки.

    //Creem una instància del validador. Açò ens permet manipular-lo
    $validator = Validator::make($request->all(), [
        'nomCompanyia' => 'required',
        'urlCompanyia' => 'url'
    ]);

    $imageError = false;
    $imgOriginal = null;
    $imgMitjana = null;
    $imgXicoteta = null;
    $fallaValidacio = !$validator->passes(); //-> Retorna true si cap error, false en cas contrari.

    if($request->hasFile('logoCompanyia') && !$fallaValidacio)
    {
        $imatge = $request->file('logoCompanyia');

        if($imatge->isValid() && $this->verificaExtensionsImatges($imatge->getClientOriginalExtension(), $imatge->guessExtension()))
        {
            $sPath = $imatge->store('images/companyies/', 'public');
            $fullPathOriginal = public_path() . "/storage/" . $sPath;
            $fpInfo = pathinfo($fullPathOriginal);
            $imgOriginal = sprintf("%s.%s", $fpInfo['filename'], $fpInfo['extension']);

            //Crear les miniatures
            $mitjana = Image::make($fullPathOriginal)->widen(300, function ($constraint) {
                $constraint->upsize();
            });

            $imgMitjana = sprintf("%s_300.%s", $fpInfo['filename'], $fpInfo['extension']);
            $mitjana->save($fpInfo['dirname'] . '/' . $imgMitjana);

            $xicoteta = Image::make($fullPathOriginal)->widen(100, function ($constraint) {
                $constraint->upsize();
            });

            $imgXicoteta = sprintf("%s_100.%s", $fpInfo['filename'], $fpInfo['extension']);
            $xicoteta->save($fpInfo['dirname'] . '/' . $imgXicoteta);
        }
        else
        {
            $imageError = true;
            $validator->getMessageBag()->add('logoCompanyia', "Sembla que el fitxer d'imatge no és vàlid o està corrupte. Només s'accepten els formats: .jpg, .jpeg, .png, .gif");
        }
    }
    else
    {
        $imageError = true;
        $validator->getMessageBag()->add('logoCompanyia', "Sembla que el fitxer d'imatge no és vàlid o ha sigut rebutjat per el servidor si és massa gran.");
    }

    if($fallaValidacio || $imageError)
    {
        $data['mode'] = "nou";
        $data['urlFormulari'] = "administracio/companyies/afegir";
        $data['nomCompanyia'] = $request->nomCompanyia;
        $data['idCompanyia'] = 0;
        $data['urlCompanyia'] = $request->urlCompanyia;
        $data['logoCompanyia'] = $request->logoCompanyia;
        $data['errors'] = (object) $validator->errors();

        return view($this->formulariTemplate, $data);
    }

    $companyia = new Companyies();
    $companyia->nom = $request->nomCompanyia;
    $companyia->url = $request->urlCompanyia;
    $companyia->logo_original = $imgOriginal;
    $companyia->logo_300 = $imgMitjana;
    $companyia->logo_100 = $imgXicoteta;

    $companyia->save();

Как вы можете видеть, я делаю только один вызов метода $validator- > pass(), и я сохраняю результат в переменной. Когда я вызываю этот метод, все тесты Laravel собираются. Если они переданы или нет, результат сохраняется в переменной, поэтому вы можете позже проверить свою переменную. Это позволяет сделать тесты в файле, чтобы окончательно определить, все ли данные в порядке или нет.

Если есть ошибки, я перенаправляю обратно с помощью помощника view(), добавляя все данные: ввод и ошибки. Если ошибок нет, нормальное поведение метода продолжается.

Ответ 7

Если вы используете ajax-вызовы, не забудьте создать ValidationException.

if ($subscribed) {
    $validator->errors()->add('email', __('Your email is already subscribed.'));
    throw new ValidationException($validator);
}

Ответ 8

 $validator -> errors() -> add('attribute', 'value');
 return redirect($request -> url())
                    -> withErrors($validator)
                    -> withInput();

В "значение" можно передать что угодно.

Ответ 9

Пользователь Matt K сказал в комментарии, что laravel с тех пор внедрил хуки проверки, которые делают именно то, что мы хотим:

$validator = Validator::make(...);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add('field', 'Something is wrong with this field!');
    }
});

if ($validator->fails()) {
    // this actually runs! even if the original validator succeeded!
}