Reputation: 4813
It's amazing that I can't find a solution for something that seems so straight forward.
My website has a simple settings page. All settings are stored in a simple table:
| id | name | label | type | value | is_user_created | is_editable | edit_date | original_name | category | file | subcategory |
+----+---------------------+---------------------------------------------------+--------+-----------------------+-----------------+-------------+---------------------+---------------+------------------+----------------------+-------------+
| 21 | index_header_large | Large header for index page | bool | true | 0 | 1 | 2018-09-17 13:22:20 | | Layout | | Heading |
| 25 | website_title | Short title | string | My website | 0 | 1 | 2018-09-17 13:22:20 | | Details website | | |
| 26 | website_owner | Name of the owner | string | Not specified | 0 | 1 | 2018-09-17 13:22:20 | | Gegevens website | | |
+----+---------------------+---------------------------------------------------+--------+-----------------------+-----------------+-------------+---------------------+---------------+------------------+----------------------+-------------+
The settings table has a column called type
. I use this to generate a FormType
that has the correct fields / asserts for the type of value the form will have to render.
They all look very similar, this type of for a string type:
class SettingsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
/* @var $entity \App\Entity\Setting */
$entity=$builder->getData();
$builder
->add('value', TextType::class, array(
'label' => $entity->getLabel(),
'trim' => true,
'translation_domain' => 'app'
));
;
}
}
In the controller I fetch the settings and iterate them to create the forms
$settings = $repSettings->findAllOrderedByCategory();
$settingForms = [];
/* @var $setting Setting */
foreach ($settings as $setting) {
if ($setting->getType() === 'bool') {
array_push($settingForms, $this->createForm(SettingsBoolType::class, $setting, array(
'action' => $this->generateUrl(
'admin_set_setting_value',
array(
"_locale" => $request->getLocale(),
"_id" => $setting->getId()
)
)
))->createView());
} else if ($setting->getType() === 'file') {
array_push($settingForms, $this->createForm(SettingsFileType::class, $setting, array(
'action' => $this->generateUrl(
'admin_set_setting_file',
array(
"_locale" => $request->getLocale(),
"_id" => $setting->getId())
)
))->createView());
} else {
array_push($settingForms, $this->createForm(SettingsType::class, $setting, array(
'action' => $this->generateUrl(
'admin_set_setting_value',
array(
"_locale" => $request->getLocale(),
"_id" => $setting->getId())
)
))->createView());
}
}
return $this->render('admin/manage_settings.html.twig', array_merge(
array(
'settingForms' => $settingForms,
)
));
Good so far, the forms render and work because each form has a unique ID set in the action url. But this method has some issues.
Wrong values submitted: It seems to work for string value forms, but sometimes a boolean value form submits a value for the wrong setting.
Duplicate IDs: The block_prefixes
for each form is set to the class name (e.g settings_value).
I know this is not the way you should to this, but I'm clueless as to how I should. A collection maybe? Should I create a super class in order to render a CollectionType
? In that case I need to know how I can apply my own layout, because I render headers for every category
and subcategory
column in the settings table.
A push in the right direction would be appreciated :)
Upvotes: 0
Views: 2191
Reputation: 2319
I ran into the same problem today. Here's how I solved it :
You will have to adapt your code to fill in your application, the following code is just a demo I made about how things works.
Do not hesitate to ask in comments for questions
The $data
is just an array with some Setting
objects.
To get faster, I used symfony built-in types for the Settings::type property. You're free to code an adapter in the ExtendedFieldCollectionType
<?php
namespace App\Controller;
use App\Model\Setting;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class DemoController extends AbstractController
{
/**
* @Route(name="demo_demo", path="/demo")
* @Template()
* @param Request $request
* @return array
*/
public function demo(Request $request)
{
$data = [
new Setting('count', NumberType::class, 0.5),
new Setting('text', TextType::class,'sample text'),
new Setting('date', DateType::class, new \DateTime())
];
$form = $this->createForm('App\Form\ExtendedFieldCollectionType', $data);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// ...
}
return [
'form' => $form->createView()
];
}
}
And the form type:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ExtendedFieldCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
foreach ($data as $name => $value) {
$form->add($value->id, $value->type, [
'property_path' => '[' . $name . '].value', // the value property is my setting value
]);
}
});
}
}
And the model I used
<?php
namespace App\Model;
class Setting
{
public $id;
public $type;
public $value;
/**
* ProjectField constructor.
* @param $id
* @param $type
* @param $value
*/
public function __construct($id, $type, $value)
{
$this->id = $id;
$this->type = $type;
$this->value = $value;
}
}
Upvotes: 2