unable to validate DateType - symfony

Here's what I have:
In my route I create a default entity. "startdate" and "enddate", both date objects.
/**
* #Route("/vacature/nieuw", name="create_vacancy")
*/
public function createVacancyAction(Request $request)
{
$vacancy = new Vacancy();
$vacancy->setStartdate(new \DateTime())
->setEnddate(new \DateTime());
$form = $this->createForm(VacancyType::class, $vacancy);
// check if valid and persist or whatever
}
In my VacancyType I set the date constraint for both my date fields like so:
->add("startdate", DateType::class, array(
"widget" => "single_text",
"constraints" => array(
new Date(array(
"message" => "vacancy.date.message"
)),
new GreaterThanOrEqual("today")
)
))
The dates are pre-filled in html in a yyyy-mm-dd format which is how I want them to.
Then, without editing the html field I receive this error upon submitting the form:

found it! seemed I tried the constraint new GreaterThanOrEqual("startdate"). Thinking it would refer to my startdate....sadly it doesn not :(

Related

ChoiceType multiple attribute according to entity property (how to choose between returning a collection of entities or one entity)

I'm working on a quiz project with the Symfony framework (version 4.4) and Doctrine as ORM.
There is a ManyToOne relation between the Answer and the Question entities, as for the QuizQuestion and Answer entities. I use the QuizQuestion entity to make the link between a quiz, a question, and the selected answer(s).
I use a EntityType "QuizQuestionType" with the multiple attribute set to true to collect answers, and it works as expected :
$builder
->add('answers', EntityType::class, [
'class' => Answer::class,
'choices' => $this->fillAnswers($quizQuestion),
'expanded' => true,
'multiple' => true,
]);
The thing is, I want to be able to setup question as multiple or single choice. If I set the EntityType multiple attibute to false, I got the error :
Entity of type "Doctrine\ORM\PersistentCollection" passed to the
choice field must be managed. Maybe you forget to persist it in the
entity manager?
I could use two answers entities with a OneToMany and a OneToOne relations, but it seems a really poor design to me.
I wonder how it can be done, ideally with a property in the Question entity that indicates if it is a multiple or unique choice question. That will allow me to simply declare it in the backend (because technically, a multiple choice question may have only one good answer, so I can't calculate it by the number of answers).
Do you have any idea on how I can achieve this ?
Here is the conceptual data model :
CDM
The answer entity : https://pastebin.com/kiRTHnvL
The QuizQuestion entity : https://pastebin.com/wL3v9fwT
Thank you for your help,
EDIT 01/08/2020
As suggested by #victor-vasiloi, I added an event listener to the form type so I can setup the correct extensions. I was not able to add the transformer though. I found the solution here and created an extension to use a data transformer from the event listener :
QuizQuestionType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder){
$quizQuestion = $event->getData();
$form = $event->getForm();
if ($quizQuestion->getQuestion()->getIsMultiple()){
$form->add('answers', EntityType::class, [
'class' => Answer::class,
'choices' => $this->fillAnswers($quizQuestion),
'expanded' => true,
'multiple' => true,
]);
} else {
$form->add('answers', EntityType::class, [
'class' => Answer::class,
'choices' => $this->fillAnswers($quizQuestion),
'expanded' => true,
'multiple' => false,
'model_transformer' => new CollectionToAnswerTransformer(),
]);
}
})
;
}
ModelTransformerExtension
class ModelTransformerExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
// return FormType::class to modify (nearly) every field in the system
return [FormType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
if (isset($options['model_transformer'])) {
$builder->addModelTransformer($options['model_transformer']);
}
}
public function configureOptions(OptionsResolver $resolver) {
parent::configureOptions($resolver);
$resolver->setDefaults(array('model_transformer' => null));
}
}
Now the form could be loaded. When submitting though (in a case of a unique answer with radio buttons), a CollectionToArrayTranformer was giving the following error :
Expected argument of type "App\Entity\Answer", "array" given at
property path "answers".
I tried a custom CollectionToAnswerTransformer, that looks like this :
class CollectionToAnswerTransformer implements DataTransformerInterface
{
/**
* #param mixed $collection
* #return mixed|string
*/
public function transform($collection)
{
if (null === $collection){
return '';
}
else
{
foreach ($collection as $answer){
return $answer;
}
}
}
/**
* #param mixed $answer
* #return ArrayCollection|mixed
*/
public function reverseTransform($answer)
{
$collection = new ArrayCollection();
$collection->add($answer);
return $collection;
}
}
But with no better results. I get the error :
Expected argument of type "App\Entity\Answer", "instance of
Doctrine\Common\Collections\ArrayCollection" given at property path
"answers".
It looks like an issue with the reverse transformer method, but if I change it to return an entity, I got the opposite error :
Could not determine access type for property "answers" in class
"App\Entity\QuizQuestion": The property "answers" in class
"App\Entity\QuizQuestion" can be defined with the methods
"addAnswer()", "removeAnswer()" but the new value must be an array or
an instance of \Traversable, "App\Entity\Answer" given...
I think I'm almost at it, but I don't know if my transformer is the way to go or if it is easier than that...
To setup questions with single choice you could use a radio button, and checkboxes for multiple choices.
Radio button is expanded "true" and multiple "false".
Checkbox is expanded "true" and multiple "true".
Code example that display checkboxes:
$builder
->add('filter', EntityType::class, array(
'class' => 'FilterBundle:Filter',
'multiple' => true,
'expanded' => true,
'required' => true
));
Source: https://symfony.com/doc/current/reference/forms/types/choice.html#select-tag-checkboxes-or-radio-buttons
And if you want to define it for each question before displaying, there could be a field on your question entity (for example a boolean "multiple").
You can dynamically set the multiple option based on the given Question using a form event listener on the Symfony\Component\Form\FormEvents::PRE_SET_DATA event, here's where you can learn more about dynamically modifying the form and form events.
Using the same logic, when the multiple option is set to true, you can add the Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer model transformer to the answers field like this $builder->get('answers')->addModelTransformer(new CollectionToArrayTransformer()); which will ensure the transformation between the Doctrine Collection and the choices array (including single choice).

How do you Filter CSV exports on silverstripe CMS?

I am trying to export a CSV of my data which is currently displayed in a section of my Silverstripe CMS as filtered by a particular date range . It works fine at the moment when exporting the entire contents but I would like to be able to filter the results that are exported so that it returns all results within a particular date range.
My Database has a column thats records the date created - in the format 'D-M-Y; H-M-S' which I think could be used to do the filtering but I cant figure out how to set up the search filter. I understand that if you use the searchable fields and then export, you only export the filtered search results so would assume thats the best way of doing it but can't figure out how to implement it.
Any suggestions would be greatly appreciated.
-- disclaimer - I would have liked to put this on the silverstripe forum but I am completely unable to sign up for some reason - I never receive the email confirmations. ---
<?php
namespace AffiliateProgram;
use SilverStripe\Forms\GridField\GridField;
use UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
use SilverStripe\Security\Permission;
use SilverStripe\ORM\DataObject;
class MemberBonus extends DataObject
{
private static $db = [
'Amount' => 'Currency',
'Confirmed' => 'Boolean',
'Level' => 'Int',
'Percentage' => 'Int'
];
private static $has_one = [
'Member' => 'AffiliateProgram\Member',
'MemberPayment' => 'AffiliateProgram\MemberPayment',
'PaymentType' => 'AffiliateProgram\PaymentType',
'ProgramType' => 'AffiliateProgram\ProgramType'
];
private static $summary_fields = [
'Amount' => 'Amount (USD)',
'Member.Email' => 'Email',
'Level',
'MemberPayment.PaymentType.Symbol' => 'Recieved As',
'Percentage' => 'Percentage Bonus Applied',
'ProgramType.Name' => 'Program Type',
'MemberPayment.Created' => 'Payment Date',
'Confirmed' => 'Confirmed?',
'MemberPayment.ID' => 'Payment ID'
];
}
There is also a DateCreated column on the table.
You can add custom search fields to a ModelAdmin via getSearchContext(), and customise the query based on them with getList(). See this section of the SilverStripe documentation.
Here's an example of excluding results that have a CreatedAt value below the date provided in a search field (assuming your ModelAdmin only manages MemberBonus):
<?php
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\Forms\DatetimeField;
class MemberBonusAdmin extends ModelAdmin
{
...
public function getSearchContext()
{
$context = parent::getSearchContext();
$context->getFields()->push(new DatetimeField('q[CreatedAfter]', 'Created After'));
return $context;
}
public function getList()
{
$list = parent::getList();
$params = $this->getRequest()->requestVar('q');
if (!empty($params['CreatedAfter'])) {
$list = $list->exclude('CreatedAt:LessThan', $params['CreatedAfter']);
}
return $list;
}
}
To get a range working, you'd just need to add a CreatedBefore field and filter.

How to validate array of arrays in Symfony 4

I want to know how can I validate array of arrays in symfony.
My validation rules are:
User - NotBlank
Date - Date and NotBlank
Present - NotBlank
So far I have done this:
$validator = Validation::createValidator();
$constraint = new Assert\Collection(array(
'user' => new Assert\NotBlank(),
'date' => new Assert\Date(),
'present' => new Assert\NotBlank()
));
$violations = $validator->validate($request->request->get('absences')[0], $constraint);
But the problem is that it only allows to validate single array eg.
$request->request->get('absences')[0].
Here is how the array looks like:
You have to put the Collection constraint inside All constraint:
When applied to an array (or Traversable object), this constraint allows you to apply a collection of constraints to each element of the array.
So, your code will probably look like this:
$constraint = new Assert\All(['constraints' => [
new Assert\Collection([
'user' => new Assert\NotBlank(),
'date' => new Assert\Date(),
'present' => new Assert\NotBlank()
])
]]);
Update: if you want to use annotations for this, it'll look something like this:
#Assert\All(
constraints={
#Assert\Collection(
fields={
"user"=#Assert\NotBlank(),
"date"=#Assert\Date(),
"present"=#Assert\NotBlank()
}
)
}
)

Sortable Sonata Type Model in Admin

Did someone tried the tutorial about Sortable Sonata Type Model in Admin.
I've followed it step by step without missing anything (I'm pretty sure) but can't get a good result at the end.
Basically what I'm trying to do is : I have 3 entities, Article, Tag and ArticleTag (eq to User, Expectation and UserHasExpectation in the tutorial)
Everything seems good until the UserHasExpectationAdmin:
protected function configureFormFields(FormMapper $formMapper){
// ...
$formMapper
->add('userHasExpectations', 'sonata_type_model', array(
'label' => 'User\'s expectations',
'query' => $this->modelManager->createQuery('UserBundle\Entity\Expectation'),
'required' => false,
'multiple' => true,
'by_reference' => false,
'sortable' => true,
))
;
$formMapper->get('userHasExpectations')->addModelTransformer(new ExpectationDataTransformer($this->getSubject(), $this->modelManager));}
I think an attribute 'class' => 'UserBundle\Entity\Expectation' should be added to 'userHasExpectations' field else Symfony says that it's an invalid value.
Then the other problem is in the dataTransformer:
It launch me the error:
Attempted to call an undefined method named "create" of class "Main\CoreBundle\Form\DataTransformer\TagDataTransformer"
I think a use statement should be added but I don't know which one. More over, suppose I have the right use statement I don't realize what the writer is aiming to do, if it's creating UserHasExpectation records why don't he add a userHasExpectations->setUser($this->User) ???
Also I want to add after "vardumping" $this->Subject before :
$formMapper->get('userHasExpectations')->addModelTransformer(new ExpectationDataTransformer($this->getSubject(), $this->modelManager));
It seems to have a proper Entity Object with all fields on NULL values...
FINALLY SOLVED IT!
So, the code of the tutorial contains many...mistakes
In spite of trying to create 'userHasExpectation' in the DataTransformer we just return the object userHasExpectation in the reverse DataTransformer then we create our records in the postPersist and postUpdate of our Admin Class that way :
/**
* {#inheritdoc}
*/
public function postUpdate($object)
{
$position = 0;
$uniqId = $this->getUniqId();
$request = $this->getRequest()->get($uniqId);
$qb = $this->modelManager->createQuery('MainCoreBundle:ArticleTag', 'at');
$TagsToRemove = $qb->where('at.article = :article')
->setParameter('article', $object)
->getQuery()
->getResult();
foreach ($TagsToRemove as $Tag) {
$this->modelManager->delete($Tag);
}
foreach($request["article_tags"] as $tag)
{
$Tag = $this->modelManager->find('MainCoreBundle:Tag', $tag);
$article_tags = new ArticleTag;
$article_tags->setTag($Tag);
$article_tags->setArticle($object);
$article_tags->setPosition($position++);
$this->modelManager->create($article_tags);
}
}
/**
* {#inheritdoc}
*/
public function postPersist($object)
{
$position = 0;
$uniqId = $this->getUniqId();
$request = $this->getRequest()->get($uniqId);
foreach($request["article_tags"] as $tag)
{
$Tag = $this->modelManager->find('MainCoreBundle:Tag', $tag);
$article_tags = new ArticleTag;
$article_tags->setTag($Tag);
$article_tags->setArticle($object);
$article_tags->setPosition($position++);
$this->modelManager->create($article_tags);
}
}
Hope this will help Somebody who has the same trouble.
#Sonata-admin-team : I hope you will read this and have time to update the tutorial in question.
Thanks,
Epixilog
For Sonata 3 adding the class attribute 'class'=> 'UserBundle\Entity\Expectation' resolved the problem for me.

How do I add an unbound field to a form in Symfony which is otherwise bound to an entity?

Maybe I'm missing the obvious but how do I (or can I) add an extra "unbound" field to a Symfony form that is otherwise bound to an entity?
Let's say I have an entity with fields first_name and last_name. I do the typical thing in my form class buildForm method.
$builder
->add('first_name')
->add('last_name')
;
and this in my controller:
$editForm = $this->createForm(new MyType(), $entity);
That works nicely but I'd like to add another text box, let's call it "extra", and receive the value in the POST action. If I do $builder->add('extra')‍, it complains that
NoSuchPropertyException in PropertyAccessor.php line 479:
Neither the property "extra" nor one of the methods "getExtra()", "extra()", "isExtra()", "hasExtra()", "__get()" exist and have public access in class...
Which is correct. I just want to use it to collect some extra info from the user and do something with it other than storing it with the entity.
I know how to make a completely standalone form but not one that's "mixed".
Is this possible?
In your form add a text field with a false property_path:
$builder->add('extra', 'text', array('property_path' => false));
You can then access the data in your controller:
$extra = $form->get('extra')->getData();
UPDATE
The new way since Symfony 2.1 is to use the mapped option and set that to false.
->add('extra', null, array('mapped' => false))
Credits for the update info to Henrik Bjørnskov ( comment below )
Since Symfony 2.1, use the mapped option:
$builder->add('extra', 'text', [
'mapped' => false,
]);
According to the Documentation:
allow_extra_fields
Usually, if you submit extra fields that aren't configured in your form, you'll get a "This form should not contain extra fields." validation error.
You can silence this validation error by enabling the allow_extra_fields option on the form.
mapped
If you wish the field to be ignored when reading or writing to the object, you can set the mapped option to false.
class YourOwnFormType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'allow_extra_fields' => true
)
);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$form = $builder
->add('extra', TextType::class, array(
'label' => 'Extra field'
'mapped' => false
))
;
return $form;
}
}

Resources