I have a Symfony form that has fields that should let users write emails but not URLs. I am currently using this regex:
return new Regex(
[
'pattern' => '((http|https|ftp|ftps)://)?([a-zA-Z0-9\-]*\.) [a-zA-Z0-9]{2,4}(/[a-zA-Z0-9=.?&-]*)?',
'match' => false,
'message' => $this->translator->trans('form.field.urlNotAllowed', ['%label%' => $label])
]
);
This regex matches all URLs, but also match emails for validation. What I want to do is excluding emails from validation and match only URLs.
My code:
/**
* @param RegistrationFormField $field
* @param string $key
* @param array $validationAttributes
* @return Regex
*/
public function getUrlNotAllowedConstraint($field, $key, &$validationAttributes)
{
$event = $field->getRegistrationForm()->getEvent();
$label = /** @Ignore */
$this->translator->trans($field->getLabel(), [], 'custom') ?: $this->getDefaultLabelName($event, $key);
$validationAttributes['data-validation'][] = 'url_not_allowed';
return new Regex(
[
'pattern' => '((http|https|ftp|ftps)://)?([a-zA-Z0-9\-]*\.) [a-zA-Z0-9]{2,4}(/[a-zA-Z0-9=.?&-]*)?',
'match' => false,
'message' => $this->translator->trans('form.field.urlNotAllowed', ['%label%' => $label])
]
);
}
Any help?
I have created the CustomSequentically Constraint:
namespace App\Form\Validator;
use Symfony\Component\Validator\Constraints\Composite as
ConstraintsComposite;
/**
* Use this constraint to sequentially validate nested
constraints.
* Validation for the nested constraints collection will stop at
first violation.
*
* @Annotation
* @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
*
*/
class CustomSequentially extends ConstraintsComposite
{
public $constraints = [];
public function getDefaultOption()
{
return 'constraints';
}
public function getRequiredOptions()
{
return ['constraints'];
}
protected function getCompositeOption()
{
return 'constraints';
}
public function getTargets()
{
return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
}
}
And the CustomSequentiallyValidator Class:
namespace App\Form\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class CustomSequentiallyValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof CustomSequentially) {
throw new UnexpectedTypeException($constraint, CustomSequentially::class);
}
$context = $this->context;
$validator = $context->getValidator()->inContext($context);
$originalCount = $validator->getViolations()->count();
foreach ($constraint->constraints as $c) {
if ($originalCount !== $validator->validate($value, $c)->getViolations()->count()) {
break;
}
}
}
}
And used the constraint like this:
$constraints = new CustomSequentially([
'constraints' => [
new Regex([
'pattern' => '/((http|https|ftp|ftps):\/\/)?((?!@)[a-zA-Z0-9\-]*\.) [a-zA-Z0-9]{2,4}(\/[a-zA-Z0-9=.?&-]*)?/',
'match' => false,
'message' => $this->translator->trans('form.field.urlNotAllowed', ['%label%' => $label])
]),
new Regex([
'pattern' => '/[@]/',
'match' => true,
'message' => 'It is an email'
]),
],
]);
return $constraints;
Now, if I write a URL or an email both are not passing the validation(a simple text is passing).
CodePudding user response:
This will allow any string but if it is a URL it will check with regex. If it detects a URL it will then see if it is an email address by checking if the URL contains the @
symbol and allow it.
Improve or iterate on for your needs as you see fit.
The Constraint
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
class NoUrl extends Constraint
{
public $urlMessage = 'This looks like a URL, this is not valid.';
}
The Validator
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class NoUrlValidator extends ConstraintValidator
{
public function validate($url, Constraint $constraint)
{
if (!$constraint instanceof NoUrl) {
throw new UnexpectedTypeException($constraint, NoUrl::class);
}
if (null === $url || '' === $url) {
return;
}
if (!is_string($url)) {
throw new UnexpectedValueException($url, 'string');
}
if (preg_match('/((http|https|ftp|ftps):\/\/)?([a-zA-Z0-9\-]*\.) [a-zA-Z0-9]{2,4}(\/[a-zA-Z0-9=.?&-]*)?/', trim($url))) {
// str_contains() is PHP 8 , use strpos() for PHP <8
// String contains an @ symbol so just return as it must be an email address. You can add more checks if needed yourself.
if (str_contains($url, '@')) {
return;
}
$this->context->buildViolation($constraint->urlMessage)->addViolation();
}
}
}
In Your Form
...
use App\Validator\NoUrl;
class YourFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('yourfield', TextType::class, array(
'constraints' => array(
new NoUrl()
)
))
...