Validation constraint for Twig Template syntax

Symfony

Creating custom validation constraint could be tricky for new Symfony2 developers. Based on the idea of validating Twig syntax I’ll show you how to create custom validator with DI dependencies.

Imagine that you want to allow to edit templates through your administration panel – it’s common task in CMS, E-shop engines. To avoid errors while compiling templates (source syntax errors) you have to validate posted template source.

To accomplish that we’ll create our own configurable Validation Constraint.

Creating Constraint class

The first step is to create a Constraint class. It’s very simple class, but crucial for validation process.

It contains 2 public properties:

  • message – message to show if validation fails
  • useTwigMessage – our custom parameter which will indicate to use validation message from the message property or Twig error message from parsing process

The @Annotation annotation is necessary to use that constraint in classes via annotations.

Due to special annotation and defined public properties we’ll be able to define constraint via class annotations like:

/**
 * @Octivi\PlaygroundBundle\Validator\Constraints\Twig(message="My message", useTwigMessage=true)
 */

There is also validatedBy() method which tells Symfony2 Validator Component which class it should use to make validation. In this implementation we are using “twig” value because it’ll be later our alias for validator service in Dependency Injection.

<?php
// in src/Octivi/PlaygroundBundle/Validator/Constraints/Twig.php

namespace Octivi\PlaygroundBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class Twig extends Constraint
{

    public $message = 'Value is not a valid Twig template.';
    public $useTwigMessage = false;

    public function validatedBy()
    {
        return 'twig';
    }
}

The TwigValidator service

Now we will create the “real” class which implements validation logic.

It has the Twig_Environment instance injected in the constructor which later lets us to parse given source code and check for thrown exceptions – if Twig_Error_Syntax exception will be thrown it means that given source code is invalid – the syntax is not correct.

$value is our value to check validation method against. Important thing is to check if the value is null – if so the flow of method should stop (return null) to avoid later errors. To check if the value is actually empty you will likely use the additional NotBlank constraint.

The $constraint object passed as the 2nd argument in validate method is actually our Twig constraint object. Thus we’re able to choose between using “our” validation message or original message from the Twig exception.

 <?php
 // in src/Octivi/PlaygroundBundle/Validator/Constraints/TwigValidator.php

 namespace Octivi\PlaygroundBundle\Validator\Constraints;

 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;

 class TwigValidator extends ConstraintValidator
 {

     /**
      * @var \Twig_Environment
      */
     protected $twig;

     public function __construct(\Twig_Environment $twig)
     {
         $this->twig = $twig;
     }

     public function validate($value, Constraint $constraint)
     {
         if (!$value) {
             return;
         }

         $twig = $this->twig;
         $message = $constraint->message;

         try {
             $twig->parse($twig->tokenize($value));
         } catch (\Twig_Error_Syntax $e) {
             if ($constraint->useTwigMessage) {
                 $message = $e->getMessage();
             }

             $this->context->addViolation($message);
         }
     }

 }

Next, we have to define our Validator class as a service in DI (because of the Twig_Environment dependency).

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="octivi_playground.validator.twig" class="Octivi\PlaygroundBundle\Validator\Constraints\TwigValidator">
            <argument type="service" id="twig" />
            <tag name="validator.constraint_validator" alias="twig" />
        </service>
    </services>
</container>

The important line is

<tag name="validator.constraint_validator" alias="twig" />

Because of the “twig” alias we were able to use the service by Constraint via validatedBy method.

Usage

Ok, now we have got everything set up and ready to use.

I’ve defined the Template model which has “Twig” validation rule on the “source” property.

<?php
// in src/Octivi/PlaygroundBundle/Model/Template.php

namespace Octivi\PlaygroundBundle\Model;

use Octivi\PlaygroundBundle\Validator\Constraints\Twig;

class Template
{
    /**
     * @Twig
     */
    protected $source;

    public function getSource()
    {
        return $this->source;
    }

    public function setSource($source)
    {
        $this->source = $source;
    }
}

Validating in controller is as simple as:

// in some controller action

$template = new Template;
$template->setSource('{{ test }');

$validator = $this->get('validator');

$errors = $validator->validate($template);

Looking to scale-out your
web application?

Hire Octivi!

Antoni is a Software Architect and Scrum Master at Octivi. He is responsible for software architecture of our key projects, he also holds Professional Scrum Master certificate.