A lo largo de mi carrera he tenido la oportunidad de «meter las narices» en unas cuantas aplicaciones… a algunas de ese aplicaciones podríamos llamarlas «legacy» en el sentido de que son aplicaciones que necesitan algo de «cariño». Recuerdo una pregunta en una ¿Y tu cómo te enfrentas a aplicaciones legacy? mi respuesta fue Con paciencia. Así que hoy escribiremos sobre antipatrones más comunes cuando nos enfrentamos a una aplicación legacy. Este artículo no vamos a reescribir todo en <el fancy framwork que sea>,sino que veremos como hacer nuestras aplicaciones un poco más mantenibles.
Si quieres profundizar más sobre el legacy este artículo titulado «Mi código en producción ya es legacy» de Fran Iglesias es genial.

Credenciales repartidas por el código
Quizás no sea el antipatrón más grave, ni mucho menos, pero si el que más he podido ver. Lo normal es tener el usuario y password de la aplicación en el repositorio de código (gracias a git no lo perderemos nunca…).
Esto es una mala práctica ya que por un lado, hace que sea complicado crear una configuración local ya que el código está vinculado a un entorno específico. Por otro lado supone un fallo de seguridad, porque cualquiera con acceso al repositorio podría acceder a la base de datos, FTP o incluso obtener tokens de acceso a otras aplicaciones.
Para solucionarlo, lo más sencillo es utilizar una librería como vlucas/phpdotenv que permite crear un fichero .env
y acceder a las variables como si fuesen globales. La manera de hacerlo en un proyecto es crear un fichero .env.example
que añadiremos a git. En este fichero tendremos todas las variables que estén definidas en el código
DB_HOST=
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=
Lo que tendremos que hacer es añadir al .gitignore
el fichero .env
para que las credenciales dejen de estar en nuestro repositorio.
Para usar el el fichero .env
en nuestro código solo tenemos que cargar el fichero y usar las variables $_ENV['DATABASE'];
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
//access to variables
$s3_bucket = getenv('S3_BUCKET');
$s3_bucket = $_ENV['S3_BUCKET'];
$s3_bucket = $_SERVER['S3_BUCKET'];
Listo ya podemos tener diferentes credenciales por entorno. Ahora para en el entorno de producción lo que tenemos que hacer es inyectar estas variables, así que dependiendo de si tienes un servidor, usas Docker, o lo que sea, tendremos que inyectarlas de una forma u otra.
Para eliminar los ficheros que contienen credenciales del repositorio, aquí tenemos este tutorial: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/removing-sensitive-data-from-a-repository
Estado global / variables globales
En relación con lo anterior, otro punto importante es el uso de estados globales de la aplicación. Tener un estados globales en una aplicación hace que esta sea impredecible.
El problema con los estados globales no es el estado en si, sino que puede que un objeto haga modificaciones sobre dicho estado (side effects) y por ello modifique el estado global, lo que conlleva a que no sabemos predecir cual será el valor. Tener estos estados perjudica no solo la ejecución, sino también la legibilidad ya que si hay variables globales definidas tenemos que preocuparnos de saber cual es su valor tanto en producción como en el entorno de desarrollo. Esta respuesta de stackoverflow sobre «global states» tiene bastante miga si quieres seguir indagando.
Una alternativa para que tengamos el estado global de la aplicación controlado es usar «dependencia de inyección, ya que eso nos da algo de control sobre que instancia se usa y donde. Una librería como https://github.com/PHP-DI/PHP-DI puede ayudarnos con la inyección de dependencias.
No usar composer
Aunque pueda parecernos extraño todavía hay bastantes proyectos que no usan Composer. Para estar todos en la misma linea, Composer es un sistema de gestión de paquetes para programar en PHP el cual provee los formatos estándar necesarios para manejar dependencias y librerías de PHP. ¿Cómo lo hacen estos proyectos? pues lo que he visto es que por ejemplo tienen una carpeta lib
donde se van copiando todas las librerías.
Sin entornos (aka todo a producción)
Alguna vez he visto que hay aplicaciones o partes de una aplicación mucho más grande que solo está en producción. Es decir, no hay repositorio, no hay versionado, no hay copia de seguridad… me he encontrado con que son scripts para tratar datos, para desplegar o incluso algunos endpoints que hay quedado olvidados.
Esta parte va relacionada con el primer punto de tener credenciales repartidas por el código. La manera de atajar esto es usar repositorios, meter todos los scripts en una carpeta o en un repositorio aparte ya es un primer paso. Después, dependiendo de la naturaleza del script podemos usar deployer para desplegar o soluciones como simple-backup.
Problemas de seguridad
Tener una aplicación y no usar ningún framework hace que tengamos que tener en cuentas muchos temas, uno de ellos es la seguridad. Si la aplicación es algo legacy, ya podemos prepararnos para alguna brecha de seguridad. Las más comunes son:
- SQL Injection por escapar los parametros de una sentencia SQL –> Usemos al menos PDO.
- XSS Injection por no escapar los datos que estamos viendo. Usar htmlspecialchars puede prevenir alguna que otra desgracia.
- Subida de ficheros… tener una subida de ficheros al servidor es un endpoint complicado, porque pueden colarnos malware.
- Password. Pues que podemos decir aquí, desde no encriptar los passwords, dejarnos un log con los password en plano, usar algoritmos de encriptación débiles.
Un punto también delicado en este sentido es tener toda la aplicación accesible desde Internet, en otras palabras cualquier archivo, por ejemplo un script de cualquier podría ser accedido tan solo conociendo el nombre del fichero. Obviamente, esto ma en linea también con el punto que hemos hablado antes de tener entornos y los ficheros .env
La solución no es trivial en muchos casos, la idea sería que tuviésemos públicos solo aquellos archivos orientados a la web (normalmente en index.php y los ficheros assets) el resto no debería de ser accesible. Para ello tendremos que configurar el Apache, Nginx para que solo sea accesible una carpeta de la aplicación.
Nada de tests
Que podemos decir de este punto,… normalmente si las aplicaciones son legacy, es «normal» que no haya tests.
Si bien entras de lleno a ponernos a escribir pruebas unitarias va a ser un dolor, quizás podamos empezar a crear pruebas funcionales, o al menos que las nuevas funcionalidades estén cubiertas por tests.
Podemos empezar por tener una sencillas pruebas que nos digan si los endpoint están devolviendo Ok o alguno está fallando, hay herramientas como PHPUnit, Codeception o Cypress que pueden ayudarnos en esta parte.
Manejo de errores ¿qué es eso?
Si algo se rompe debemos darnos cuenta rápidamente. Si buscamos un poco por Internet seguro que encontramos un montón de charlas sobre observabilidad, logs, métricas, trazabilidad, Prometheus,… el problema que tenemos es que la mayoría de aplicaciones legacy manejan los errores de manera deficiente, o peor aún ni siquiera los manejan y deja que se propaguen…
Hay buenos artículos como este que nos enseñan un poco como tratar las excepciones en PHP. Podemos empezar por lanzar excepciones específicas, lo que nos ayudará a detectar donde está el error. Hace tiempo ya escribimos sobre excepciones y dimos algunos trucos de como escribir código.
¿Cuál es el siguiente paso?
Pues como siempre la respuesta es depende. Depende del futuro de la aplicación, del presupuesto, del equipo,… se pueden tomar unos caminos u otros.
Una vez que ya tengamos la aplicación controlada: tenemos un entorno local, podemos desplegar en producción y hacer rollback sin demasiado dolor lo que deberíamos pensar es en subir a una versión de PHP actual (PHP 7.x). Hay bastantes herramientas que nos ayudan a pasar de versión de PHP.
Lo siguiente es empezar a poner orden a la aplicación y poder separar en capas, al menos separar la parte de vista de la lógica de negocio y de la base de datos.
Y ya si vamos para nota podemos echarle un ojo a Things You Should Never Do, Part I y a StranglerFigApplication. Aunque puedan parecer artículos algo antiguos, creo que no han envejecido demasiado mal. pero como hemos dicho al principio, todo depende de según como se mire.
Si el tema te ha gustado, no puedo dejar de recomendar el libro Modernizing Legacy Applications In PHP que cubre bastantes de los puntos que hemos descrito aquí, así como otros tantos consejos que nos ayudarán a hacer más mantenibles nuestras aplicaciones legacy.