Módulos personalizados en PrestaShop 8: Desarrollo con Symfony 6
Introducción a los módulos personalizados en PrestaShop 8
PrestaShop 8 marca un hito importante en la evolución de la plataforma de ecommerce open source más popular del mundo. Con la adopción de Symfony 6 como framework central, los desarrolladores tienen ahora un ecosistema más robusto, moderno y flexible para crear módulos personalizados. Esta combinación no solo mejora el rendimiento y la seguridad, sino que también abre la puerta a prácticas de desarrollo más limpias, reutilizables y alineadas con los estándares actuales del desarrollo web.
El desarrollo de módulos PrestaShop ha pasado de ser una tarea a menudo tediosa y propensa a errores a una experiencia más estructurada, gracias a la integración de componentes Symfony como el Routing, Doctrine, Twig y el potente sistema de inyección de dependencias. En este artículo, exploraremos en profundidad cómo aprovechar Symfony 6 para crear módulos personalizados en PrestaShop 8, desde la estructura básica hasta técnicas avanzadas de personalización.
Estructura de un módulo moderno en PrestaShop 8
Para comenzar, es fundamental entender la estructura de directorios que PrestaShop 8 espera de un módulo personalizado. Aunque la base sigue siendo similar a versiones anteriores, la integración con Symfony 6 introduce cambios significativos en la organización del código.
Directorio raíz del módulo
mimodulo/
├── config/
│ ├── routes.yaml
│ └── services.yaml
├── controllers/
│ ├── front/
│ └── admin/
├── src/
│ ├── Entity/
│ ├── Repository/
│ ├── Form/
│ └── Install/
├── views/
│ ├── templates/
│ └── assets/
├── translations/
├── vendor/
├── mimodulo.php
├── logo.png
└── index.php
[TIP] El directorio
src/es el núcleo de la lógica de negocio. Aquí es donde realmente aprovechamos Symfony 6, con entidades Doctrine, formularios modernos y servicios inyectables.
El archivo principal del módulo
El archivo mimodulo.php sigue siendo el punto de entrada obligatorio, pero ahora debe declarar correctamente el namespace y extender de Module. Además, es recomendable implementar interfaces para funciones específicas.
<?php
namespace MyNamespace\Mimodulo;
if (!defined('_PS_VERSION_')) {
exit;
}
class Mimodulo extends \Module
{
public function __construct()
{
$this->name = 'mimodulo';
$this->tab = 'front_office_features';
$this->version = '1.0.0';
$this->author = 'TuNombre';
$this->need_instance = 0;
$this->ps_versions_compliancy = [
'min' => '8.0.0',
'max' => _PS_VERSION_
];
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->l('Módulo Personalizado Symfony');
$this->description = $this->l('Descripción del módulo con Symfony 6');
$this->confirmUninstall = $this->l('¿Estás seguro de desinstalar?');
}
public function install()
{
return parent::install()
&& $this->installTab()
&& $this->installDatabase();
}
private function installTab()
{
// Código para instalar pestañas de administración
}
private function installDatabase()
{
// Código para crear tablas usando Doctrine
}
}
Configuración de Symfony 6 en el módulo
La verdadera potencia de PrestaShop 8 radica en su capacidad para cargar configuraciones Symfony desde el módulo. Esto se logra mediante los archivos config/services.yaml y config/routes.yaml.
Servicios y dependecias
En config/services.yaml definimos los servicios de nuestro módulo, aprovechando la inyección de dependencias.
services:
_defaults:
autowire: true
autoconfigure: true
public: false
MyNamespace\Mimodulo\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests}'
MyNamespace\Mimodulo\Controller\:
resource: '../controllers/*'
public: true
tags: ['controller.service_arguments']
[INFO] El uso de
autowire: truepermite que Symfony resuelva automáticamente las dependencias de tus clases, reduciendo drásticamente el código boilerplate.
Rutas y controladores
Las rutas se definen en config/routes.yaml y pueden apuntar tanto a controladores de front como de back office.
mimodulo_front_list:
path: /modulo/mimodulo/list
methods: [GET]
defaults:
_controller: 'MyNamespace\Mimodulo\Controller\Front\ListController::index'
_legacy_controller: 'AdminMimodulo'
_legacy_link: 'AdminMimodulo'
mimodulo_admin_config:
path: /modulo/mimodulo/config
methods: [GET, POST]
defaults:
_controller: 'MyNamespace\Mimodulo\Controller\Admin\ConfigController::index'
_legacy_controller: 'AdminMimoduloConfig'
Creación de entidades con Doctrine
Uno de los cambios más significativos es la posibilidad de usar Doctrine ORM para manejar la persistencia de datos. Esto reemplaza el antiguo sistema de Db::getInstance() y proporciona una capa de abstracción mucho más potente.
Definición de una entidad
<?php
namespace MyNamespace\Mimodulo\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table()
* @ORM\Entity(repositoryClass="MyNamespace\Mimodulo\Repository\ProductoRepository")
*/
class Producto
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id_producto", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="nombre", type="string", length=255)
*/
private $nombre;
/**
* @var float
*
* @ORM\Column(name="precio", type="decimal", precision=10, scale=2)
*/
private $precio;
// Getters y setters...
}
Repositorio personalizado
<?php
namespace MyNamespace\Mimodulo\Repository;
use Doctrine\ORM\EntityRepository;
class ProductoRepository extends EntityRepository
{
public function findProductosActivos()
{
return $this->createQueryBuilder('p')
->where('p.activo = :activo')
->setParameter('activo', true)
->orderBy('p.nombre', 'ASC')
->getQuery()
->getResult();
}
}
Formularios modernos con Symfony Forms
La integración de Symfony Forms en PrestaShop 8 permite crear formularios complejos con validación automática y manejo de errores.
Creación de un formulario
<?php
namespace MyNamespace\Mimodulo\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class ProductoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nombre', TextType::class, [
'label' => 'Nombre del producto',
'required' => true,
'attr' => ['class' => 'form-control']
])
->add('precio', NumberType::class, [
'label' => 'Precio',
'scale' => 2,
'attr' => ['class' => 'form-control']
])
->add('guardar', SubmitType::class, [
'label' => 'Guardar producto',
'attr' => ['class' => 'btn btn-primary']
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'MyNamespace\Mimodulo\Entity\Producto',
]);
}
}
Controladores y Twig para las vistas
Los controladores en Symfony 6 son mucho más expresivos y se integran perfectamente con el motor de plantillas Twig, que PrestaShop 8 ha adoptado como estándar.
Controlador de front office
<?php
namespace MyNamespace\Mimodulo\Controller\Front;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use MyNamespace\Mimodulo\Repository\ProductoRepository;
class ListController
{
private $productoRepository;
public function __construct(ProductoRepository $productoRepository)
{
$this->productoRepository = $productoRepository;
}
public function index(Request $request)
{
$productos = $this->productoRepository->findAll();
return new Response(
$this->render('@Modules/mimodulo/views/templates/front/list.html.twig', [
'productos' => $productos
])
);
}
private function render($template, $parameters)
{
// Método helper para renderizar Twig
// En un controlador real, se inyectaría el motor de Twig
}
}
Plantilla Twig
{# views/templates/front/list.html.twig #}
{% extends 'page.tpl' %}
{% block content %}
<div class="container">
<h1>{{ 'Listado de productos'|trans({}, 'Modules.Mimodulo') }}</h1>
<div class="row">
{% for producto in productos %}
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ producto.nombre }}</h5>
<p class="card-text">{{ 'Precio: %price%'|trans({'%price%': producto.precio|number_format(2, ',', '.')}, 'Modules.Mimodulo') }}</p>
</div>
</div>
</div>
{% else %}
<div class="col-12">
<p>{{ 'No hay productos disponibles'|trans({}, 'Modules.Mimodulo') }}</p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
Hooks y eventos personalizados
Los hooks siguen siendo el mecanismo principal para integrar módulos con el núcleo de PrestaShop, pero ahora pueden ser manejados mediante eventos Symfony.
Registro de hooks
public function install()
{
return parent::install()
&& $this->registerHook('displayHome')
&& $this->registerHook('actionFrontControllerSetMedia');
}
public function hookDisplayHome($params)
{
// Lógica para mostrar contenido en la página principal
return $this->renderTemplate('front/home_block.tpl');
}
public function hookActionFrontControllerSetMedia($params)
{
$this->context->controller->registerStylesheet(
'module-mimodulo-style',
'modules/mimodulo/views/css/style.css',
['media' => 'all', 'priority' => 150]
);
}
Eventos Symfony
PrestaShop 8 también permite disparar y escuchar eventos Symfony, lo que facilita la comunicación entre módulos.
// Disparar un evento
use Symfony\Component\EventDispatcher\GenericEvent;
$this->get('event_dispatcher')->dispatch(
new GenericEvent($producto),
'mimodulo.producto_creado'
);
// Escuchar el evento (en services.yaml)
services:
App\EventListener\ProductoListener:
tags:
- { name: kernel.event_listener, event: mimodulo.producto_creado, method: onProductoCreado }
Migraciones y actualizaciones de base de datos
Con Doctrine, las migraciones se vuelven mucho más manejables. Aunque PrestaShop 8 no incluye DoctrineMigrationsBundle por defecto, podemos implementar un sistema simple.
Sistema de versiones
class Migrator
{
private $db;
private $installedVersion;
public function __construct(\Db $db)
{
$this->db = $db;
$this->installedVersion = $this->getInstalledVersion();
}
public function migrate()
{
$versions = [
'1.0.0' => 'migrateTo100',
'1.1.0' => 'migrateTo110',
];
foreach ($versions as $version => $method) {
if (version_compare($this->installedVersion, $version, '<')) {
$this->$method();
$this->setInstalledVersion($version);
}
}
}
private function migrateTo100()
{
$sql = "CREATE TABLE IF NOT EXISTS `" . _DB_PREFIX_ . "mimodulo_producto` (
`id_producto` int(11) NOT NULL AUTO_INCREMENT,
`nombre` varchar(255) NOT NULL,
`precio` decimal(10,2) NOT NULL,
PRIMARY KEY (`id_producto`)
) ENGINE=" . _MYSQL_ENGINE_ . " DEFAULT CHARSET=utf8;";
return $this->db->execute($sql);
}
}
Buenas prácticas y rendimiento
Para asegurar que tu módulo funcione de manera óptima en PrestaShop 8, considera las siguientes recomendaciones:
- Usa caché de Doctrine: Configura el caché de segundo nivel para consultas frecuentes.
- Minimiza consultas SQL: Aprovecha las relaciones de Doctrine para hacer eager loading cuando sea necesario.
- Implementa lazy loading: Symfony 6 lo soporta nativamente para servicios no críticos.
- Traduce todo: Usa el sistema de traducciones de PrestaShop con archivos XLF.
- Prueba en diferentes versiones: Asegúrate de que tu módulo funcione con PHP 8.1+.
[WARNING] Evita usar funciones obsoletas como
Tools::getValue()directamente. Symfony 6 proporciona el objetoRequestque es mucho más seguro y testeable.
Conclusión
El desarrollo de módulos personalizados en PrestaShop 8 con Symfony 6 representa un salto cualitativo en la plataforma. La combinación de un ORM moderno, formularios flexibles, controladores limpios y un sistema de eventos robusto permite crear extensiones más mantenibles, seguras y escalables.
Aunque la curva de aprendizaje puede ser pronunciada para quienes vienen de versiones anteriores, los beneficios a largo plazo son enormes. La comunidad de PrestaShop está adoptando rápidamente estas nuevas prácticas, y los módulos que no migren a Symfony 6 quedarán obsoletos en las próximas versiones.
Recursos adicionales
- Documentación oficial de PrestaShop 8 para desarrolladores
- Symfony 6: The Fast Track (libro oficial)
- Foro de desarrolladores de PrestaShop
- Repositorios de módulos open source en GitHub
[TIP] Comienza migrando un módulo simple a la nueva estructura. La práctica te dará la confianza para abordar proyectos más complejos.
