I'm using Symfony2 validation getters to validate if endDate is later than startDate. It works fine except that I want the error to appear for endDate field not above the whole form. All other validation errors appears above the field it validates. Is there any way to do that?
validation part in my Entity:
/**
* #Assert\True(message = "Invalid date")
*/
public function isDatesValid()
{
return ($this->startDate < $this->endDate);
}
I don't think there is a way to achieve that with a getter validation.
Instead you could use a custom constraint: http://symfony.com/doc/current/cookbook/validation/custom_constraint.html
You'll need to overide the getTargets() method in order to access all properties:
class DateRangeConstraint extends Constraint {
public $message = 'Your message';
public function getTargets() {
return Constraint::CLASS_CONSTRAINT;
}
}
class DateRangeConstraintValidator extends ConstraintValidator {
public function validate($obj, Constraint $constraint) {
if ($obj->startDate() < $obj->endDate()) {
$this->context->addViolationAt(
'startDate',
$constraint->message,
array(),
null
);
}
}
}
Assign this constraint to one or both properties.
Related
I would like to extend the extension cart with a new field to put in the IBAN in the checkout.
So I created a new extension and added a database field with the following code in ext_tables.sql
#
# Table structure for table 'tx_cart_domain_model_order_item'
#
CREATE TABLE tx_cart_domain_model_order_item (
iban varchar(255) DEFAULT '' NOT NULL
);
Now I need to extend the class Item in
ext/cart/Classes/Domain/Model/Order/item.php
I tried to create a file in my extension
ext/cartextend/Classes/Domain/Model/Order/item.php
and tried to extend the class with:
namespace Extcode\Cart\Domain\Model\Order;
use Extcode\Cart\Property\Exception\ResetPropertyException;
class Item extends \Extcode\Cart\Domain\Model\Order
{
/**
* Iban
*
* #var string
*/
protected $iban;
/**
* #return string
*/
public function getIban()
{
return $this->iban;
}
/**
* #param string $iban
*/
public function setIban($iban)
{
$this->iban = $iban;
}
}
I also added an input field that is implemented correctly.
But the IBAN is not saved at all - i guess the extending of the class is wrong.
I really appreciate any hint.
Many thanks! Urs
Maybe you have to extend item.php like this (the rest looks fine):
namespace Extcode\YourExtension\Domain\Model\Order;
class Item extends \Extcode\Cart\Domain\Model\Order\Item
and do not forget to make it known to extbase for you to use iban in the front-end trough typoscript: (I have it to extend cart_products, you'll have to adept it)
config.tx_extbase {
persistence {
classes {
Extcode\CartExtendedProduct\Domain\Model\Product\Product {
mapping {
tableName = tx_cartproducts_domain_model_product_product
recordType =
}
}
}
}
objects {
Extcode\CartProducts\Domain\Model\Product\Product.className = Extcode\CartExtendedProduct\Domain\Model\Product\Product
}
}
I have TYPO3 version 7.6.18, I need add some property to model user
$public age = 0;
and I tried add different methods :
public setAge(){$this->age = 23;}
public age(){return $this->age;}
public getAge(){return $this->age;}
public age(){return 23;}
public getAge(){return 23;}
And on fluid I always get 0. {user.age} - I get 0. What is the problem ? May be because I have not field 'age' in DB table ? But I need add age property to user model without field in DB. Is it possible ? how to do it ?
First things first:
What user model are you talking about?
fe_user, beuser - or something completly different?
How did you tried to add these?
Have you created a custom class which extends the user class?
Greetings,
KamiYang
you must define your getter in this way:
public function getAge()
{
return 23;
}
In your examples you have forgot the function declaration.
If this don't work, check if the correct domain model is gotten.
Try to something like below.
/**
* #var Age
*/
protected $age = 23;
public function __construct($age) {
$this->setAge($age);
}
public function setAge($age) {
$this->age = $age;
}
public function getAge() {
return $this->age;
}
My goal: I built a custom constraint in SYMFONY, I needed to pass a variable to that constraint.
The context: The constraint do a check if a value is unique in the DB, if it is not, it raises a CONSTRAINT alert. That works alright when the FORM is used to create a new tuple in the DB but if it is an edit it raises an exception which should be bypass by checking that the value already existing, exists for the tuple Id being edited.
Hence I needed to pass the Id of the tuple being edited to my constraint check.
At first I implemented my custom constraint in my entity:
class MyEntity{
/**
* #MyBundleAssert\CheckValueAlreadyInDB(
* message = "already_exists_in_db",
* fieldToSearch = "my_value",
* tableToSearch = "my_table"
*)
*/
private myValue;
}
As one can see, I did not find a way to implement a way to pass a VARIABLE using the constraint with ANNOTATION. By searching, I understood I could do that by using the __construct() of my custom constraint class:
/**
* #Annotation
*/
class CheckValueAlreadyInDB extends Constraint{
public $message;
public $fieldToSearch;
public $tableToSearch;
public $idToCheck;
public $idToCheckFieldName;
public function __construct($options){
if(count($options)>0){
$this->idToCheck = $options['idToCheck'];
$this->idToCheckFieldName = $options['idToCheckFieldName'];
$this->fieldToSearch = $options['fieldToSearch'];
$this->tableToSearch = $options['tableToSearch'];
$this->message = $options['message'];
}
}
public function validatedBy()
{
return 'validator_check_value_already_in_db';
}
}
And, the ConstraintValidator extended class linked to it:
class CheckValueAlreadyInDBValidator extends ConstraintValidator
{
private $con;
public function __construct($con){
$this->con = $con;
}
public function validate($value, Constraint $constraint)
{
////My stuff to get a record from the DB////
$sel = new PdoSelect($this->con);
$search = $sel->returnRecordsInTableForSpecificKey([$constraint->fieldToSearch],[$value], $constraint->tableToSearch,false);
//////////////////////////////////////////////
$sameId = false;
if($constraint->idToCheck!==null){
$idToCheckInRetrieveRecord = $search->{$constraint->idToCheckFieldName};
$sameId = ($idToCheckInRetrieveRecord==$constraint->idToCheck)?true:false;
}
if($search!=null&&!$sameId){
$this->context->buildViolation($constraint->message)
->setParameter('%string%', $value)
->addViolation();
}
}
}
With service:
validator.unique.check_value_already_in_db:
class: MyBundle\Form\CustomConstraints\CheckValueAlreadyInDBValidator
arguments: ['#doctrine.dbal.default_connection']
tags:
- { name: validator.constraint_validator, alias: validator_check_value_already_in_db }
I my FORM (AbstractType extended class) for the field regarding myValue, I did edit the constraints attribute.
class MyEntityType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
....
$builder->add('myValue',****Type::class,array(
'constraints' => array(
new CheckValueAlreadyInDB(array(
'idToCheck'=>$options['data']->getId(),
'idToCheckFieldName'=>'id',
'fieldToSearch'=>'my_value',
'tableToSearch'=>'my_table',
'message' => "value_already_exists_in_db"))
)
));
...
}
}
I thought that the CONSTRAINT defined in the buildForm() would override the one defined in the * #MyBundleAssert\CheckValueAlreadyInDB(..) of MyEntity class (which should be the default behaviour). But It did not! I had to delete the ANNOTATION above MyEntity to make the constraint work as defined in the buildForm().
Does anyone know if there is a setting that could permit to have a constraint in a buildForm() overriding one existing as an ANNOTATION in MyEntity, but still let the ANNOTATION above a field in MyEntity be the default behavior? Or is there is a way to pass VARIABLE to ANNOTATIONS?
I found the solution.
My mistake was to try to use constraints in class MyEntityType extends AbstractType:
$builder->add('myValue',****Type::class,array(
'constraints' => array(
new CheckValueAlreadyInDB(array(
'idToCheck'=>$options['data']->getId(),
'idToCheckFieldName'=>'id',
'fieldToSearch'=>'my_value',
'tableToSearch'=>'my_table',
'message' => "value_already_exists_in_db"))
)
));
Update:
DON'T USE IT HERE
Have a look at class-constraint-validator section in the doc.
Implement the ConstraintValidator extended class above the class of the Entity where the validator has to execute its check and not above one attribute of the Entity class. That way one can have access to other attributes of the entity and use it as conditionals in the ConstraintValidator extended class.
I've got the following scenario: I'm validating appointments and there's a custom validator, which tells the user if his choosen date is valid or not. It's not valid, if the date is already blocked by another entity. This works flawlessly on adding new entities.
Now I'd like to trigger the date validation on edit only if the date itself has changed. So just changing the title of the appointment should not validate the date.
My entity class:
use Doctrine\ORM\Mapping as ORM;
use Acme\Bundle\Validator\Constraints as AcmeAssert;
/**
* Appointment
*
* #ORM\Entity
* #AcmeAssert\DateIsValid
*/
class Appointment
{
/**
* #ORM\Column(name="title", type="string", length=255)
*
* #var string
*/
protected $title;
/**
* #ORM\Column(name="date", type="date")
*
* #var \DateTime
*/
protected $date;
}
The validator class (used as a service):
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the date of an appointment.
*/
class DateIsValidValidator extends ConstraintValidator
{
/**
* {#inheritdoc}
*/
public function validate($appointment, Constraint $constraint)
{
if (null === $date = $appointment->getDate()) {
return;
}
/* Do some magic to validate date */
if (!$valid) {
$this->context->addViolationAt('date', $constraint->message);
}
}
}
The corresponding Constraint class is set to target the entity class.
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class DateIsValid extends Constraint
{
public $message = 'The date is not valid!';
/**
* {#inheritdoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
/**
* {#inheritdoc}
*/
public function validatedBy()
{
return 'acme.validator.appointment.date';
}
}
Now I don't find a clean way to depend on a date change. I could simply track the old date in my entity, but that doesn't feel like a proper solution, if I'd like to implement more complex constraints. :[
Cheers
Since symfony 2.3 you can use Form Events to solve this problem. I added the change-check code to my FormType, by storing (and cloning) the original entity at the form creation.
Then added a POST_SUBMIT event listener to check if the fields were changed. The listener can add validation errors to your fields.
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError;
use Acme\Bundle\Entity\Appointment;
class AppointmentType extends AbstractType
{
private $originalAppointment;
public function __construct(Appointment $original)
{
// save the original entity
$this->originalAppointment = clone $original;
}
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
// define your fields
$builder->addEventListener(FormEvents::POST_SUBMIT, [$this, 'dateCheckListener']);
}
public function dateCheckListener(FormEvent $event)
{
$appointment = $event->getData();
$form = $event->getForm();
// if no appointments exist, we can skip the check
if (empty($appointment) || empty($this->originalAppointment)) {
return;
}
if ($appointment->getDate() !== $this->originalAppointment->getDate()) {
// the dates changed, you can call your validator here
if ('dates are not valid') {
$form->get('date')->addError(new FormError('We have a problem.'));
}
}
}
}
In your controller, you can create this formType with the original appointment:
$appointment = $this->getYourAppointmentSomehow();
$form = $this->createForm(new AppointmentType($appointment), $appointment);
Maybe you will find this article useful, to check which property is changed. Everything is possible in symfony. You might end up writing entity listeners, listener resolvers and so on. Things can get ultra advanced.
http://docs.doctrine-project.org/en/latest/reference/change-tracking-policies.html
Pay attention to the setter method:
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
Do you see the trick?:)
I would also use !== operator to also check variable type.
You can also simplify things. You dont need to call _onPropertyChanged, but call the function, which will set a property 'dateChanged' to true. Then use method:
public function getGroupSequence()
{
if($this->dateChanged)
{
return ['date_check'];
}
else
{
return false;
}
}
And also tell your class that it implements GroupSequenceProviderInterface.
You can then use the validation group in your validation.yml for example.
maybe you want to try it with a preUpdate-Listener instead of a custom validation constraint?
Section 10.5.4 in the doctrine documentation gives an example of a validation listener "ValidCreditCardListener".
i know this will not work for automagic form validation, but i think it's the fastest way atm.
edit:
another option could be to use #UniqueEntiy constraint for the date field of your Appointment class. this will not break form validation but will cause an additional database query (as far as i know)
again, im trying a simple form for recovery forgot user password.
The form only contains a email input. The form handler, receive the email, search the user, restore your password and send a email with the new password. Is important says that user not is authenticated.
So, i need create a custom constraint that check if the email exists in the db. I have an method of a own service that do it. So, i need bind the method with the constraint.
The custom validation class is:
class ExistEmailValidator extends ConstraintValidator
{
protected $userService;
public function setUserService($userService)
{
echo "Setter...";
var_dump($userService);
die();
$this->userService = $userService;
}
public function validate($value, Constraint $constraint)
{
if($this->userService->existUserEmail($value) == false){
$this->context->addViolation($constraint->message, array('%string%' => $value));
}
}
}
I inject the service from services.yml:
validator.unique.ExistEmailValidator:
class: AppsManantiales\CommonBundle\Validator\Constraints\ExistEmailValidator
tags:
- {name: validator.constraint_validator, alias: user_email_validator}
calls:
- [setUserService, ["userservice"]]
The problem is taht, never inject the service, because Symfony says:
Error: Call to a member function existUserEmail() on a non-object
Note: the user service id is: "userservice".
php app/console container:debug | grep UserService
userservice container AppsManantiales\CommonBundle\Services\UserService
Any ideas ?
UPDATE 1
My constraint class is:
class ExistEmail extends Constraint{
public $message = 'El e-mail "%string%" no pertenece a ningun usuario del sistema.';
public function validatedBy()
{
return get_class($this).'Validator';
}
}
And the form definition is:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('anUserEmail', 'email',
array(
'constraints' => array(
new ExistEmail()
)
));
}
Replace,
public function validatedBy()
{
return get_class($this).'Validator';
}
in your ExistEmail constraint class by,
public function validatedBy()
{
return 'user_email_validator';
}
in order to map your constraint with the right instance of ExistEmailValidator (The one instantiated in the service container).