Cómo crear un servicio propio para Symfony en PrestaShop

Esta vez toca una entrada algo más técnica en la que vamos a explicar cómo hacer uso de los servicios de Symfony que PrestaShop incorporó en sus versiones 1.7/8. Para ello vamos a crear un pequeño módulo que muestre en el listado de pedidos el peso total del pedido, pero vamos por partes.

¿Qué es Symfony?

Symfony es un framework de PHP, pero ¿Qué es un framework PHP? Un framework PHP son un conjunto de herramientas, que nos permiten crear aplicaciones web modulares basadas en el patrón vista, modelo, controlador. La utilización de un framework nos permite la creación de aplicaciones más seguras, robustas y escalables, pero además nos facilita componentes reutilizables que nos ahorrarán un montón de tiempo en nuestros desarrollos. Algunos de estos componentes son, enrutadores, eventos, logs, gestión de sesiones, contenedor de servicios, etc. Los componentes que nos interesan en este caso son los Contenedores de Servicios.

¿Qué es un servicio en Symfony?

La idea es pensar en un servicio con un objeto de una clase que realiza una tarea concreta y reutilizable, como por ejemplo, llamadas a una API, algún tipo de cálculo, consulta a base de datos, etc.

Estos servicios se registran en el contenedor de servicios de Symfony, el cual se encarga de crearlo y resolver sus dependencias cuando lo necesitamos, el uso de servicios nos permite tener un código más limpio y reutilizable.

PrestaShop y Symfony

A partir de la versión 1.7 PrestaShop incorporó Symfony para mejorar su Backend, a día de hoy siguen incorporando nuevos apartados en Symfony con el lanzamiento de cada versión, conviviendo con el sistema antiguo (legacy).

El uso de Symfony permite un backend más rápido y seguro, además de que permite extender su funcionalidad a través de los servicios propios del core y los que podamos inyectar.

¿Cómo crear e inyectar un servicio con un módulo de PrestaShop?

Lo mejor es verlo con un ejemplo real, así que vamos a presentaros un módulo que lo que haces es crear una nueva columna en el listado de pedidos del backend, que incluye el peso total del pedido. Vamos a ello:

Estructura del módulo

El módulo se va a llamar tprestorderweight y se creará en la carpeta modules siguiente la siguiente estructura:

  • Carpeta modules
    • Carpeta tprestorderweight
      • tprestorderweight.php (archivo principal del módulo)
    • Carpeta config
      • services.yml (registro del servicio)
    • Carpeta src
      • Carpeta Service
        • OrderWeightService.php (lógica del servicio)
    • composer.json

Registrar el servicio

services.yml

services:
  tprestorderweight.order_weight:
    class: Tprest\OrderWeight\Service\OrderWeightService
    public: true
    arguments:
      - '@doctrine.dbal.default_connection'

En este archivo indicamos:

ID del servicio: será un nombre único, en este caso: tprestorderweight.order_weight

Para recuperarlo y poder utilizarlo en un hook se utilizará el siguiente código:

$container = \PrestaShop\PrestaShop\Adapter\SymfonyContainer::getInstance();
$svc = $container->get('tprestorderweight.order_weight');

namespace: Tprest\OrderWeight\Service\OrderWeightService nombre de la clase con namespace que utilizará el servicio.

public: true: permite obtenerlo desde un módulo/hook, de lo contrario no puedes pedirlo utilizando el método get.

arguments: lista de dependencias que el contenedor pasa al constructor del servicio.

En nuestro caso tenemos
public function __construct(private Connection $conn)
así que recibimos una instancia de doctrine.dbal.default_connection que nos proporciona la conexión con la base de datos.

Cada parámetro se separa con un – y será un argumento en el constructor del servicio.

Implementar la lógica del servicio

OrderWeightService.php

// modules/tprestorderweight/src/Service/OrderWeightService.php
namespace Tprest\OrderWeight\Service;

use Doctrine\DBAL\Connection;

class OrderWeightService
{
    public function __construct(private Connection $conn) {}

    public function getOrderWeight(int $orderId): float
    {
        $sql = 'SELECT COALESCE(SUM(od.product_weight * od.product_quantity), 0)
                FROM ' . _DB_PREFIX_ . 'order_detail od
                WHERE od.id_order = :id';

        $val = method_exists($this->conn, 'fetchOne')
            ? $this->conn->fetchOne($sql, ['id' => $orderId])
            : $this->conn->executeQuery($sql, ['id' => $orderId])->fetchColumn(0);

        return round((float) $val, 3);
    }
}

Esta es la lógica del servicio, en el constructor recibimos una conexión a la base de datos y en el método getOrderWeight ejecutamos una consulta que obtiene el peso total de un pedido.

Autoload PSR-4

composer.json

// modules/tprestorderweight/composer.json
{
"name": "tprest/tprestorderweight",
"type": "prestashop-module",
"autoload": { "psr-4": { "Tprest\OrderWeight\": "src/" } }
}

En la raíz del módulo ejecutar: composer dump-autoload

Este composer registra el namespace (PSR-4) para que el core de PrestaShop encuentre la clase sin necesidad de utilizar «require» manuales.

Los campos:

  • name: identificador del paquete.
  • type: se trata de un metadato que indica que se trata de un módulo PrestaShop.
  • autoload: psr-4 mapa de namespaces:
    • Indica que cada clase que empiece por Tprest\OrderWeight\ se aloje en la carpeta src del proyecto
    • Entonces Tprest\OrderWeight\Service\OrderWeightService se cargará desde: modules/tprestorderweight/src/Service/OrderWeightService.php

Archivo del módulo

tprestorderweight.php

<?php
if (!defined('_PS_VERSION_')) exit;

use PrestaShop\PrestaShop\Core\Grid\Column\Type\DataColumn;
use PrestaShop\PrestaShop\Core\Grid\Definition\GridDefinitionInterface;
use PrestaShop\PrestaShop\Core\Grid\Data\GridData;
use PrestaShop\PrestaShop\Core\Grid\Record\RecordCollection;

class Tprestorderweight extends Module
{
    public function __construct()
    {
        $this->name = 'tprestorderweight';
        $this->version = '1.0.1';
        $this->author = 'Labox';
        $this->tab = 'administration';
        $this->bootstrap = true;
        parent::__construct();

        $this->displayName = $this->l('Peso en listado de pedidos (tprest)');
        $this->description = $this->l('Añade una columna con el peso total del pedido al grid de pedidos usando un servicio Symfony.');
    }

    public function install()
    {
        return parent::install()
            && $this->registerHook('actionOrderGridDefinitionModifier')
            && $this->registerHook('actionOrderGridDataModifier'); // ← usa este
    }

    /**
     * Añade la columna "Peso (kg)" al grid de pedidos.
     */
    public function hookActionOrderGridDefinitionModifier(array $params)
    {
        /** @var GridDefinitionInterface $definition */
        $definition = $params['definition'];

        // la clave 'total_weight' debe existir en los records después del presenter modifier
        $definition->getColumns()->addAfter(
            'total_paid_tax_incl',
            (new DataColumn('total_weight'))
                ->setName($this->l('Peso (kg)'))
                ->setOptions(['field' => 'total_weight'])
        );
    }

    public function hookActionOrderGridDataModifier(array $params)
    {
        if (empty($params['data']) || !($params['data'] instanceof GridData)) {
            \PrestaShopLogger::addLog('[tprestorderweight] GridData no presente o tipo no válido en actionOrderGridDataModifier');
            return;
        }

        /** @var GridData $data */
        $data = $params['data'];

        // Servicio desde el contenedor de Symfony (más fiable que $this->get())
        $container = \PrestaShop\PrestaShop\Adapter\SymfonyContainer::getInstance();
        /** @var \Tprest\OrderWeight\Service\OrderWeightService $weightService */
        $weightService = $container->get('tprestorderweight.order_weight');

        // Construimos un nuevo array de records con la clave total_weight añadida
        $modified = [];
        foreach ($data->getRecords() as $record) { // $record es array
            // Ajusta si tu versión/override usa otra clave para el ID
            $idOrder = (int)($record['id_order'] ?? $record['id_order_id'] ?? $record['id'] ?? 0);
            $record['total_weight'] = $idOrder ? $weightService->getOrderWeight($idOrder) : 0.0;

            // Para depurar claves:
            // \PrestaShopLogger::addLog('[tprestorderweight] keys: '.implode(',', array_keys($record)));

            $modified[] = $record;
        }

        // Reemplazamos el GridData completo
        $params['data'] = new GridData(
            new RecordCollection($modified),
            $data->getRecordsTotal(),
            $data->getQuery() // mantiene paginación/filtros actuales
        );
    }

}

Utilizando el hook actionOrderGridDefinitionModifier añadimos una columna al grid del listado de pedidos, en este caso Peso (Kg).

Para inyectar los datos de la nueva columna utilizamos el hook actionOrderGridDataModifier, este hook recibe un GridData, el cual es modificado añadiendo una nueva columna a cada registro con la clave total_weight por pedido que nos devuelve nuestro servicio.

Conclusión

Symfony brinda a PrestaShop una base moderna y flexible. Un servicio no es más que una pieza de lógica reutilizable registrada en el contenedor, la cual puedes declarar en servicies.yml e inyectar en hooks o controladores para cubrir necesidades concretas del Back Office y Front Office con un código más limpio y fácil de mantener.

Este desarrollo ha sido validado en la versión 8.1.6 de PrestaShop. En otras versiones podrían presentarse ligeras variaciones. Si necesitas asistencia con la adaptación, recuerda que cuentas con nuestro servicio de soporte para ayudarte.

Con esto y más ¡Tu tienda online siempre preparada!

Te puede interesar

  • Entrada

    Como hacer un override en PrestaShop

    En ocasiones nos toca modificar archivos nativos de PrestaShop porque necesitamos funcionalidades a medida o porque somos capaces de resolver algún pequeño bug que hemos encontrado en el sistema. El problema es que al realizar estas modificaciones directamente tocamos el Core de PrestaShop, algo que es totalmente desaconsejado y que, además, si actualizamos PrestaShop perdemos …

    Seguir leyendo

  • Entrada

    Añadir columnas a los listados del backoffice

    En este post vamos a contar como añadir columnas extras a tus listados del backoffice a través del hook: actionAdmin <ControllerName> ListingFieldsModifier En muchas ocasiones tenemos que mostrar columnas personalizadas en los listados que genera PrestaShop en su backoffice y además necesitamos que estas columnas se puedan filtrar y exportar, pues bien, el mejor método …

    Seguir leyendo

  • Entrada

    Utilización de Eventos Javascript PrestaShop

    En esta entrada describimos que son los eventos javascript Prestashop y como podemos utilizarlos para modificar o adaptar el comportamiento de nuestras tiendas.

    Seguir leyendo

¿Necesitas ayuda más avanzada para solucionar tus problemas?

Contáctanos o echa un vistazo a nuestros servicios, entre los cuales podrás encontrar varios planes de mantenimiento de Prestahop.

Nuestros servicios

Impulsa la venta online y haz crecer tu negocio en poco tiempo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles. Ver política de cookies