Use formBuilder in a foreach to generate multiple fields - symfony

I'm developing a project with Symfony 5, currently busy with an import of data from CSV to a database. It's requested to offer the client the possibility to choose, which data from the CSV is corresponding to which field of the database.
For example, I want to import users to the database.
The array of fields from the database:
$databaseFields = ['username', 'email', 'lastname', 'firstname'];
The array of headers, frome the CSV:
$headersCsvArray = ['email', 'username', 'lastName'];
Based on this 2 arrays, I need to build a form:
foreach ($databaseFields as $databaseField) {
$builder->add('extraFields', ChoiceType::class,
[
'label' => $databaseField,
'placeholder' => 'Choose a column from the excel file',
'choices' => $headersCsvArray,
'multiple' => false,
'expanded' => false,
'required' => false
]
);
}
To be clear, I need an input for each field of the User entity, with a dropdown with every header from the CSV.
This form is link to an ImportUserFormModel, with basically nothing for the moment:
class ImportUserFormModel
{
public $extraFields;
}
The result I need when I do $form->getData('extraData'):
$importDatas = [
'username' =>
[
'username' => true,
'emails' => false,
'lastName' => false
],
'email' =>
[
'username' => false,
'emails' => true,
'lastName' => false
],
'lastname' =>
[
'username' => false,
'emails' => false,
'lastName' => true
],
'firstname' =>
[
'username' => false,
'emails' => false,
'lastName' => true
],
];
Where the TRUE value is the one choose from the select dropdown.
Here is the result I actually have:
So I'm asking myself: Is it possible to use the foreach here?

Ok if you want it dynamic this could be a solution.
Create a Model for your extra field too, like
class ExtraField {
private $databseField;
private $csvField;
// ...
}
And in your base model use it as a collection
class ImportUserFormModel
{
private $extraFields;
public getFields(): ExtraField[] {
// ...
}
public addField(ExtraField $extraField) {
// ...
}
}
Create a FormType for your ExtraField model and another one for your base model where extraFields is a CollectionType.
Then before the form creation define the fields in your model
$importUserFormModel = new ImportUserFormModel();
foreach ($databaseFields as $databaseField) {
$extraField = new ExtraField();
$extraField->setDatabaseField($databaseField);
$importUserFormModel->addExtraField($extraField);
}
$form = $this->createForm(ImportUserFormModelType::class, $importUserFormModel);
// ....
The thing i'm not sure about is how to put the name of the database field in the label of the form field but you could template it.

Related

Symfony 6: embedded form Collection with File upload in child form

I have an entity ClientFileAction which is parent to an entity Attachment in OneToMany relation. Attachment holds not only file path, but also information about files, like title, upload date, etc.
Attachment:
#[ORM\Column(type: 'string', length: 255)]
private $title;
#[ORM\Column(type: 'datetime')]
private $uploaded;
#[ORM\Column(type: 'string', length: 255)]
private $filePath;
When it comes to create a form type AttachmentType and upload files one to one, there is no problem:
AttachmentType:
$builder
->add('title', null, ['label' => 'Title', 'required' => true])
->add('attachmentFile', FileType::class, [
'label' => 'File',
'mapped' => false,
'required' => true,
'constraints' => [
new File([
'maxSize' => '1024k',
])
],
]);
In the controller I just get uploaded file with $attachmentFile = $form->get('attachmentFile')->getData(); and then proceed to the usual UploadedFile::move() stuff.
PROBLEM: EMBED AttachmentType IN PARENT FORM
But when I try to upload multiple attachments (not only files, but attachments with a title field), the uploaded file field seems to be unreachable.
ClientFileActionType:
$builder
->add('description', null, ['label' => 'Description', 'required' => true])
->add('attachments', CollectionType::class, ['label' => false,
'allow_add' => true,
'by_reference' => false,
'entry_type' => AttachmentType::class,
'entry_options' => ['label' => false],
]);
When I embed the AttachmentType as Collection inside ClientFileActionType, then, in the controller I don't find a way to get uploaded files:
$attachments = $form->get('attachments')->getData();
$attachments is an array of Attachment, and, as attachmentFile is not a mapped field, it dissapeared on the $form->handleRequest($request);.
I need a way to get unmapped attachmentFile fields of the child forms someway, something like:
$attachmentFiles = $form->get('attachments.attachmentFile')->getData();
That throws an error. Is there a correct way to do that?
I found the correct way to do it as I was typing the question.
The uploaded files are in the Request object, so a correct approach for this scenario of file upload management could be:
if ($form->isSubmitted() && $form->isValid())
{
$i=0;
$files = $request->files->all('client_file_action')['attachments'];
foreach ($files as $file)
{
$attachmentFile = $file['attachmentFile'];
$originalFilename = pathinfo($attachmentFile->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename.'-' . uniqid() . '.' .$attachmentFile->guessExtension();
$attachmentFile->move('path/to/folder',$newFilename);
$attachment = $clientFileAction->findAttachment($i);
if ($attachment != null)
$attachment->setFilePath('path/to/folder/' . $newFilename);
$i++;
}
$clientFileActionRepository->add($clientFileAction, true);
}

Symfony - Error with the data parameter on a form

Context of the problem :
I created a symfony form.
Each tool has a collection of modules.
The user has a collection of modules of any tool.
What I want :
I want for each tool there are checkboxes corresponding to the tool's modules. The module checkboxes that the user owns are checked.
([] = checkbox)
Tool1 : []Module1 [x]Module2 [x]Module3
Tool2 : []Module4 [x]Module5
Tool3 : [x]Module6 []Module7
What I currently have:
For each tool, there are checkboxes corresponding to the tool's modules. But I have a problem to tick the checkboxes of user's modules. I get an error on the data parameter.
The form field :
$user = $options['user'];
$tools = $options['tools'];
foreach ($tools as $tool) {
$name = 'profile_'.str_replace(array('-', ' ', '.'), '', $tool->getLibelle());
$builder
->add($name, ChoiceType::class, [
'label' => $tool->getLibelle(),
'choices' => $tool->getModules(),
'choice_value' => 'id',
'choice_label' => function (?Module $module) {
return $module ? $module->getName() : '';
},
'data'=> $user->getModules(), // ERROR HERE
'expanded' => true,
'multiple' => true,
'mapped'=>false
])
;
}
[...]
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'user'=> null,
'category'=> null,
'tools'=> null,
]);
}
The error :
My question :
Why do I have this error? How can I use the data parameter correctly to achieve the expected result?
You are on the good way, try to dump what is $user->getModules() returning, it has to be an array. May be is not returning an array, check te relation.
I did a little test and it works perfectly.
$name = 'name_field';
$builder->add($name,ChoiceType::class, array(
'choices' => array('Yes', 'No'),
'data' => array('Yes', false),
'mapped' => false,
'expanded' => true,
'multiple' => true
));
Here the solution :
Seems to me that $user->getModules() returns a collection. I managed to find another solution and that works (I changed the type of the field to EntityType)
foreach ($tools as $tool) {
$name = 'acces_'.str_replace(array('-', ' ', '.'), '', $tool->getLibelle());
$builder
->add($name, EntityType::class, [
'class'=> Module::class,
'label' => $tool->getLibelle(),
'data' => $user->getModules(),
'choices'=> $tool->getModules(),
'choice_value' => 'id',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'required' => true,
'mapped'=>false,
])
;
}
ChoiceType: data parameter need array
EntityType: data parameter need collection
Thanks for the help !

Cakephp 3 - Query Fixture in IntegrationTestTrait

I am having issues querying a loaded Fixture in my TestCase for an IntegrationTestTrait. I want to verify that a record already exist inside of a Fixture, then insert a duplicate record and verify that there is still only 1 record in the database.
During my test case initialization, I set the session variable for authentication.
public function setUp() {
parent::setUp();
$this->loadFixtures(
'Students', 'Users');
// Configure Authentication
$this->session([
'Auth' => [
'User' => [
'id' => 21896,
'institution_id' => 1,
'student_id' => null,
'contact_id' => 91,
'email' => 'AuthenticatedEmail#school.edu',
'role' => 'DSP',
'is_admin' => false
]
]
]);
// Load Tables
$this->Students = TableRegistry::getTableLocator()->get('Students');
}
In my Test Case, I check to see if the Database contains a record, then submit a POST request then test to see if the record did not insert.
public function testAddStudentSuccess() {
$data = [
'institution_id' => 1,
'contact_id' => null,
'id_number' => '200XYZ',
'last_name' => 'Trimor',
'first_name' => 'Paul',
'email' => '1_test#email.com'
];
// Test Pre-condition
$query = $this->Students->find('all')->where([
'id_number' => $data['id_number']
]);
$this->assertEquals(1, $query->count());
$this->post('students/add', $data);
// Test Post-condition
$this->assertResponseSuccess();
$query = $this->Students->find('all')->where([
'id_number' => $data['id_number']
]);
$this->assertEquals(1, $query->count());
}
However, when I run the Test Case, I get the following error:
Notice Error: Undefined variable: _SESSION in/var/www/html/samusg/src/Model/Table/StudentsTable.php, line 206]
A couple things:
The last assertion works! After $this->post('students/add', $data) is submitted, the $query is populated with data.
The first assertion does not work. I debug the Fixture before the $this->post() is called and it returns empty.
In the Test Table, there is a test for $_SESSION variable, which is what line 206 referring to.
Long Story short: The Fixture is not populated with data during the start of the Test Case, but once the integration runs then the Fixture magically contains all the data. I get $_SESSION errors, but I already set the session in the setUp(), so I'm lost.
I greatly appreciate any help. Thank you
I was able to by pass this message by setting the $_SESSION super global directly on my Test:
public function setUp() {
parent::setUp();
$this->loadFixtures(
'Students', 'Users');
// Configure Authentication
$this->session([
'Auth' => [
'User' => [
'id' => 21896,
'institution_id' => 1,
'student_id' => null,
'contact_id' => 91,
'email' => 'AuthenticatedEmail#school.edu',
'role' => 'DSP',
'is_admin' => false
]
]
]);
$_SESSION = [
'Auth' => [
'User' => [
'id' => 21896,
'institution_id' => 1,
'student_id' => null,
'contact_id' => 91,
'email' => 'AuthenticatedEmail#school.edu',
'role' => 'DSP',
'is_admin' => false
]
]
];

How to make a DateIntervalType field in Symfony form not required

I have a DateIntervalType field:
$builder
->add('effort', DateIntervalType::class, [
'label' => false,
'required' => false,
'widget' => 'integer',
'input' => 'dateinterval',
'with_years' => false,
'with_months' => false,
'with_days' => false,
'with_hours' => true,
'with_minutes' => true,
]);
If I submit the form I get the error message that it is invalid if I leave the hours and minutes empty. I have no constraints for the entity attribute (no #Assert\NotBlank() or anything like that) and it is nullable:
class Template
{
/**
* #ORM\Column(type="dateinterval", nullable=true)
*/
private $effort;
// ...
}
The submitted values are:
"effort" => [▼
"hours" => ""
"minutes" => ""
]
I want to submit the form without values and without getting this error.
You do not get errors if you change widget from integer to text:
$builder
->add('effort', DateIntervalType::class, [
// ...
'widget' => 'text',
// ...
]);

Cakephp 3.x Saving multiple tables

In my cakephp (3.3) project, How to save multiple tables in a single save. My association is as follows, also I am giving my code here.
Table1 : users
In class UsersTable extends Table
$this->hasOne('ProfileDetails', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
Table2 : profile_details
In class ProfileDetailsTable extends Table
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
What should be the data format to save multiple tables?
In my template I have added the following , for now I have used only few fields
$this->Form->input('User.username',['type' => 'text','class'=>'form-control']) ?>
$this->Form->input('User.password',['type' => 'password','class'=>'form-control']) ?>
$this->Form->input('User.email',['type' => 'text','class'=>'form-control']) ?>
$this->Form->input('ProfileDetail.first_name',['type' => 'text','class'=>'form-control']) ?>
In the controller before saving i have debuged which gives result as follows
debug($this->request->data);
$user = $this->Users->patchEntity($user, $this->request->data);
debug($user);
exit;
$this->Users->save($user)
Result of debug
\src\Controller\UsersController.php (line 39)
[
'User' => [
'username' => 'Tester',
'password' => '43434324',
'email' => 'test#gmail.com',
'role' => 'admin'
],
'ProfileDetail' => [
'first_name' => 'Tester'
]
]
\src\Controller\UsersController.php (line 41)
object(App\Model\Entity\User) {
'User' => [
'username' => 'Tester',
'password' => '43434324',
'email' => 'test#gmail.com',
'role' => 'admin'
],
'ProfileDetail' => [
'first_name' => 'Tester'
],
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'User' => true,
'ProfileDetail' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [
'email' => [
'_required' => 'This field is required'
]
],
'[invalid]' => [],
'[repository]' => 'Users'
}
Could you please suggest me how to save this data in multiple tables with single save with validation?
Looking at your debug, there is an error saying the email field is required. Maybe that is your problem..
If still not working , try this:
replace User by users
and ProjectDetail by project_details
In your form. And also remove joinType from following:
$this->hasOne('ProfileDetails', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);

Resources