Algo muy curioso que me he encontrado en el último proyecto es, diciendolo de una manera suave, lo mejorable que puede llegar a ser el sistema de webservice de PrestaShop. No es que sea malo, de hecho está muy bien montado, el problema es que se montó muy bien hace años, se estancó y nadie ha vuelto para retomarlo.

El proyecto tenia por misión mover una tienda enorme de un pseudo presta 1.6 a un presta estandar 1.7 (digo pseudo 1.6 porque tenía tantas adaptaciones que eso ya no era un PrestaShop). Durante el planteamiento del proyecto, entre otras cosas, se decidió que el programa de gestión hablará con la web mediante el webservice oficial (https://devdocs.prestashop.com/1.7/development/webservice/ para quien no lo conozca), evitando que se dirigiera contra la base de datos directamente.
Recibir las llamadas por webservice nos permitiría estandarizar los datos, prevenir errores y disparar los ganchos internos adecuados. Sin embargo, también nos traería más trabajo, ya deberíamos adaptar cualquier módulo para que fuera accesible por esta vía, además de otras cosas que ya saldrían más adelante.

Comienza el desarrollo, comienzan los problemas.

¡Manos a la obra! El equipo de desarrollo de presta había hecho un gran trabajo con el webservice que tomariamos como base, sin embargo, encontramos enseguida la primera traba: el webservice es un desarrollo estable pero abandonado, no permite ampliaciones externas de una forma limpia y carece de algunas funcionalidades básicas. ¿Cómo ibamos a hacer accesibles los módulos desde el webservice si no podíamos ampliar sus funciones?

Para ponernos en situación, pongamos el ejemplo de un caso muy básico. Imaginemos que desarrollamos un módulo que añade propiedades al usuario, un campo nuevo por ejemplo en el que almacenar el nombre de su ciudad natal. Que aparezca el campo en el frontal es facil pero, para que se muestre el nuevo campo en el código del webservice solo había una vía. Una horrible vía, siniestra, espantosa… sobrecargar las entidades, o en otras palabras, hacer overrides.

Quien haya trabajado en Presta y haya usado overrides entenderá el nivel de horror, pero lo que no podia imaginar es que el problema empeoraría. Siguiendo el ejemplo del modulo anterior, la única salida oficial para nuestro problema es sobreescribir la clase Customer y añadir la propiedad, lo cual generaba un problema más, sólo un módulo puede realizar un override de una entidad para añadir una propiedad al webservice (trabalenguas…). En pocas palabras, una entidad quedaría bloqueada para el primer módulo que llegase. Si tengo otro módulo que necesite añadir una propiedad, se acabó el juego.

Seamos creativos y busquemos una solución como buenos programadores que somos

¿Qué hacemos? Tenemos dos problemas:
1- No podemos llenar el sistema de overrides, habrá centenares de módulos que integrar en el webservice.
2- No podemos bloquear un entidad para un único módulo.
La solución está clara, desarrollar nuestra propia solución, aunque debe ceñirse a las premisas del proyecto (lo mas estandar posible para permitir seguir actualizando oficialmente).

En el webservice se emplean básicamente dos operaciones: lectura y escritura. Debemos localizar dónde se realiza cada una para diseñar una solución adecuada.
Por suerte, esta parte estaba muy bien diseñada de base. En cada entidad existe un array que define campos especiales para el webservice, estableciendo el nombre del método para el getter y el setter. Ejemplo simplificado:

protected $webserviceParameters = array(
        'fields' => array(
            'passwd' => array('setter' => 'setWsPasswd'),
        )
    );

public function setWsPasswd($passwd)
    {
        /** @var \PrestaShop\PrestaShop\Core\Crypto\Hashing $crypto */
        $crypto = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Crypto\\Hashing');
        if ($this->id == 0 || $this->passwd != $passwd) {
            $this->passwd = $crypto->hash($passwd);
        }

        return true;
    }

Cuando el webservice carga una entidad, llama a su funcion getWebserviceParameters, la cual está definida en la clase ObjectModel. Sobreescribiendo esta función podemos crear un gancho que permita añadir campos a la entidad:

public function getWebserviceParameters($ws_params_attribute_name = null)
    {
        //Invocamos a los hooks para los setters y getters de modulos
        $virtualGettSett = Hook::exec('actionWsVirtualGetSet', ['class' => strtolower(get_class($this))], null, true);
        (...)
        $parameters = parent::getWebserviceParameters($ws_params_attribute_name);
        return $parameters;
    }

Perdón por el código omitido, pero no puedo publicar todo el código del cliente por razones obvias :P.

El código añade al array original los getters y setters nuevos, añadiendo además una marca que indica el nombre del módulo responsable (para saber a quien hay que llamar mas que nada).

¡Acabamos de crear una via limpia para ampliar el webservice! Nos resta enganchar el módulo y programar la lógica del getter y setter.

public function hookActionWsVirtualGetSet($params)
    {
        if (isset($params['class']) && $params['class'] == 'customer') {
            return [
                'birthplace' => [
                    'getter' => 'getWsBirthplace',
                    'setter' => 'setWsBirthplace'],
            ];
        }
    }

public function getWsBirthplace($object)
    {
        $birthplace = 'Elche';
        return $birthplace;
    }

public function setWsBirthplace($object, $value)
    {
        //Logica para guardar el lugar de nacimiento
    }

El último punto por desgracia no puedo explicarlo como me gustaría, el código es eternamente largo. Corresponde a modificar las clases WebserviceRequest y WebserviceOutputBuilder para llamar al getter y al setter del módulo.

if (Module::isEnabled($field['module'])) {
	$getterModule = Module::getInstanceByName($field['module']);
	if (method_exists($getterModule, $field['getter'])) {
		$field['value'] = $getterModule->{$field['getter']}($object);
	}
}

Conclusión

Es un gran trabajo pero es uno de esos grandes trabajos de los que te sientes orgulloso. Es cierto que dedicamos una mañana a desarrollar todo esto pero, gracias a eso, podemos hacer compatible cualquier módulo, de una manera sencilla, limpia y en un tiempo record.

Creo sinceramente que un gancho como este es fundamental en el webservice, algo que con el tiempo estoy seguro de que llegará oficialmente ya que incrementa las posibilidades de forma exponencial.