How to make text field compulsory only when the checkbox is checked with symfony annotation validations in model classes - validation

It is probably a very simple question but I've run out of juice here. Vat field is compulsory only when isVatable checkbox is check by user otherwise it can be ignored. How do I achieve this with group validation (annotations) in model class, not entity?
I checked Validation Groups and Group Sequence but to be honest didn't get my head around.
FormType
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add('vat', 'text')
->add('isVatable', 'checkbox')
;
}
public function getName()
{
return 'user';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'My\FrontendBundle\Model\UserModel']
);
}
}
ModelClass
class UserModel
{
/**
* #Assert\NotBlank(message="Vat is required only when checkbox is checked.")
*/
protected $vat;
/**
* #var bool
*/
protected $isVatable = false;
}

I find that the #Assert\True() constraint on a method usually works well for me for these sorts of validation scenario. You can add some validation constraints to methods as well as properties, which is pretty powerful.
The basic idea is that you can create a method, give it this annotation - if the method returns true the validation passes; if it returns false it fails.
class UserModel
{
/**
* #var string
*/
protected $vat;
/**
* #var bool
*/
protected $isVatable = false;
/**
* #Assert\True(message="Please enter a VAT number")
*/
public function isVatSetWhenIsVatableChecked()
{
// if this property is unchecked we don't
// want to do any validation so return true
if (!$this->isVatable) {
return true;
}
// return true if $this->vat is not null
// you might want to add some additional
// validation here to make sure the
return !is_null($this->vat);
}
}
Additionally, you can map the error message to a specific form field with the error_mapping option in your FormType object, as documented here:
http://symfony.com/blog/form-goodness-in-symfony-2-1#error-mapping-fu
Hope this helps :)

Perhaps something like this in ...\Validator\Constraints:
VAT.php
use Symfony\Component\Validator\Constraint;
class VAT extends Constraint
{
public $message = 'VAT is compulsory for applicable items';
public $vat;
public $isVatable;
}
VATConstraint.php
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\PropertyAccess\PropertyAccess;
class VATValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$accessor = PropertyAccess::createPropertyAccessor();
$data = $accessor->getValue($this->context->getRoot(), 'data');
$vat = $data['vat'];
$isVatable = $data['isVatable'];
if ($isVatable && empty($vat)) {
$this->context->addViolation($constraint->message, array('%string%' => $value));
return false;
}
return true;
}
}
User model Entity:
use YourBundle\Validator\Constraints as MyAssert;
class UserModel
{
/**
* #MyAssert\NotBlank(message="Vat is required only when checkbox is checked.")
*/
protected $vat;
...
}

You should use a Class Constraint Validator
First make your Constraint class and your validatorClass:
<?php
// AppBundle/Validator/Constraints/NotBlankIfTaxEnabled.php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class NotBlankIfTaxEnabled extends Constraint
{
public $message = 'If isVat is enabled you have to enter a value in the Vat field.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
and
<?php
// AppBundle/Validator/Constraints/NotBlankIfTaxEnabledValidator.php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class NotBlankIfTaxEnabledValidator extends ConstraintValidator
{
public function validate($customer, Constraint $constraint)
{
if($customer->getIsVatable() && strlen($customer->getVat()) == 0)
{
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Then add de classcontraint to your Entity/Model
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Validator\Constraints as AppAssert;
/**
* Customer
*
* #ORM\Table()
* #ORM\Entity
* #AppAssert\NotBlankIfTaxEnabled
*/
class Customer
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=64)
*
*/
private $vat;
/**
* #var boolean
*
* #ORM\Column(name="taxEnabled", type="boolean")
*/
private $isVatable;
and do not forget to switch off the required attribute for both fields in your formType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CustomerType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('vat', NULL, array('required' => FALSE))
->add('isVatable', NULL, array('required' => FALSE))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Customer'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_customer';
}
}

Related

Laravel: Stop using UUID as foreign key

Laravel is trying to use uuid field as foreign key. And I want to use foreign key with the field id. Is there any option there?
Using this trait on Model. And then it is trying to use the uuid as foreign key. But still I want to use id as foreign key.
<?php
namespace App\Library;
use Ramsey\Uuid\Uuid;
trait UsesUuid
{
/**
* #return string
*/
public function getKeyName()
{
return 'uuid';
}
/**
* #return string
*/
public function getKeyType()
{
return 'string';
}
/**
* #return false
*/
public function getIncrementing()
{
return false;
}
/**
* #param $query
* #param $uuid
* #return mixed
*/
public function scopeUuid($query, $uuid)
{
return $query->where($this->getUuidName(), $uuid);
}
/**
* #return string
*/
public function getUuidName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
* #return string
*/
public function getRouteKeyName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
*
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getUuidName()} = Uuid::uuid4()->toString();
});
}
}
There is nothing special about this trait. You can make your own trait with id instead of uuid and everything will work fine.
The issue came from methods getIncrementing() and getKeyName(). Laravel calls getKeyName() in amount of built-in functions to interacts with relationships, also other actions like delete(), route bindings, etc.
You should allow any models which uses this trait to custom the Primary key (PK), so that uuid is only common column at all.
Your trait definition is force PK column as uuid.
Below is my recommended for the trait
<?php
namespace App\Library;
use Ramsey\Uuid\Uuid;
trait UsesUuid
{
/* Override this method to set `uuid` as PK */
public function isUuidAsPrimaryKey()
{
return false;
}
/**
* #return string
*/
public function getKeyName()
{
return $this->isUuidAsPrimaryKey() ? $this->getUuidName() : $this->primaryKey;
}
/**
* #return string
*/
public function getKeyType()
{
return $this->isUuidAsPrimaryKey() ? 'string' : $this->keyType;
}
/**
* #return false
*/
public function getIncrementing()
{
return !$this->isUuidAsPrimaryKey();
}
/**
* #param $query
* #param $uuid
* #return mixed
*/
public function scopeUuid($query, $uuid)
{
return $query->where($this->getUuidName(), $uuid);
}
/**
* #return string
*/
public function getUuidName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
* #return string
*/
public function getRouteKeyName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
*
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getUuidName()} = Uuid::uuid4()->toString();
});
}
}
If a model need to have uuid as PK, for example Book model
class Book extends Model
{
use UsesUuid;
public function isUuidAsPrimaryKey()
{
return true;
}
}
Please recheck the method isUuidAsPrimaryKey. If it may not be overridden (due to conflict), then use a property instead.

How to get all property for a custom entity in FOSUserBundle?

I am using FOSUserBundle
I created a User entity that extents baseuser, and I added a protected property I called $apiKey. The registration form works fine.
then, I created a userController that extends controller, and in a methoid to edit my user I have:
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('AppBundle:User')->find($id);
my $user has the apiKey property but this is empty (of course the field in the DB is not).
any idea?
thanks
UPDATE: user entity
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Group")
* #ORM\JoinTable(name="fos_user_user_group",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* #ORM\Column(type="string", unique=true, nullable=true)
*/
protected $apiKey;
/* public function __construct()
{
parent::__construct();
// your own logic
//$this->roles = array('ROLE_USER'); //default role for new users
}*/
public function __construct()
{
parent::__construct();
// your own logic
$this->groups = new ArrayCollection();
}
/**
* #return mixed
*/
public function getApiKey()
{
return $this->apiKey;
}
/**
* #param mixed $apiKey
*/
public function setApiKey($apiKey)
{
$this->apiKey = $apiKey;
}
/**
* #return mixed
*/
public function getGroups()
{
return $this->groups;
}
/**
* #param mixed $groups
*/
public function setGroups($groups)
{
$this->groups = $groups;
}
}

Symfony FOSuser translation new form

I have problem with translation of new inputs in the registration form in FOSUSER. Basicly I Extended the FOS user Entity:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use FOS\UserBundle\Model\User as BaseUser;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Fill it", groups={"Registration", "Profile"})
* #Assert\Length(
* min=3,
* max=255,
* minMessage="To short.",
* maxMessage="To long.",
* groups={"Registration", "Profile"}
* )
*/
protected $nameU;
public function getNameU() {
return $this->nameU;
}
public function setNameU($nameU) {
$this->nameU= $nameU;
}
Ovveride the registration FORM
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Gregwar\CaptchaBundle\Type\CaptchaType;
use FOS\UserBundle\Util\LegacyFormHelper;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nameU')
}
public function getParent()
{
return 'FOS\UserBundle\Form\Type\RegistrationFormType';
// Or for Symfony < 2.8
// return 'fos_user_registration';
}
public function getBlockPrefix()
{
return 'app_user_registration';
}
// For Symfony 2.x
public function getName()
{
return $this->getBlockPrefix();
}
}
In my registration view I have:
{{ form_label(form.nameU, null,{}) }}
Add to translation specific declaration for a locale of FOS USER BUNDLE in app/Resource/FosUser../translation/.....
form:
group_name: Gruppenname
username: Benutzername TEST <- this change OK
nameU: TESTS <- this not
The translation is not working and it's look like variable name nameU :(
Ok. I have found it. I forget to send it in the registration type:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->
->add('numeU',null, array('label' => 'form.nameU', 'translation_domain' => 'FOSUserBundle'))

Constraint validator namespace loading from wrong namespace

I am trying to make custom validator in symfony 2, but it loads from wrong namespace. Error says:
Attempted to load class "DateChecker" from namespace "Symfony\Component\Validator\Constraints\Errand\MainBundle\Validator\Constraints.
Here is my code:
MainBundle/Validator/Constraints/DateCheker.php:
<?php
namespace Errand\MainBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class DateChecker extends Constraint
{
/**
*
* #var string
*/
public $message = 'some message';
/**
*
* #return string
*/
public function validatedBy()
{
return get_class($this).'Validator';
}
/**
* Get class constraints and properties
*
* #return array
*/
public function getTargets()
{
return array(self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT);
}
}
MainBundle/Validator/Constraints/DateCheckerValidator.php:
namespace Errand\MainBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class DateCheckerValidator extends ConstraintValidator
{
/**
* Method to validate
*
* #param string $value Property value
*
*
* #return boolean
*/
public function validate($value, Constraint $constraint)
{
//validation
}
}
MainBundle/Resources/config/validation.yml:
Errand\MainBundle\Entity\Task:
constraints:
- Errand\MainBundle\Validator\Constraints\DateChecker: ~

symfony annotations validation override entities/models

I'm trying to override the entities validatation of a forum bundle.
I do it like this:
Category entity:
//src/MSD/ForoBundle/Entity/Category.php
namespace MSD\ForoBundle\Entity;
use Herzult\Bundle\ForumBundle\Entity\Category as BaseCategory;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="Herzult\Bundle\ForumBundle\Entity\CategoryRepository")
*/
class Category extends BaseCategory
{
}
Topic entity:
//src/MSD/ForoBundle/Entity/Topic.php
namespace MSD\ForoBundle\Entity;
use Herzult\Bundle\ForumBundle\Entity\Topic as BaseTopic;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Topic
*
* #ORM\Entity(repositoryClass="Herzult\Bundle\ForumBundle\Entity\TopicRepository")
*
*/
class Topic extends BaseTopic
{
/**
* #ORM\ManyToOne(targetEntity="Category")
*/
protected $category;
/**
* #Assert\NotBlank()
* #Assert\MinLength(limit=4, message="Just a little too short| ")
* #Assert\Regex(
* pattern="/^[a-zA-Z0-9\-_¿?!¡ ]{4,50}$/",
* message="El tema puede contener letras, números, guiones y espacios, interrogantes y exclamaciones. Entre 4 y 30 caracteres"
* )
*/
protected $subject;
/**
* {#inheritDoc}
*/
public function getAuthorName()
{
return $this->author;
}
/**
* #ORM\ManyToOne(targetEntity="User")
*/
private $author;
public function setAuthor(User $user)
{
$this->author = $user;
}
public function getAuthor()
{
return $this->author;
}
}
Post Entity:
//src/MSD/ForoBundle/Entity/Post.php
namespace MSD\ForoBundle\Entity;
use Herzult\Bundle\ForumBundle\Entity\Post as BasePost;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="Herzult\Bundle\ForumBundle\Entity\PostRepository")
*/
class Post extends BasePost
{
/**
* #ORM\ManyToOne(targetEntity="Topic")
*/
protected $topic;
/**
* #Assert\Regex(
* pattern="/^[^<>]{4,1000}$/",
* message="El mensaje no puede contener '<' ni '>'. Entre 4 y 1000 caracteres"
* )
*
*/
public $message;
public function getAuthorName()
{
return $this->author;
}
/**
* #ORM\ManyToOne(targetEntity="User")
*/
private $author;
public function setAuthor(User $user)
{
$this->author = $user;
}
public function getAuthor()
{
return $this->author;
}
}
And the validation works... except the message of the firt post!! that is created when a new topic is created.
I've tried many changes, but without success.
Any idea of why does it happend?
Thank you
Yeah! I got it. The solution was to add this in the Topic entity:
/**
* #Assert\NotBlank
* #Assert\Valid
*/
protected $firstPost;
Then, the message of the first post is validated.

Resources