Banner

Ultima revisión 02/04/2013

Cómo hacer override en módulos de Prestashop

Desde Prestashop 1.4 ya se permitía a los desarrolladores la posibilidad de cambiar el comportamiento de las funciones estándar sin modificar el núcleo. En la versión 1.5 se ha ido más lejos al dar la posibilidad de reescribir los archivos durante la instalación de un módulo.

Esto es lo que la función de instalación se parecía en Prestashop 1.4:

/**
* Insert module into datable
*/
public function install(){
    if (!Validate::isModuleName($this->name)) die(Tools::displayError());
    $result = Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($this->name).'\'');
    
    if ($result) return false;
    $result = Db::getInstance()->AutoExecute(_DB_PREFIX_.$this->table, array('name' => $this->name, 'active' => 1), 'INSERT');
    
    if (!$result) return false;
    $this->id = Db::getInstance()->Insert_ID();
    
    return true;
}

Como podemos observar, el sistema comprueba la tabla de módulos para ver si el módulo está instalado y, si no fuese así, crea una nueva entrada poniendo el flag de active a 1 . En el nuevo Prestashop 1.5 esta función es:

/**
* Insert module into datable
*/
public function install(){
    // Check module name validation
    if (!Validate::isModuleName($this->name)) die(Tools::displayError());
    
    // Check PS version compliancy
    if (version_compare(_PS_VERSION_, $this->ps_versions_compliancy['min']) < 0 || version_compare(_PS_VERSION_, $this->ps_versions_compliancy['max']) >= 0){
        $this->_errors[] = $this->l('The version of your module is not compliant with your PrestaShop version.');
        return false;
    }

    // Check module dependencies
    if (count($this->dependencies) > 0){
        foreach ($this->dependencies as $dependency){
            if (!Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($dependency).'\'')){
                $error = $this->l('Before installing this module, you have to installed these/this module(s) first :').'<br />';
                foreach ($this->dependencies as $d)
                    $error .= '- '.$d.'<br />';
                    $this->_errors[] = $error;
                    return false;
                }

                // Check if module is installed
                $result = Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($this->name).'\'');
                if ($result){
                    $this->_errors[] = $this->l('This module has already been installed.');
                    return false;
                }

                // Install overrides
                try {
                    $this->installOverrides();
                } catch (Exception $e) {
                    $this->_errors[] = sprintf(Tools::displayError('Unable to install override: %s'), $e->getMessage());
                    $this->uninstallOverrides();
                    return false;
                }

                // Install module and retrieve the installation id
                $result = Db::getInstance()->insert($this->table, array('name' => $this->name, 'active' => 1, 'version' => $this->version));

                if (!$result){
                    $this->_errors[] = $this->l('Technical error : PrestaShop could not installed this module.');
                    return false;
                }

                $this->id = Db::getInstance()->Insert_ID();
                Cache::clean('Module::isInstalled'.$this->name);

                // Enable the module for current shops in context
                $this->enable();

                // Permissions management
                Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (SELECT id_profile, '.(int)$this->id.', 1, 1 FROM '._DB_PREFIX_.'access a WHERE id_tab = (SELECT `id_tab` FROM '._DB_PREFIX_.'tab WHERE class_name = \'AdminModules\' LIMIT 1) AND a.`view` = 1)');
                Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (SELECT id_profile, '.(int)$this->id.', 1, 0 FROM '._DB_PREFIX_.'access a WHERE id_tab = (SELECT `id_tab` FROM '._DB_PREFIX_.'tab WHERE class_name = \'AdminModules\' LIMIT 1) AND a.`view` = 0)');

                // Adding Restrictions for client groups
                Group::addRestrictionsForModule($this->id, Shop::getShops(true, null, true));

                return true;
            }
        }
    }
}

La nuevas versiones de Prestashop ofrecen la posibilidad de especificar el sistema de versiones. Para saber si los módulos son compatibles se hace a través de $this->ps_versions_compliancy['min'] y $this->ps_versions_compliancy['max']. Si la versión del PrestaShop del cliente no es compatible con el módulo, se mostrará un mensaje al usuario que dirá algo similar a: La versión de el módulo no es compatible con su versión de PrestaShop. Las funciones installOverrides y addOverride serán las responsables de los controladores y la redefinición de las clases durante la instalación del módulo.

/**
* Install overrides files for the module
*
* @return bool
*/
public function installOverrides()
{
    // Get local path for module
    if (!is_dir($this->getLocalPath().'override'))
        return true;
    $result = true;
    foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
    {
        $class = basename($file, '.php');
        // Add all methods in a module override to the override class
        if (Autoload::getInstance()->getClassPath($class.'Core'))
            $result &= $this->addOverride($class);
    }
    return $result;
}
/**
* Add all methods in a module override to the override class
*
* @param string $classname
* @return bool
*/
public function addOverride($classname)
{
    $path = Autoload::getInstance()->getClassPath($classname.'Core');

    // Check if there is already an override file, if not, we just need to copy the file
    if (!($classpath = Autoload::getInstance()->getClassPath($classname)))
    {
        $override_src = $this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path;
        $override_dest = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'override'.DIRECTORY_SEPARATOR.$path;
        if (!is_writable(dirname($override_dest)))
            throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), dirname($override_dest)));
        copy($override_src, $override_dest);
        return true;
    }

    // Check if override file is writable
    $override_path = _PS_ROOT_DIR_.'/'.Autoload::getInstance()->getClassPath($classname);
    if (!is_writable($override_path))
        throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $override_path));

    // Make a reflection of the override class and the module override class
    $override_file = file($override_path);
    eval(preg_replace(array('#^\s*<\?php#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array('', 'class '.$classname.'OverrideOriginal'), implode('', $override_file)));
    $override_class = new ReflectionClass($classname.'OverrideOriginal');
    $module_file = file($this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path);
    eval(preg_replace(array('#^\s*<\?php#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array('', 'class '.$classname.'Override'), implode('', $module_file)));
    $module_class = new ReflectionClass($classname.'Override');

    // Check if none of the methods already exists in the override class
    foreach ($module_class->getMethods() as $method)
        if ($override_class->hasMethod($method->getName()))
            throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overriden.'), $method->getName(), $classname));

    // Check if none of the properties already exists in the override class
    foreach ($module_class->getProperties() as $property)
        if ($override_class->hasProperty($property->getName()))
            throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname));

    // Insert the methods from module override in override
    $copy_from = array_slice($module_file, $module_class->getStartLine() + 1, $module_class->getEndLine() - $module_class->getStartLine() - 2);
    array_splice($override_file, $override_class->getEndLine() - 1, 0, $copy_from);
    $code = implode('', $override_file);
    file_put_contents($override_path, $code);
    return true;
}

Con el fin de hacer override desde los módulos, sería necesario crear la estructura de override con toda la estructura interna identica a los controladores y clases que se van a sobreescribir. Si por ejemplo queremos hacer override a la clase FrontController, dentro de nestro módulo, crearemos la estructura override / classes / controllers y, en esta última carpeta meteremos nuestro FrontController cambiado.

Si, por ejemplo, quiseramos hacer override o sobrecargar CmsController, sería necesario crear la estructura de carpetas override / controllers / front y en este último copiar el archivo CmsController.php con el siguiente contenido en la carpeta

class CmsController extends CmsControllerCore {
    public function setMedia(){
        parent::setMedia();
        die('Prueba de override');
    }
}

Cuando el módulo sea instalado, PrestaShop encontrará los archivos que necesita para sobrecargarlos. Prestashop comprueba las clases y controladores antes de hacer cambios y después hace el override. Sólo cuado se va a realizar override de un método que ya estaba sobrecargado es cuando puede haber problemas o pérdidas. Cuando se desinstala el módulo se eliminarán los métodos sobrecargados y, si hubiese habido cambios después de la instalación, también se perderían.

De todas formas es recomendable usar Hooks en los módulos siempre que se pueda antes que hacer override a los métodos o clases ya que, con esto, se evitarán reescrituras accidentales.

Para hacer override en la versión 1.4 se debe hacer con addOverride($class) y removeOverride($class) de lo cual, hablaremos mañana.