0

I've inherited an app and I'm not (yet) a symfony expert. The app has a simple user entity (among other things, of course) and it has some unique constraints:

/**
 * User
 *
 * @ORM\Table(name="users")
 * @ORM\Entity
 * @UniqueEntity(
 *     fields={"username", "school"},
 *     message="There's already a registered user with that login in this school."
 * )
 * @UniqueEntity(fields={"email"},
 *      message="That email is already registered")
 * )
 */

When creating a form, and when violating such constraints, the messages are displayed on the form itself and the flow isn't back to the controller (which is perfectly OK).

I have a non-ORM property, with $plainPassword, which holds the entered text in the form, together with the usual $password keeping the crypted password. This is the form part for this field:

    $builder->add(
        'plainPassword',
        TextType::class,
        [
            'required' => false,
            'attr' =>
            [
                'autocomplete' => 'off',
            ],
            'error_bubbling' => true,
        ]
    )
    ;

Now, for the password, I have a custom validator, that can be seen in the next function:

public function isPasswordValid(string $p = null)
{
    if (null==$p) {
        $p = $this->getPlainPassword();
    }

    // If I'm not changing the password, there must be one previously
    $success = strlen($this->getPassword())>0;
    if (strlen($p)) {
        $success = (strlen($p)>7 && preg_match('/[A-Za-z]/', $p) && preg_match('/[0-9]/', $p));
    }
    return $success;
}

This functions works nice by itself, but I want to attach it to the form so when the plainPassword field has an "invalid" password (i.e., the function returns false). So I've tried by using an annotation block right before the function:

/**
 * @Assert\IsTrue(message = "The password is not a valid password")
 */
public function isPasswordValid(string $p = null)
{
    if (null==$p) {
        $p = $this->getPlainPassword();
    }

    // If I'm not changing the password, there must be one previously
    $success = strlen($this->getPassword())>0;
    if (strlen($p)) {
        $success = (strlen($p)>7 && preg_match('/[A-Za-z]/', $p) && preg_match('/[0-9]/', $p));
    }
    return $success;
}

The message goes to form.errors, and I could show it when the form is called in non-modal mode in a very specific (but different) way, but the default form behaviour via ajax showing as pop-up even skips this behaviour, so I don't get this message. Then, I've tried with error_mapping in the field definition in the form to link the property and the function, but the result is the same (i.e, it's not shown).

I've even tried to build a custom validator. In all these cases, the behaviour when validating the password is OK, but when the password is an invalid one, the form is not submitted, but no error message at all (unlike the other fields defined in the @UniqueConstraint). So, I either Cancel or just enter a valid password and submit the form, but a user doesn't have that reference about the error (and the profiler or the dev logs don't show anything about this, so I have to figure out by changing the password).

So, there must be something I'm missing or it's simply that by the means I've tested it's not possible. I'm diving now with the expression language for a expression assert, but I'm running out of ideas and it's taking me a couple of days just for this, so I ask for your help at this point to find out what I did wrong, overlooked or didn't do yet.

Thanks in advance.

UPDATE: Added a custom validator, where I'm not sure if I manage correctly empty data or anything related.

// ValidPassword.php
namespace XXX\UsersBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class ValidPassword extends Constraint
{
    public $message = 'InvalidPasswordMessage';
}

(I want the message to be handled by the translations file.)

// ValidPasswordValidator.php
namespace XXX\UsersBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use XXX\UsersBundle\Entity\User;

class ValidPasswordValidator extends ConstraintValidator
{
    public function validate($p, Constraint $constraint)
    {

        $success = true;
        if (strlen($p)) {
            $success = (strlen($p)>7 && preg_match('/[A-Za-z]/', $p) && preg_match('/[0-9]/', $p));
        }

        if (!$success) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();

        }
    }

And in the entity:

use XXX\UsersBundle\Validator\Constraints as UserAssert;
...

    /**
     * @var string
     * @UserAssert\ValidPassword
     */
    private $plainPassword;

This is the part of the twig template to show the involved fields:

...

<div class="row">
    <div class="col-md-4">
    {{ form_row(form.name) }}
    </div>
    <div class="col-md-4">
    {{ form_row(form.surname) }}
    </div>
</div>

<div class="row">
    <div class="col-md-4">
{{ form_row(form.email) }}
    </div>
</div>

<div class="row">
    <div class="col-md-4">
{{ form_row(form.plainPassword) }}
    </div>
</div>
10
  • I suspect that there's a lot of troubles in your code. First, the part // If I'm not changing the password, there must be one previously seems erroneous. The plainPassword will be empty in your Entity almost all the time, cause this attribute is used to be validate and used only in a few situations. Take a look to your Entity attribute and check first. Second, Where do you put isPasswordValid? I hope that not in your Entity cause Entities are simple bags for data (usually come from DB). Do not add validation to Entities. Use FormTypes and Validators on your Controller layers. Commented Aug 14, 2018 at 1:32
  • The plainPassword property is mapped to the form field, so whatever is entered is to be validated. The $p=null is just to prevent that no password is entered and treat accordingly, but that's not the problem I'm facing. You're right that the function is in the entity, but having already a custom validator, what should I do in the form/controller to feed the error to the form in the same way that UniqueConstrainsts do in the entity? I think that if it was a simple assert, setting it in the property should work, but the assert is somehow complicated. Commented Aug 14, 2018 at 7:02
  • I've just updated the post with the custom validator code. Hope it helps. Commented Aug 14, 2018 at 7:18
  • wich Symfony version? Commented Aug 14, 2018 at 13:59
  • The version is 3.1, but I have the plan to upgrade as soon as possible, when all the "missing" features required by the customer are working. I don't think there's a major change or dependency so you have a suggestion where version 3.4 or even 4 is mandatory. But anyway, thanks for your help. Commented Aug 14, 2018 at 15:42

1 Answer 1

2

ANSWERING

Your code it almost done. Your trouble is that you are using

'error_bubbling' => true

This config changes the object for error to be appended to the parent field target or the form. So, in your case, errors in plainPasswordField will be added to the form instead to the field.

MORE INFORMATION

After made some tests I guess that there's a little bit confusing about Form Errors and Form Field Errors.

If your view code does not have form_errors(form) or form(form), all form.vars.errors won't be shown.

Child form fields with error_bubbling=true or codes like:

// Generic Controller
$form->addError(new FormError('My Generic Form Error not associate with any field!'));
return $this->render('my_template.html.twig', [
        'form' => $form->createView(),
    ]);

olny can be displayed in twig template with

form_errors(form)

Useful Tips:

  • When creating form templates debug your form view with form(form) (must be the first call on template) to show everything in the form object, like fields (with html form input|select, field label, field errors, field helpers) and errors in the form itself The doc say about form_rest() but it not show form.vars.errors

  • Use form.vars.errors|length to check for errors on form view variable in twig

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, Marcos. Sorry for the delay, but removing error_bubbling and thanks to your explanations, it worked. I think that was the initial state and added it to test, but surely I missed something. I'd mark this as solved, but I'm afraid my reputation doesn't allow me to do it, so please any admin who can do it, mark it so it can help anybody in the same situation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.