Overrides blindados — instalacion sin conflictos
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.
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
mymodule/
├── mymodule.php # Archivo principal
├── override_v8/ # Overrides propios (NO usar 'override/')
│ └── classes/
│ └── Product.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.
<?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
| Regla | Razon |
|---|---|
Nunca usar installOverrides() en prod | Bloquea la instalacion en caso de conflicto |
| Nunca usar namespace en ficheros de override | PrestaShop espera clases sin namespace en /override/ |
Nunca usar ::class en overrides | Compatibilidad con PHP 7.4, usar get_class() |
| Comprobar que el fichero fuente existe | El modulo podria instalarse en PS 9 sin el override_v8 |
| Limpiar class_index despues de copiar | PS no reconoce nuevos overrides sin regenerar el indice |
| Solo eliminar si el contenido coincide | Otro modulo podria haber modificado el fichero |
| Dejar el fichero si ya existe en /override/ | No asumir que somos los unicos duenos del override |