Symfony validation: First property constraint then class constraint - validation

I'm using Symfony 2.5 and my Model class is the following:
/**
* #UserAssert\UserPasswordReset
*/
class ResetPassword {
/**
* #var string
* #Assert\NotBlank()
*/
public $username;
/**
* #var string
* #Assert\NotBlank()
* #Assert\Date
*/
public $birthday;
/**
* #var string
* #Assert\NotBlank()
*/
public $plainSecurityAnswer;
function __toString()
{
return $this->username . $this->birthday->format('Y-m-d H:i:s') . $this->plainSecurityAnswer;
}
}
This Model is mapped to a ResetFormType.
Now my intention: How can i say / configure, that i first want the property constraints to be passed. And if all property constraints are passed (e.g. no field is blank), i want the #UserAssert\UserPasswordReset to be called.
At the moment, it always validates the property AND the class constraints.
Regards ++

I think you can do it using a GroupSequence Validator like this:
/**
* #UserAssert\UserPasswordReset(groups={"PasswordReset"})
* #Assert\GroupSequence({"Default", "PasswordReset"})
*/
class ResetPassword
{
//----
}
In this mode UserPasswordReset will be validated only after the Defaults Asserts.
In the docs you will find some implementations example to use groups sequences..

Related

What is the best way for reusable values throughout the application in Symfony 3?

I want to have a file or list that I can update easily with values that might change throughout my application.
I don't really want to hard code text values into the templates. I prefer to have all of these values in one place and labelled correctly.
Examples of values that might get updated are:
Page title
Logo text
Brand or company name
I have thought about two options:
Add them to the twig config in config.yml. This is a bit messy and doesn't seem organised if I decide to put a lot of values there.
Make a database table for these and include the entity in each controller where I need to use the values. This might be creating too much work.
Are there any other options or are one of these more suitable?
Thank you.
You need to create a twig function and use it to return the value you want. For example:
namespace AppBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TwigExtension extends \Twig_Extension implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #var ContainerInterface
*/
protected $container;
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('parameter', function($name)
{
try {
return $this->container->getParameter($name);
} catch(\Exception $exception) {
return "";
}
})
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'app.twig.extension';
}
}
This will create a function called parameter and once you call it in twig {{ parameter('my.parameter') }} it will return the parameter. You need to load it as a service, which you can do by adding the following to your services.yml file:
app.twig.extension:
class: AppBundle\Twig\TwigExtension
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: twig.extension }
From personal experience people usually want to be able to change some of the parameters. This is why I usually prefer to create a Setting or Parameter entity which would look something like this:
/**
* Setting
*
* #ORM\Table(name="my_parameters")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ParameterRepository")
*/
class Parameter
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="parameter_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="value", type="text", nullable=true)
*/
private $value;
/**
* #param string|null $name
* #param string|null $value
*/
public function __construct($name = null, $value = null)
{
$this->setName($name);
$this->setValue($value);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Parameter
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set value
*
* #param string $value
*
* #return Parameter
*/
public function setValue($value = null)
{
$this->value = serialize($value);
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
$data = #unserialize($this->value);
return $this->value === 'b:0;' || $data !== false ? $this->value = $data : null;
}
}
Then I would add a CompilerPass which will help get all of the parameters from the database and cache them so that your app doesn't make unnecessary sql queries to the database. That might look something similar to the following class:
// AppBundle/DependencyInjection/Compiler/ParamsCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParamsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$settings = $em->getRepository('AppBundle:Parameter')->findAll();
foreach($settings as $setting) {
// I like to prefix the parameters with "app."
// to avoid any collision with existing parameters.
$container->setParameter('app.'.strtolower($setting->getName()), $setting->getValue());
}
}
}
And finally, in your bundle class (i.e. src/AppBundle/AppBundle.php) you add the compiler pass:
namespace AppBundle;
use AppBundle\DependencyInjection\Compiler\ParamsCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $builder)
{
parent::build($builder);
$builder->addCompilerPass(new ParamsCompilerPass(), , PassConfig::TYPE_AFTER_REMOVING);
}
}
Now you can create a DoctrineFixture template to load the parameters you use all the time. With the TwigExtension you will still be able to call the parameter from the twig template and you can create a web UI to change some of the parameters/settings.

PHPDoc #return type equals class field type (in PhpStorm 10.0.4)

So I have a trait like as below:
trait RepositoryTrait
{
/**
* Find a model by its primary key.
*
* #param int $id
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function find($id, array $columns = ['*'])
{
return $this->query()->find($id, $columns);
}
}
and the interface:
interface Repository
{
/**
* Find a model by its primary key.
*
* #param int $id
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function find($id, array $columns = ['*']);
}
and I have repository class like:
class FixedAssetRepository implements Repository
{
use RepositoryTrait;
/**
* FixedAsset model.
*
* #var FixedAsset
*/
protected $model;
/**
* Repository constructor.
*
* #param FixedAsset $fixedAsset
*/
public function __construct(FixedAsset $fixedAsset)
{
$this->model = $fixedAsset;
}
}
and what I'm looking for is to define somehow that method find (in trait or interface) is type of FixedAsset - but this should work always for each new Repository class that I'll create (that implements Repository interface).
I need it for type hinting in PhpStorm 10.0.4
Is it possible somehow?
So the result should be that when I call somewhere I.e.:
$fixedAsset = $this->fixedAssetRepositry->find($id);
PhpStorm will know that $fixedAsset is a object of a class FixedAsset not just \Illuminate\Database\Eloquent\Model (now is working like that).
So I need something that like in interface (or trait?):
/**
* Find a model by its primary key.
*
* #param int $id
* #param array $columns
* #return $this->model
*/
public function find($id, array $columns = ['*']);
but of course #return $this->model doesn't work.
I'll be appreciated for any suggestions about that.
The only solution that I can think of right now would be using #method in PHPDoc comment for that FixedAssetRepository class and "redeclare" that find() method with correct signature (return type). Since it's a PHPDoc solution it will not have any effect on PHP code during runtime.
Sample code:
/**
* #method FixedAsset find(int $id, array $columns) Find a model by its primary key
*/
class FixedAssetRepository implements Repository
{
...
More on the #method tag -- https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#711-method

Symfony2 conditional form validation

I have some difficulties about applying validation for only one associated entity.
So I have two entities, News and NewsTranslation. A news could be translated in multiple languages. But I would like to apply validation only if locale is en.
// AppBundle/Entity/News.php
class News
{
use ORMBehaviors\Translatable\Translatable;
use ORMBehaviors\Timestampable\Timestampable;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="status", type="smallint")
* #Assert\NotBlank
*/
private $status;
...
}
// AppBundle/Entity/NewsTranslation.php
class NewsTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, nullable=true)
* #Assert\NotBlank
* #Assert\Length(max = 255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="text", type="string", nullable=true)
* #Assert\NotBlank
*/
private $text;
}
# AppBundle/Resources/config/validation.yml
AppBundle\Entity\News:
properties:
translations:
- Valid: ~
I tried to use a Closure for the validation_groups form option. But it looks like Symfony do validation on News entity and Valid constraint apply the same groups on NewsTranslation.
I know I could use Callback constraint but that's mean to redo NotBlank, Length and other exiting constraints by myself. And I would like to avoid it if possible.
EDIT:
I'm using Symfony 2.8.*
I try using an en validation group. But looks like the validation is launch on News entity with validation_groups. And with Valid constraint the en validation group is given to validate NewsTranlation. So even it's the en or fr translation the group change nothing in this case.
I also try using the validation medatada through an #Assert\Callback or by using loadValidatorMetadata method into NewsTranslation entity. And the problem stay similar. I can't apply an constraint for a specific entity of collection.
I finally found a way by creating a custom validator.
Like this I could use core constraints easily.
In the translation entity, I could use my validator like this:
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, nullable=true)
* #Assert\Length(max = 255)
* #AppAssert\ValidTranslation(locales = {"fr"}, constraints = {
* #Assert\NotBlank
* })
*/
private $title;
And the validator:
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraints\Composite;
/**
* #Annotation
* #Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* #author Nicolas Brousse
*/
class ValidTranslation extends Composite
{
public $locales = array();
public $constraints = array();
public function getCompositeOption()
{
return 'constraints';
}
public function getRequiredOptions()
{
return array('locales', 'constraints');
}
}
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* #author Nicolas Brousse
*/
class ValidTranslationValidator extends ConstraintValidator
{
/**
* If property constraint
* {#inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof ValidTranslation) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\ValidTranslation');
}
if (false) { // #todo check by interface or trait
throw new UnexpectedTypeException($value, 'not a translation entity');
}
$context = $this->context;
$entity = $this->context->getObject();
if (in_array($entity->getLocale(), $constraint->locales)) {
$context = $this->context;
if ($context instanceof ExecutionContextInterface) {
$validator = $context->getValidator()->inContext($context);
$validator->validate($value, $constraint->constraints);
} else {
// 2.4 API
$context->validateValue($value, $constraint->constraints);
}
}
}
}
you form need to return 2 validations_groups, "Default" and the validation group corresponding to the "en" locale

Assert unique validation in Sonata Admin

I'm using Symfony 2.1 for a project. I use SonataAdminBundle for administration usage.
i want to add an assert to my slug property in my admin class.. how can i do this?
in my entity i had set the assertion but it seems that it doesn't work here :(
related codes:
the entity :
/*
* #ORM\Table(name="default_doctor_specialty")
* #UniqueEntity("uniqueSlugName")
* #ORM\Entity
*/
class Test {
//..
/**
* #var string
* #Gedmo\Slug(fields={"name"},unique=false)
*
* #ORM\Column(name="unique_slug_name", type="string", length=255, nullable=false , unique=true)
*/
private $uniqueSlugName;
}
in admin class:
class TestAdmin extends Admin {
protected $formOptions = array(
'validation_groups' => 'Default'
);
//...
}
why the default validation doesn't work???
& even if doesn't work like this how can i set the unique validation inside admin class ???
thanks for your answers :)
finally i defined a validation group for my entity:
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
/*
* #ORM\Table(name="default_doctor_specialty")
* #DoctrineAssert\UniqueEntity(fields="uniqueSlugName", message="A Speciality with same slug already exists", groups={"test"})
* #ORM\Entity
*/
class Test {
//..
/**
* #var string
* #Gedmo\Slug(fields={"name"},unique=false)
*
* #ORM\Column(name="unique_slug_name", type="string", length=255, nullable=false , unique=true)
*/
private $uniqueSlugName;
}
and in admin class i used test validation group instead of default!
thanks to AHWEBDEV on github!
From this link
This is the full exemple , it depend on your symfony and sonata version.
// src/AppBundle/Entity/Service.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity(
* fields={"host", "port"},
* errorPath="port",
* message="This port is already in use on that host."
* )
*/
class Service
{
/**
* #ORM\ManyToOne(targetEntity="Host")
*/
public $host;
/**
* #ORM\Column(type="integer")
*/
public $port;
}
I prefer not to mess my entities with hundreds of lines of such low level details like validation. One can define validation rules inside the Admin class. Usually the validation rules are different for admins than for clients as well.
final class TestAdmin
{
// … skipped for brevity
public function validate(ErrorElement $errorElement, $object)
{
$errorElement->addConstraint(new UniqueEntity(['fields' => ['slug']]));
}
}

Symfony2+Doctrine - Validating one-to-many collection of entities

I have a form to create a new entity. That entity has a collection of other entities that are also entered in that form.
I want to use the validation options of the entity in the collection to validate those entities but it does not work. The validation rules of the "main" entity (Person) are checked, but the validation rules of the entities in the addressList collection (Address) are not checked. When I input invalid information in the fields, the submitted form is successfully validated.
In this example, the annotation for street is not used on validation.
class Person
{
...
/**
* #ORM\OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "detach"})
*/
protected $addressList;
....
}
class Address
{
...
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="addressList")
* #ORM\JoinColumn(name="person_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $owner;
/**
* #ORM\Column(type="string", length=75)
* #Assert\MinLength(
* limit=3,
* message="Street must have atleast {{ limit }} characters."
* )
*/
protected $street;
...
}
How can I get the form to validate the supplied Address entities?
I had the same problem but was solved with:
/**
* #ORM\OneToMany(
* targetEntity="Entity",
* mappedBy="mappedEntity",
* cascade={"persist" , "remove"}
* )
* #Assert\Valid
*/
I use this:
use Symfony\Component\Validator\ExecutionContextInterface;
class Person
{
...
/**
* #ORM\OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "detach"})
*/
protected $addressList;
....
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context)
{
if (!$this->getAddressList()->count()) {
$context->addViolationAt(
'addressList',
'You must add at least one address',
array(),
null
);
}
}
}
http://symfony.com/doc/current/reference/constraints/Callback.html
Just add annotation assert like following
/**
* #Assert\Count(
* min = "1",
* minMessage = "You must specify at least one"
* )
* #Assert\Valid
*
*/
protected $name_of_collection_property;
You could also use the "Valid" constraint with the "All" constraint :
/**
* #ORM\OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "detach"})
* #Assert\All({
* #Assert\Valid
* })
*/
protected $addressList;

Resources