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>
// If I'm not changing the password, there must be one previouslyseems 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 putisPasswordValid? 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.