Home > database >  Symfony 4 Regex, find a pattern that excludes email check from url check
Symfony 4 Regex, find a pattern that excludes email check from url check

Time:07-29

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()
                )
            ))
...
  • Related