Problems to validate empty date field with extbase - extbase

Why did Extbase throws an exception if my start date field is empty. If the date has the wrong format, the validation works. But an empty value shows this:
Uncaught TYPO3 Exception ... Events::setEnd() must be an instance of DateTime, null given ...
What is wrong with the following code in my model?
/**
* start
*
* #var \DateTime
* #validate NotEmpty
* #validate(type="DateTime", options={"locale"="de_DE"})
*/
protected $start;
And here is my TCA
'start' => [
'exclude' => false,
'label' => 'Start',
'config' => [
'type' => 'input',
'renderType' => 'inputDateTime',
'size' => 10,
'eval' => 'datetime',
'default' => time()
],
],
Cache cleared, typo3temp folder deleted.
UPDATE:
Perhaps the error is in my initializeUpdateAction() where i have to set the date format?
public function initializeUpdateAction() {
$user = $this->request->getArgument('feUsers');
$events = $user['events'];
if( is_array($events) ) {
foreach ($events as $i => $event) {
$this->arguments->getArgument('feUsers')
->getPropertyMappingConfiguration()->forProperty('events.'.$i.'.start')
->setTypeConverterOption(
'TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\DateTimeConverter',
\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT,
'd.m.Y'
);
}
}
}

enhance your validation:
'eval' => 'datetime,int',
an empty date field is represented with 0, which is no valid date format.

Related

After submitting the form, providing a file, I get the error: "Field is required"

I have this form: https://greektoenglish.com/translation
After I complete the form, provide it with a file, and finally submit it, I get this error: "field is required". That the file field is required. But I already completed the field.
If I remove "'#required' => TRUE," from the code where the file upload field is declared, fill the form out, and submit it, then the form is submitted correctly.
How can I solve this?
This is my code:
<?php
namespace Drupal\submit_translation\Form;
use Drupal\Component\Utility\EmailValidatorInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\mimemail\Utility\MimeMailFormatHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The example email contact form.
*/
class SubmitTranslation extends FormBase {
/**
* The email.validator service.
*
* #var \Drupal\Component\Utility\EmailValidatorInterface
*/
protected $emailValidator;
/**
* The language manager service.
*
* #var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The mail manager service.
*
* #var \Drupal\Core\Mail\MailManagerInterface
*/
protected $mailManager;
/**
* Constructs a new ExampleForm.
*
* #param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
* The email validator service.
* #param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
* #param \Drupal\Core\Mail\MailManagerInterface $mail_manager
* The mail manager service.
*/
public function __construct(EmailValidatorInterface $email_validator, LanguageManagerInterface $language_manager, MailManagerInterface $mail_manager) {
$this->emailValidator = $email_validator;
$this->languageManager = $language_manager;
$this->mailManager = $mail_manager;
}
/**
* {#inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('email.validator'),
$container->get('language_manager'),
$container->get('plugin.manager.mail')
);
}
/**
* {#inheritdoc}
*/
public function getFormId() {
return 'submit_translation_form';
}
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $dir = NULL, $img = NULL) {
$form['intro'] = [
'#markup' => $this->t('Use this form to send us the document that we\'ll translate!'),
];
$form['from'] = [
'#type' => 'textfield',
'#title' => $this->t('Name'),
'#description' => $this->t("Your full name."),
'#required' => TRUE,
];
$form['from_mail'] = [
'#type' => 'textfield',
'#title' => $this->t('Email address'),
'#description' => $this->t("Your email address."),
'#required' => TRUE,
];
$form['params'] = [
'#tree' => TRUE,
'subject' => [
'#type' => 'textfield',
'#title' => $this->t('Title'),
'#description' => $this->t("The title of the document."),
'#required' => TRUE,
],
'count' => [
'#type' => 'textfield',
'#title' => $this->t('Word Count'),
'#description' => $this->t("The word count of the document."),
'#required' => TRUE,
],
'body' => [
'#type' => 'textarea',
'#title' => $this->t('Comments'),
'#description' => $this->t("Tell us if you have any special requirements."),
'#required' => TRUE,
],
// This form element forces plaintext-only email when there is no HTML
// content (that is, when the 'body' form element is empty).
'plain' => [
'#type' => 'hidden',
'#states' => [
'value' => [
':input[name="body"]' => ['value' => ''],
],
],
],
'attachments' => [
'#name' => 'files[attachment]',
'#type' => 'file',
'#title' => $this->t('Choose a file to send for translation.'),
'#required' => TRUE,
],
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Send message'),
];
return $form;
}
/**
* {#inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Extract the address part of the entered email before trying to validate.
// The email.validator service does not work on RFC2822 formatted addresses
// so we need to extract the RFC822 part out first. This is not as good as
// actually validating the full RFC2822 address, but it is better than
// either just validating RFC822 or not validating at all.
$pattern = '/<(.*?)>/';
$address = $form_state->getValue('from_mail');
preg_match_all($pattern, $address, $matches);
$address = isset($matches[1][0]) ? $matches[1][0] : $address;
if (!$this->emailValidator->isValid($address)) {
$form_state->setErrorByName('from_mail', $this->t('That email address is not valid.'));
}
$file = file_save_upload('attachment', [ 'file_validate_extensions' => array('doc docx pdf')], 'temporary://', 0);
if ($file) {
$form_state->setValue(['params', 'attachments'], [['filepath' => $file->getFileUri()]]);
}
}
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// First, assemble arguments for MailManager::mail().
$module = 'submit_translation';
$key = "solon_key";
$to = "info#gexl.eu";
$langcode = $this->languageManager->getDefaultLanguage()->getId();
$params = $form_state->getValue('params');
$reply = "";
$send = TRUE;
$params['body'] .= " Count: " . $params['count'];
// Second, add values to $params and/or modify submitted values.
// Set From header.
if (!empty($form_state->getValue('from_mail'))) {
$params['headers']['From'] = MimeMailFormatHelper::mimeMailAddress([
'name' => $form_state->getValue('from'),
'mail' => $form_state->getValue('from_mail')
]);
}
elseif (!empty($form_state->getValue('from'))) {
$params['headers']['From'] = $from = $form_state->getValue('from');
}
else {
// Empty 'from' will result in the default site email being used.
}
// Handle empty attachments - we require this to be an array.
if (empty($params['attachments'])) {
$params['attachments'] = [];
}
// Remove empty values from $param['headers'] - this will force the
// the formatting mailsystem and the sending mailsystem to use the
// default values for these elements.
foreach ($params['headers'] as $header => $value) {
if (empty($value)) {
unset($params['headers'][$header]);
}
}
// Finally, call MailManager::mail() to send the mail.
$result = $this->mailManager->mail($module, $key, $to, $langcode, $params, $reply, $send);
if ($result['result'] == TRUE) {
$this->messenger()->addMessage($this->t('Your message has been sent.'));
}
else {
// This condition is also logged to the 'mail' logger channel by the
// default PhpMail mailsystem.
$this->messenger()->addError($this->t('There was a problem sending your message and it was not sent.'));
}
}
}
This happens because the form element '#type' => 'file' has no #value to validate. #required fields must have a #value set otherwise validation fails.
This is (now considered) a very old issue that has been fixed in Drupal 9.5.x, but this was assumed in the good old days of Drupal 7, as mentioned in the Form API reference :
#required: Indicates whether or not the element is required. This
automatically validates for empty fields, and flags inputs as
required. File fields are NOT allowed to be required.
So I guess the best solution is to upgrade to 9.5.x or above, if feasible, but as sometimes upgrading makes things complicated, you might prefer to review and apply the patch manually to your current code base.
[EDIT]: If still having issues after upgrade to >= 9.5.2,
Looking at the patch, a default valueCallback is now used to provide a #value to file form elements, but.. well there is another issue :
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input === FALSE) {
return NULL;
}
$parents = $element['#parents'];
$element_name = array_shift($parents); # <- problem here :/
$uploaded_files = \Drupal::request()->files->get('files', []);
$uploaded_file = $uploaded_files[$element_name] ?? NULL;
if ($uploaded_file) {
// Cast this to an array so that the structure is consistent regardless of
// whether #value is set or not.
return (array) $uploaded_file;
}
return NULL;
}
See how it doesn't care about whether or not the element has a #name explicitly defined ? and whether or not #parents is a tree ? Now because of those wrong assumptions on the element's name and its parents, you are somehow forced to either :
Leave the #name property unset and refer to the file later on validation/submit as 'params' (the parents root) instead of 'attachment'. Or,
Stick with #tree => FALSE. Or,
Provide your own #value_callback (deprecated ...?)

Symfony form validation does not work on edit

I have a form with 3 fields as below : storeClient , type and line that belong to Fashion entity.
I basically have the same problem mentioned here :
Symfony Form Validation not working in Edit
But I was surprised when I edited the form and chose the placeholder as option for Client and I filled the line and type fields , I got that for the client field, it DOES display my validation message "Please choose an option" .which is good .
However for the remaining two fields, if line or type are edited in such a way to choose the placeholder option, the application crashed and gives the argumet exception error.
I did a dump; die; inside the controller (you can see it commented below) . And I got the $fashion object like this when I chose the placeholder for all the fields aka for the client,type and line fields :
Fashion object :
storeClient: null
line: Proxies ...\RefLine
___isinitilized___ :true
and all the info of the line that was set initiallly in this object when I first enterede its edit page.
type: Proxies ...\RefType
___isinitilized___ :true
and all the info of the type that was set initiallly in this object when I first enterede its edit page.
so my question is why the validations work on edit for the client field and does not work for line and type ? I do not think that it is related to the fact that it is a choicettype whereas the other two are entitytype. Moreover, I didn't put a "?" in the setter of client. So i don't see why it works for this field and WHY it gave a Null value when I printed the object and it didn't print the initial client value that was already set in the object when I first landed on the edit page although the two other fields hold the values that were already stored in the object initially.
FashionType.php
->add('storeClient', ChoiceType::class,
[
'label' => 'Store Client',
'choices' => $choicesClient,
'choice_value' => function ($value) {
if ($value instanceof Client) {
return $value->getId();
} else {
return $value;
}
},
'placeholder' => 'Choose ..',
'choice_label' => 'diplayLabel',
'attr' => ['class' => "chosen"],
'required' => true,
]
)
->add('type',
EntityType::class,
[
'label' => 'Clothes Type',
'class' => RefType::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('refType')
->orderBy('refType.id', 'ASC');
},
'attr' => ['class' => "chosen"],
'placeholder' => 'Choose..',
'required' => true,
'choice_label' => 'label',
])
->add('line',
EntityType::class,
[
'label' => 'cotation.creation_form.ligne_de_cotation',
'class' => RefLine::class,
'choice_value' => function ($value) {
if ($value instanceof RefLine) {
return $value->getId();
} else {
return $value;
}
},
'query_builder' => function (EntityRepository $er) {
return $er->getShoppingLines();
},
'attr' => ['class' => "chosen"],
'placeholder' => 'Choose..',
'required' => true,
'choice_label' => 'getLabel',
])
IN my controller, this function is called upon submitting the form :
public function validerAction(Request $request, $idFashion)
{
$em = $this->getDoctrine()->getManager();
/** #var Fashion $fashion */
$fashion = ($idFashion === null) ? new Fashion() : $em->getRepository(
'App:Fashion'
)->find($idFashion);
$form = $this->createForm(FashionType::class, $fashion);
// try {
$form->handleRequest($request);
//} catch(\InvalidArgumentException) {
//dump($fashion);die;
// }
if ($form->isSubmitted() && $form->isValid()) {..}
Here are my setters:
/**
* Set line
*
* #param Refline $line
*
* #return Fashion
*/
public function setLine(RefLine $line)
{
$this->line = $line;
return $this;
}
/**
* Set type
*
* #param RefType $type
*
* #return Fashion
*/
public function setType(RefType $type)
{
$this->type = $type;
return $this;
}
/**
* Set storeClient
*
* #param Client $storeClient
* #return Fashion
*/
public function setStoreClient($storeClient)
{
$this->storeClient = $storeClient;
return $this;
}
THe three fields were declared like this :
/**
* #ORM\ManyToOne(targetEntity="App\Entity\RefLine")
* #ORM\JoinColumn(name="line_id", referencedColumnName="id", nullable=false)
*/
private $line;
In EntityType::class field type is by default nullable. If you want to add validation on that then you have to write this
/**
*
* #Assert\NotBlank(message="Please enter Line", groups="groupName")
*/
private $line;
For more details you can read https://symfony.com/doc/current/validation.html
if you are using group name then you should declare in Form
$resolver->setDefaults([
// ...
'validation_groups' => ['Default', 'groupName'],
]);

Symfony 3 : how to use Collection and validate it

My problems obviously come from the fact that I do not understand well how to use CollectionType and validate it.
Here are classes that I have :
class Reservation {
private startDate;
private startTime;
private endDate;
private endtime;
.....
}
class Mission {
private $reservations
public function __construct() {
$this->reservations = new ArrayCollection();
}
....
}
At a certain time I create my Mission object, and I fill the reservations firld with a collection of some Reservation objects.
My form type is like this concerning the collection :
$builder->add('reservations', CollectionType::class, array(
'entry_type' => ReservationType::class,
'entry_options' => array('label' => false),
'label' => false,
'required'=> false
));
if the ReservationType class I have this :
$builder->add('startDate', DateType::class, array(
'label' => 'Date Aller',
'widget' => 'single_text',
'format' => 'dd/MM/yyyy',
'attr' => array(
'no_results_text' => 'JJ/MM/AAAA'
)
));
$builder->add('startTime', TextType::class, array(
'label' => 'Heure Aller',
'attr' => array(
'placeholder' => 'Heure:Minutes'
)
));
$builder->add('endDate', DateType::class, array(
'label' => 'Date Aller',
'widget' => 'single_text',
'format' => 'dd/MM/yyyy',
'attr' => array(
'no_results_text' => 'JJ/MM/AAAA'
)
));
$builder->add('endTime', TextType::class, array(
'label' => 'Heure Aller',
'attr' => array(
'placeholder' => 'Heure:Minutes'
)
));
In the twig :
{% for reservation in form_mission.reservations %}
I display the reservation Object startDate, startTime, endDate, endTime
{% endfor %}
Everything is well displayed.
Let's try some simple validation with annotations (NotBlank one). Inside Mission class, above reservations private field :
/**
* #Assert\Collection(
* fields = {
* "startDate" = #Assert\NotBlank,
* "startTime" = #Assert\NotBlank,
* "endDate" = #Assert\NotBlank,
* "endTime" = #Assert\NotBlank
* },
* allowMissingFields = true
* )
*/
private $reservations
Inside the reservations collection of my mission object, I have six Reservation Object.
I make sure that one the startDate of one of the Reservation Object is null.
Validating the form, no error appears but inside the development bar, I have 6 errors. One for each Reservation and it is 6 times the same error :
This field was not expected. 0 Caused by: ConstraintViolation {#1358 ▶}
This field was not expected. 1 Caused by: ConstraintViolation {#1370 ▶}
This field was not expected. 2 Caused by: ConstraintViolation {#5597 ▶}
This field was not expected. 3 Caused by: ConstraintViolation {#1371 ▶}
This field was not expected. 4 Caused by: ConstraintViolation {#1378 ▶}
This field was not expected. 5 Caused by: ConstraintViolation {#1382 ▶}
Inside of the ConstraintViolation I have this :
root: Form {#3185 …}
path: "data.reservations[0]"
value: Reservation {#3054 ▶}
So the errors occuring have nothing to do with the one expected, and I dont understand the errors I have.
Maybe fields property expects the keys of the collection (0, 1, 2, 3, 4, 5). So I dont understand how to validate my startDate field.
I replaced my annotation like this :
/**
* #Assert\Collection(
* fields = {
* "3" = #AssertPersonnelNotBlank,
* "4" = #AssertPersonnelNotBlank,
* "5" = #AssertPersonnelNotBlank,
* },
* allowMissingFields = true,
* allowExtraFields = true
* )
*/
The value inside my custom validator is a Reservation Object:
Conclusion : in the custom validator, you check all of the fields that should not be blank and return a single message
I dont know how to display the error in the twig
Obviously there is one display for any error in the collection. No way to show the error on the object of the collection ?
Your collection is array of classes, so you need to add validation rules to Reservation class, for example
class Reservation {
/**
* #Assert\NotBlank
*/
private startDate;
/**
* #Assert\NotBlank
*/
private startTime;
/**
* #Assert\NotBlank
*/
private endDate;
/**
* #Assert\NotBlank
*/
private endtime;
.....
}
And then in your parent entity add #Assert\Valid to collection field
/**
* #Assert\Valid
*/
private $reservations

symfony2 Entity Object vs. integer crashes

I have defined a entity like :
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client_id;
.....
public function setClientId($clientId = null)
{
$this->client_id = $clientId;
return $this;
}
There are two controllers with which I can create a new db entry. the first one is "admin-only" where the admin can create a db entry with a client id of his choice:
->add('client_id', 'entity', array(
'data_class' => null,
'attr' => array(
'class' => 'selectstyle'),
'class' => 'PrUserBundle:Client',
'property' => 'name',
'required' => true,
'label' => 'staff.location',
'empty_value' => 'admin.customer_name',
'empty_data' => null
)
)
......
// Handling the form
$em = $this->getDoctrine()->getManager();
$saniType->setName($form->get('name')->getData());
$saniType->setClientId($form->get('client_id')->getData());
$saniType->setCreated(new \DateTime(date('Y-m-d H:m:s')));
$saniType->setCreatedBy($user->getUsername());
$em->persist($saniType);
$em->flush();
The second one is for the client itself, where he's not able to set a different client id. Therefor I just removed the form field "client_id" and replace it by the users->getClientId():
$saniType->setSpecialClientId($form->get('client_id')->getData());
When I add an entry as admin, it work fine. If I try to add one as "client", it crashes with following error message
Warning: spl_object_hash() expects parameter 1 to be object, integer given in /var/www/symfony/webprojekt/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1389
I'm a newby in symfony, so I haven't got the strength to figure out what happens. I only knew that in the first case, admin will submit a object (entity). In the second (client) case, I set an integer as I get one by
$user = $this->container->get('security.context')->getToken()->getUser();
Is there a solution for it so that I can handle entity object AND given integers like it comes when the client add an entry?
You should change you relation defnition to:
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
.....
public function setClient($client = null)
{
$this->client = $client;
return $this;
}
Then in form:
->add('client', 'entity', array(
'data_class' => null,
'attr' => array('class' => 'selectstyle'),
'class' => 'PrUserBundle:Client',
'property' => 'name',
'required' => true,
'label' => 'staff.location',
'empty_value' => 'admin.customer_name',
'empty_data' => null
)
)
Handling the form:
form = $this->createForm(new SaniType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('some_success'));
}
More about handling form: http://symfony.com/doc/current/book/forms.html#handling-form-submissions
Also worth nothing:
for auto update properties like createdBy / updatedBy i would recommend you to use Doctrine Extension: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md
You don't have to know the client id. You have to set the Client himself.
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
.....
public function setClient(\Pr\UserBundle\Entity\Client $client = null)
{
$this->client = $client;
return $this;
}
I found a Solution:
Symfony2: spl_object_hash() expects parameter 1 to be object, string given in Doctrine
It's a workaround but it's ok for the moment.

Symfony2 predefined parameters

I find some predefined parameters in Symfony2 configuration files, ie. %kernel.root_dir%, %kernel.debug%.
Is there a comprehensive list of these somewhere?
They're under Symfony\Component\HttpKernel\Kernel.php;
/**
* Returns the kernel parameters.
*
* #return array An array of kernel parameters
*/
protected function getKernelParameters()
{
$bundles = array();
foreach ($this->bundles as $name => $bundle) {
$bundles[$name] = get_class($bundle);
}
return array_merge(
array(
'kernel.root_dir' => $this->rootDir,
'kernel.environment' => $this->environment,
'kernel.debug' => $this->debug,
'kernel.name' => $this->name,
'kernel.cache_dir' => $this->getCacheDir(),
'kernel.logs_dir' => $this->getLogDir(),
'kernel.bundles' => $bundles,
'kernel.charset' => $this->getCharset(),
'kernel.container_class' => $this->getContainerClass(),
),
$this->getEnvParameters()
);
}
You can also see them in app/cache/dev/appDevDebugProjectContainer.php:getDefaultParameters() (it's at the end of the file), along with all the other parameters available to your application.
/**
* Gets the default parameters.
*
* #return array An array of the default parameters
*/
protected function getDefaultParameters()
{
return array(
'kernel.root_dir' => $this->targetDirs[2],
'kernel.environment' => 'dev',
'kernel.debug' => true,
'kernel.name' => 'app',
'kernel.cache_dir' => __DIR__,
'kernel.logs_dir' => ($this->targetDirs[2].'/logs'),
...
);
}

Resources