🧩 Formulario Symfony en modulo PS 1.7+ — patron moderno
Actualizado: 2025-01-15
#Cuando usar Symfony Forms
Usa Symfony Forms cuando: (1) desarrollas para PS 1.7.7+ o 8.x, (2) quieres un formulario en el BO moderno con validacion server-side, (3) necesitas integrar con el sistema de tabs/configuracion de Symfony. Para modulos que deben ser compatibles con PS 1.6, sigue usando HelperForm.
#Estructura del modulo
Arbol de archivos para Symfony Forms
text
modules/ecom_settings/
├── ecom_settings.php # Modulo principal
├── config/
│ ├── routes.yml # Rutas Symfony
│ └── services.yml # Servicios DI
├── src/
│ ├── Controller/
│ │ └── Admin/
│ │ └── SettingsController.php # Controller Symfony
│ ├── Form/
│ │ ├── Type/
│ │ │ └── SettingsFormType.php # FormType
│ │ └── DataProvider/
│ │ └── SettingsFormDataProvider.php
│ └── Form/
│ └── Handler/
│ └── SettingsFormHandler.php # (opcional, si no usas DataProvider)
└── views/
└── templates/
└── admin/
└── settings.html.twig # Template del formulario
#FormType — definir los campos
src/Form/Type/SettingsFormType.php
php
<?php
namespace EcomSettings\Form\Type;
use PrestaShopBundle\Form\Admin\Type\SwitchType;
use PrestaShopBundle\Form\Admin\Type\TranslateType;
use PrestaShopBundle\Form\Admin\Type\TranslatableType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\ColorType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
class SettingsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// Switch (boolean) — usa el tipo PS nativo
->add('enabled', SwitchType::class, [
'label' => 'Activar modulo',
'required' => false,
])
// Texto simple
->add('title', TextType::class, [
'label' => 'Titulo del banner',
'required' => true,
'attr' => [
'placeholder' => 'Ej: Envio gratis +50 EUR',
'maxlength' => 255,
],
'help' => 'Texto que se muestra en el banner superior',
])
// Campo multilang (traducible)
->add('description', TranslatableType::class, [
'label' => 'Descripcion',
'type' => TextareaType::class,
'required' => false,
'options' => [
'attr' => ['rows' => 4],
],
])
// Select
->add('position', ChoiceType::class, [
'label' => 'Posicion del banner',
'choices' => [
'Arriba de la pagina' => 'top',
'Debajo del header' => 'below_header',
'Encima del footer' => 'above_footer',
],
'required' => true,
])
// Color picker
->add('background_color', ColorType::class, [
'label' => 'Color de fondo',
'required' => true,
])
->add('text_color', ColorType::class, [
'label' => 'Color del texto',
'required' => true,
])
// Numero
->add('display_delay', IntegerType::class, [
'label' => 'Retraso antes de mostrar (ms)',
'required' => false,
'attr' => ['min' => 0, 'max' => 10000],
'help' => '0 = mostrar inmediatamente',
])
;
}
}
#FormDataProvider — leer/escribir datos
src/Form/DataProvider/SettingsFormDataProvider.php
php
<?php
namespace EcomSettings\Form\DataProvider;
use PrestaShop\PrestaShop\Core\Form\FormDataProviderInterface;
use Configuration;
class SettingsFormDataProvider implements FormDataProviderInterface
{
/**
* Leer datos actuales para rellenar el formulario
*/
public function getData(): array
{
return [
'enabled' => (bool) Configuration::get('ECOM_BANNER_ENABLED'),
'title' => Configuration::get('ECOM_BANNER_TITLE') ?: '',
'description' => $this->getMultilangValue('ECOM_BANNER_DESC'),
'position' => Configuration::get('ECOM_BANNER_POSITION') ?: 'top',
'background_color' => Configuration::get('ECOM_BANNER_BG_COLOR') ?: '#2fb5d2',
'text_color' => Configuration::get('ECOM_BANNER_TXT_COLOR') ?: '#ffffff',
'display_delay' => (int) Configuration::get('ECOM_BANNER_DELAY'),
];
}
/**
* Guardar datos del formulario
*/
public function setData(array $data): array
{
$errors = [];
// Validaciones custom
if (empty($data['title']) && $data['enabled']) {
$errors[] = 'El titulo es obligatorio cuando el modulo esta activado.';
}
if (empty($errors)) {
Configuration::updateValue('ECOM_BANNER_ENABLED', (int) $data['enabled']);
Configuration::updateValue('ECOM_BANNER_TITLE', $data['title']);
Configuration::updateValue('ECOM_BANNER_POSITION', $data['position']);
Configuration::updateValue('ECOM_BANNER_BG_COLOR', $data['background_color']);
Configuration::updateValue('ECOM_BANNER_TXT_COLOR', $data['text_color']);
Configuration::updateValue('ECOM_BANNER_DELAY', (int) $data['display_delay']);
// Guardar campo multilang
if (isset($data['description'])) {
$this->setMultilangValue('ECOM_BANNER_DESC', $data['description']);
}
}
return $errors;
}
protected function getMultilangValue(string $key): array
{
$result = [];
foreach (\Language::getLanguages(true) as $lang) {
$result[$lang['id_lang']] = Configuration::get($key, $lang['id_lang']) ?: '';
}
return $result;
}
protected function setMultilangValue(string $key, array $values): void
{
foreach ($values as $idLang => $value) {
Configuration::updateValue($key, $value, false, 0, 0);
// Para multilang con Configuration hay que usar el truco:
Configuration::updateValue($key, [$idLang => $value]);
}
}
}
#services.yml — registrar servicios
config/services.yml
yaml
services:
_defaults:
public: true
# FormType
ecom_settings.form.type.settings:
class: 'EcomSettings\Form\Type\SettingsFormType'
tags:
- { name: 'form.type' }
# DataProvider
ecom_settings.form.data_provider.settings:
class: 'EcomSettings\Form\DataProvider\SettingsFormDataProvider'
# FormHandler — usa el FormHandler generico de PS
ecom_settings.form.handler.settings:
class: 'PrestaShop\PrestaShop\Core\Form\Handler'
arguments:
- '@form.factory'
- '@prestashop.core.hook.dispatcher'
- '@ecom_settings.form.data_provider.settings'
- 'EcomSettings\Form\Type\SettingsFormType'
- 'EcomSettingsConfiguration' # Hook name suffix
# Controller
ecom_settings.controller.admin.settings:
class: 'EcomSettings\Controller\Admin\SettingsController'
arguments:
- '@ecom_settings.form.handler.settings'
tags:
- { name: 'controller.service_arguments' }
#Admin Controller Symfony
src/Controller/Admin/SettingsController.php
php
<?php
namespace EcomSettings\Controller\Admin;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class SettingsController extends FrameworkBundleAdminController
{
public function indexAction(Request $request): Response
{
/** @var \PrestaShop\PrestaShop\Core\Form\Handler $formHandler */
$formHandler = $this->get('ecom_settings.form.handler.settings');
$form = $formHandler->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$errors = $formHandler->save($form->getData());
if (empty($errors)) {
$this->addFlash('success', $this->trans(
'Settings updated successfully.', 'Admin.Notifications.Success'
));
} else {
foreach ($errors as $error) {
$this->addFlash('error', $error);
}
}
return $this->redirectToRoute('ecom_settings_admin');
}
return $this->render(
'@Modules/ecom_settings/views/templates/admin/settings.html.twig',
[
'settingsForm' => $form->createView(),
'help_link' => false,
'enableSidebar' => true,
'layoutTitle' => 'Ecom Settings',
]
);
}
}
#Template Twig
views/templates/admin/settings.html.twig
twig
{% extends '@PrestaShop/Admin/layout.html.twig' %}
{% block content %}
{{ form_start(settingsForm) }}
<div class="card">
<div class="card-header">
<i class="material-icons">settings</i>
{{ 'Configuracion del banner'|trans({}, 'Modules.Ecomsettings.Admin') }}
</div>
<div class="card-body">
<div class="form-wrapper">
{{ form_row(settingsForm.enabled) }}
{{ form_row(settingsForm.title) }}
{{ form_row(settingsForm.description) }}
{{ form_row(settingsForm.position) }}
<div class="row">
<div class="col-md-6">
{{ form_row(settingsForm.background_color) }}
</div>
<div class="col-md-6">
{{ form_row(settingsForm.text_color) }}
</div>
</div>
{{ form_row(settingsForm.display_delay) }}
</div>
</div>
<div class="card-footer">
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary">
<i class="material-icons">save</i>
{{ 'Guardar'|trans({}, 'Admin.Actions') }}
</button>
</div>
</div>
</div>
{{ form_end(settingsForm) }}
{% endblock %}
#Routing YAML
config/routes.yml
yaml
ecom_settings_admin:
path: /ecom-settings
methods: [GET, POST]
defaults:
_controller: 'ecom_settings.controller.admin.settings::indexAction'
_legacy_controller: AdminEcomSettings
_legacy_link: AdminEcomSettings
Compatibilidad legacy
Las claves _legacy_controller y _legacy_link permiten que el sistema de permisos legacy de PS funcione con tu controller Symfony. Asi el admin puede gestionar permisos desde Empleados > Perfiles.
Descargar en Markdown
Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.