🛡️

Overrides blindados — instalacion sin conflictos

Actualizado: 2024-12-01
💡
Tecnica profesional

Esta tecnica evita uno de los errores mas comunes en produccion: el bloqueo de instalacion de modulos por conflicto de overrides. Es la aproximacion recomendada para modulos que deben coexistir con otros en tiendas de clientes.

#El problema con installOverrides()

El sistema nativo installOverrides() de PrestaShop detecta si otro modulo ya ha overrideado la misma clase o metodo. Si hay conflicto, bloquea la instalacion del modulo. En tiendas con muchos modulos, esto es un problema frecuente.

🚨
Escenario de fallo tipico

Modulo A ya overridea Product::getProductProperties(). Cuando el cliente intenta instalar tu Modulo B que tambien overridea ese metodo, PrestaShop bloquea la instalacion con el error: "Override conflict in class Product". El cliente no puede instalar tu modulo.

#La solucion: copia manual blindada

En lugar de usar el directorio override/ del modulo (que activa el sistema nativo), guardamos los overrides en un directorio personalizado (ej. override_v8/) y los copiamos manualmente durante la instalacion, solo si no existe ya el fichero destino. No hay deteccion de conflictos, no hay bloqueo.

#Implementacion completa

Estructura de directorios del modulo
php
mymodule/
├── mymodule.php          # Archivo principal
├── override_v8/          # Overrides propios (NO usar 'override/')
│   └── classes/
│       └── Product.php
└── ...
mymodule.php — instalacion y desinstalacion seguras
php
<?php

declare(strict_types=1);

if (!defined('_PS_VERSION_')) {
    exit;
}

class MyModule extends Module
{
    public function __construct()
    {
        $this->name    = 'mymodule';
        $this->tab     = 'front_office_features';
        $this->version = '1.0.0';
        parent::__construct();
    }

    public function install(): bool
    {
        return parent::install()
            && $this->registerHook('actionFrontControllerSetMedia')
            && $this->installSafeOverrides();
    }

    public function uninstall(): bool
    {
        $this->uninstallSafeOverrides();
        return parent::uninstall();
    }

    /**
     * Copia overrides manualmente SIN usar installOverrides().
     * Solo copia si el fichero destino NO existe todavia.
     */
    private function installSafeOverrides(): bool
    {
        $overrides = [
            'classes/Product.php',
            // Añadir mas ficheros aqui
        ];

        foreach ($overrides as $path) {
            $source      = _PS_MODULE_DIR_ . $this->name . '/override_v8/' . $path;
            $destination = _PS_ROOT_DIR_ . '/override/' . $path;

            if (!file_exists($source)) {
                continue; // El override no existe en el modulo
            }

            if (!file_exists($destination)) {
                $dir = dirname($destination);
                if (!is_dir($dir)) {
                    mkdir($dir, 0755, true);
                }
                copy($source, $destination);
            }
            // Si ya existe, lo dejamos (podria ser de otro modulo)
        }

        // Limpiar class_index para que PS reconozca los nuevos overrides
        $this->clearClassIndex();

        return true;
    }

    /**
     * Elimina overrides SOLO si son identicos a los del modulo.
     * Nunca elimina overrides modificados por otros.
     */
    private function uninstallSafeOverrides(): void
    {
        $overrides = [
            'classes/Product.php',
        ];

        foreach ($overrides as $path) {
            $source      = _PS_MODULE_DIR_ . $this->name . '/override_v8/' . $path;
            $destination = _PS_ROOT_DIR_ . '/override/' . $path;

            if (file_exists($destination) && file_exists($source)) {
                // Solo eliminar si el contenido coincide exactamente con el nuestro
                if (md5_file($destination) === md5_file($source)) {
                    @unlink($destination);
                }
            }
        }

        $this->clearClassIndex();
    }

    private function clearClassIndex(): void
    {
        // PS 8/9 — rutas de cache
        $cachePaths = [
            _PS_ROOT_DIR_ . '/var/cache/prod/class_index.php',
            _PS_ROOT_DIR_ . '/var/cache/dev/class_index.php',
        ];
        foreach ($cachePaths as $path) {
            if (file_exists($path)) {
                @unlink($path);
            }
        }
    }
}

#Extender ObjectModel con override

Cuando haces override de un ObjectModel para añadir campos propios, debes actualizar el array $definition en el constructor antes de llamar al padre.

override_v8/classes/Product.php
php
<?php

if (!defined('_PS_VERSION_')) {
    exit;
}

/**
 * IMPORTANTE:
 * - No usar namespace en overrides
 * - No usar ::class  (usar get_class() para PHP 7.4 compat)
 * - No poner closing PHP tag
 */
class Product extends ProductCore
{
    /** @var string Nuestro campo extra */
    public $my_custom_field;

    public function __construct(
        $id = null,
        $id_lang = null,
        $id_shop = null
    ) {
        // Añadir campo al definition ANTES de llamar al parent
        self::$definition['fields']['my_custom_field'] = [
            'type'     => self::TYPE_STRING,
            'validate' => 'isCleanHtml',
            'size'     => 255,
            'lang'     => false,
            'shop'     => true,  // respeta multitienda
        ];

        parent::__construct($id, $id_lang, $id_shop);
    }
}

#Reglas de oro

ReglaRazon
Nunca usar installOverrides() en prodBloquea la instalacion en caso de conflicto
Nunca usar namespace en ficheros de overridePrestaShop espera clases sin namespace en /override/
Nunca usar ::class en overridesCompatibilidad con PHP 7.4, usar get_class()
Comprobar que el fichero fuente existeEl modulo podria instalarse en PS 9 sin el override_v8
Limpiar class_index despues de copiarPS no reconoce nuevos overrides sin regenerar el indice
Solo eliminar si el contenido coincideOtro modulo podria haber modificado el fichero
Dejar el fichero si ya existe en /override/No asumir que somos los unicos duenos del override
Descargar en Markdown Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.