Que tiempos aquellos en que el tamaño los archivos que se subían vía HTTP se medía en KB, ¿no? Qué fácil era la vida por entonces.
Pero bueno… el mundo avanza y con él la necesidad de compartir nuestras fotos en alta calidad se hace presente.
En ese contexto, tener un upload máximo de 2 MB puede convertirse en un gran limitante.
Y, casualmente, el tamaño máximo por defecto del upload_max_filesize de PHP es de… 2 MB.
En un ambiente de desarrollo normal modificar esto es fácil, ¿no?
Basta con:
Ejecutar el comando php --ini
Abrir el archivo referido en Configuration File (php.ini)
Buscar la línea upload_max_filesize = 2M
Modificarla por un valor acorde a nuestras necesidades (Por ejemplo upload_max_filesize => 10M)
Reiniciar el servidor (o php-fpm)
Pero si estamos usando un contenedor Docker las cosas no son tan simples.
Es decir, hacer el cambio en sí no presenta una dificultad especial:
docker exec -it 7b8da0fb9892 bash
apt update
apt install -y vim
vim /usr/local/etc/php/php.ini
Modificar el valor de la configuración, guardar y listo.
El problema es que… apenas el contenedor se destruya, el cambio se fue con él y estamos otra vez donde empezamos.
Es todo una cuestión de imagen
La solución basada en el Dockerfile pasa por tener, dentro de la imagen que se construya, una definición del php.ini que contenga los cambios que necesitamos.
¿Cómo lograr eso? Pues se trata de tener, dentro de nuestro proyecto, nuestro propio php.ini y meterlo dentro de la imagen durante el build.
En concreto, supongamos que partimos de un Dockerfile que se ve así:
FROM php:7.4-apache
LABEL authors="Mauro Chojrin <mauro.chojrin@leewayweb.com>"
RUN apt-get update && \
apt-get install -y \
libpng-dev \
libzip-dev
RUN docker-php-ext-install gd && \
docker-php-ext-install zip && \
docker-php-ext-install mysqli
ADD app /var/www/html
Con esto podemos crear una imagen para luego levantar un contenedor con un Apache que podrá procesar php para responder a las peticiones.
En este ejemplo, el código (El contenido del directorio /var/www/html dentro del contenedor) será el que se encuentre dentro del directorio app de nuestro proyecto.
Lo que toca hacer es agregar un archivo php.ini dentro del proyecto e indicar a Docker que lo agregue a la imagen durante el build:
FROM php:7.4-apache
LABEL authors="Mauro Chojrin <mauro.chojrin@leewayweb.com>"
RUN apt-get update && \
apt-get install -y \
libpng-dev \
libzip-dev
RUN docker-php-ext-install gd && \
docker-php-ext-install zip && \
docker-php-ext-install mysqli
ADD app /var/www/html
ADD php.ini /usr/local/etc/php/
¿Qué debería contener el archivo php.ini? Si queremos asegurarnos de que sólo se modifique el upload_max_filesize necesitamos empezar por un archivo que contenga la configuración presente dentro del contenedor.
Cómo obtener el archivo original
Lo primero que podemos probar es buscar el archivo dentro del contenedor1:
docker run -v $(pwd):/var/www/html -it php:7.4-apache php dump_php_ini.php > php.ini
Con eso tengo el archivo que contiene la configuración exacta que se está usando actualmente.
Y ahora sí, puedo editarlo, hacer la modificación que se requiere y volver a construir la imagen usando:
docker build . -t my_apache_php
A partir de aquí, todos los contenedores que sean creados usando este Dockerfile tendrán la configuración deseada y los visitantes del sitio podrán compartir sus fotos sin inconvenientes.
Tenés tu entorno local funcionando perfecto con Docker.
Llevar la imagen al hosting y crear el contenedor no es un problema pero, cuando querés que los clientes vean el sitio… ¿qué les tenés que pasar? ¿No sería genial poder pasarles una URL con tu propio dominio?
Veamos qué necesitarías para hacerlo.
Un servidor donde alojar tus contenedores
Un dominio que puedas direccionar a tu servidor
La configuración de DNS
La configuración del webserver dentro de Docker
Asumiré que los puntos 1, 2 y 3 los tenés resueltos y me concentraré en el punto 4.
Cómo configurar Docker para que responda a un dominio único
Si se trata de un único contenedor corriendo en el servidor no es necesario hacer demasiado. Basta con asegurarse de que el contenedor esté conectado al puerto 80 del servidor.
Por ejemplo, si se inicia en un servidor un contenedor usando algo como:
docker run --restart=always -p 80:80 imagen -d
Será suficiente para dejar el contenedor corriendo en background en forma constante.
Con una configuración estándar de Apache como por ejemplo:
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
Es suficiente.
En definitiva, casi que es más difícil lograr que el contenedor no quede asociado al dominio que lo contrario.
Cómo configurar Docker para que responda a diferentes dominios
Distinto, y más interesante, es el caso de querer alojar en un mismo servidor varios sitios diferentes, cada uno con su propio contenedor.
Para lograr eso se requiere algo más de maña a nivel de infraestructura.
Un webserver como Apache permite, por ejemplo, usar múltiples hosts virtuales (VirtualHost) pero, obviamente, el puerto 80 del servidor es uno solo. Esto quiere decir que hay un único proceso escuchando en el puerto 80 y, de todo el tráfico que se recibe a través de él, es su responsabilidad decidir cómo responder.
Para que a cada sitio le llegue el tráfico que le corresponde, y sólo ese tráfico, es necesario definir algún tipo de mapeo entre la petición que recibe el webserver y el host virtual que lo atenderá.
Una forma de hacer esto es asociar diferentes hosts a diferentes puertos.
<VirtualHost *:80>
...
</VirtualHost>
<VirtualHost *:81>
...
</VirtualHost>
<VirtualHost *:82>
...
</VirtualHost>
Claro que, si la idea es que a todos los sitios se acceda a través del puerto estándar, esta opción queda descartada.
En este esquema, Apache determinará qué VirtualHost es el requerido analizando la URL. Un request de tipo http://dominio1.com será atendido siguiendo la primera definción, http://dominio2.com la segunda y http://dominio3.com la tercera.
El primer paso es claro: definir un VirtualHost por cada dominio que se quiera usar.
Segundo paso: conectar el tráfico del host al contenedor que le corresponde.
Para lograr esto es necesario contar con el módulo proxy de Apache. En el caso de Ubuntu basta con el comando sudo a2enmod proxy_http; sudo service apache2 restart para tenerlo todo listo.
Luego, por cada sitio que se quiera servir, se debe crear la configuración del VirtualHost:
De esta forma, todo el tráfico que se reciba a través de http://dominio1.com será redirigido automáticamente hacia el puerto 5000 dentro del mismo servidor (De ahí el 127.0.0.1).
Por último, para cerrar el círculo se necesita contar con un contenedor Docker escuchando en el puerto seleccionado, en este caso, el 5000. Con esto será suficiente para la prueba.
docker run -p 5000:80 -v $(pwd):/var/www/html php:7.4-apache
Asumiendo que el directorio actual contiene un archivo como este:
<?php
echo phpversion();
Al abrir el navegador en http://dominio1.com/ se verá algo como:
Si aún no tenés los dominios apuntados al servidor o querés hacer pruebas en tu entorno local basta con que modifiques tu archivo /etc/hosts apuntando todos los dominios a 127.0.0.1.
El caso del segundo dominio será prácticamente igual:
La diferencia está en el puerto. El contenedor para el segundo dominio deberá estar escuchando a un puerto diferente en el host, pero éste deberá estar mapeado al puerto 80 dentro del contenedor. Por ejemplo:
docker run -p 5001:80 -v $(pwd):/var/www/html php:8.2-apache
Y, nuevamente, al abrir el navegador en http://dominio2.com verás:
Y así continúa por cada uno de los dominios que necesites apuntar
Hace unos años, montar un entorno de desarrollo para una aplicación web PHP era una verdadera molestia:
Primero era necesario instalar Apache, luego MySQL, después configurar todo para que el Apache pudiera procesar correctamente los archivos PHP y generar la salida esperada, pasando en el medio por la configuración de MySQL y rogar que todo funcionara bien.
Y así pasábamos los días hasta que llegó XAMPP.
Qué es XAMPP
XAMPP fue, en su día, una verdadera revolución: un paquete único que contenía todo lo que podíamos necesitar para desarrollar cómodamente: click aquí, click allí y listo, a programar se ha dicho!
Si bien XAMPP simplicaba, y mucho, el montaje de un entorno local, en el fondo no se trataba de algo esencialmente diferente de lo que veníamos haciendo hasta el momento… sólo que mucho más rápido y cómodo.
El problema empezó cuando muchos programadores comenzaron a usarlo como si se tratara de una solución mágica y dejaron de lado comprender qué es lo que estaba ocurriendo tras bambalinas.
Craso error.
Error que se hace patente a la hora de subir el sitio al hosting. Es entonces cuando empiezan los problemas como:
Tengo este código que en localhost funciona perfectamente, que recibe los datos enviados mediante un formulario y saca los resultados mediante una tabla que se rellena automáticamente según los datos enviados. Resulta que cuando lo subo al servidor (hosting) no funciona y no da ningún tipo de error, simplemente no dibuja la tabla.
O:
tengo una pagina en php, en el servidor local funciona bien, es un login que al ingresar los datos dirige a una parte especifica de la web dependiendo el perfil, ahora si subo esto a mi hosting funciona perfectamente, pero si subo la web a otro dominio al ingresar los datos del login se queda la página en blanco. Ya intente que mostrara errores pero no me sale nada.
Hace algunos años habría recomendado usar máquinas virtuales para desarrollar. Hoy por hoy, existiendo Docker hay un nuevo ganador.
Qué es Docker
Docker es, ante todo, una plataforma de ejecución de aplicaciones basada en el concepto de contenedor.
La idea, muy resumida, es tener una única unidad (un paquete) que contenga en sí mismo todo lo necesario para ejecutar una aplicación.
De este modo, nos olvidamos de las pequeñas sorpresas que pueden encontrarse al llevar a un hosting un proyecto desarrollado en forma local.
¿Puede usarse Docker para desarrollos PHP?
¡Claro que sí!
Existen varias formas diferentes de usar Docker en proyectos PHP. En general, lo que se necesitará será una imagen que incluya el intérprete de PHP y, si se trata de una aplicación web, también un servidor.
XAMPP vs. Docker
Recapitulando un poco, como para que quede claro: XAMPP es un paquete de software específicamente diseñado para PHP, mientras que Docker es un sistema de manejo de contenedores genérico, es decir, no sólo puede usarse para PHP si no para cualquier lenguaje.
Por otro lado, con Docker es sumamente sencillo tener, en una misma máquina física, muchos procesos PHP corriendo diferentes versiones. Te reto a que intentes eso con XAMPP.
La desventaja, por llamarle de algún modo, de usar Docker en lugar de XAMPP es su dificultad para montarlo. Claro que, una vez que lo domines ésta desaparecerá.
¿Cómo reemplazar XAMPP por Docker?
Para reemplazar XAMPP por Docker se requiere contar con una configuración de Docker que incluya los servicios que XAMPP contiene (Además del propio Docker instalado localmente, claro).
Olvidarse del «te juro que en mi casa andaba!» es una bendición.
Claro que, para poder desplegar, primero hay que desarrollar. Y desarrollar implica, claro está, debuggear.
En PHP no contamos con un debugger incorporado a nuestros IDEs… afortunadamente existe XDebug.
El problema, sin embargo, suele ser la configuración que depende de dos factores que deben combinarse:
La instalación y configuración del lado del servidor.
La configuración del IDE para poder utilizarlo.
Existen muchas combinaciones posibles para realizar esta tarea, cada una con sus pequeñas particularidades. En este artículo me enfocaré en una de las combinaciones más populares por estos días:
VSCode sobre Ubuntu con un WebServer montado en Docker.
Lo que se necesita es agregar la definición del mapeo de directorios, desde el local al del servidor.
En este caso, el directorio en el servidor (en el contenedor Docker) es /var/www/html/, el cual debe ser conectado con el directorio app dentro de la raíz del proyecto.
Para usar el directorio del host, sin hardcodearlo, es posible usar la variable de VisualStudio workspaceFolder la cual contiene el directorio raíz del proyecto.
De modo que la configuración quedará de esta forma:
Venís trabajando sin descanso en una aplicación muy importante para entregar a un cliente.
Vas a buen ritmo, vas a llegar a la entrega sin mucho problema.
De pronto, te aparece la notificación de actualizar el sistema operativo y pensás: «Y… la verdad que no estaría mal… no puedo seguir toda la vida con la 1.0…».
Le das aceptar y, luego de unos minutos, cuando todo terminó te das cuenta de que la aplicación que andaba perfecta dejó de funcionar.
¿Eh?
¿Cómo puede ser?
Así como instintivamente tirás un php -v y comprobás que de 7.4 saltaste sin escalas a 8.1… y las cosas ya no son como antes.
¡Qué problema! Menos mal que es una situación hipotética ¿no?
Pues… no tanto. Esta historia es una adaptación libre de esta conversación que leí recientemente:
«se me actualizo php a la version 8 en Manjaro, y por ende se me descuadró todo, qué tengo que cambiar en el httpd.conf para que me funcione php8«
«Soy usuario de manjaro y me pasó lo mismo.Pero por suerte tenia dockerizado mis proyectos y no sufrí demasiado, deberías considerarlo«
Yo diría que suerte habría sido no tener que enfrentarse al problema.
Tener tus proyectos php dockerizados no es suerte, es una decisión.
Pensando un poco en esto empecé a preguntarme: ¿Cualquier aplicación php se puede dockerizar?
Qué significa dockerizar una aplicación PHP
Cuando se habla de dockerizar (me encanta este verbo) una aplicación, se hace referencia a hacer las adaptaciones necesarias para poder correrla sobre contenedores docker, tanto en un etorno de desarrollo como en uno de producción.
Estos ajustes dependen en gran medida de la naturaleza de la aplicación pero, a rasgos generales, se trata de:
Crear un Dockerfile (O al menos seleccionar una imagen pre-existente que sea compatible con las necesidades de la aplicación)
Cambiar, si las hubiera, referencias a localhost por otras que apunten a servicios dentro del ecosistema de docker
Crear una configuración de inicio de los servicios (Ya sea a través de Makefiles o similar o, mejor aún, a través de un archivo docker-compose)
En principio creo que no hay mucho más, pero si te parece que me olvidé de algo importante, por favor dejame un comentario abajo.
Qué se necesita para dockerizar una aplicación PHP
Para dockerizar una aplicación PHP se necesita, primero que nada, contar, tanto en local como en producción, con el motor y el cliente de docker.
Si se pretende usar docker-compose, también deberá estar presente en ambos lados.
Más allá de eso, es conveniente contar con un buen IDE, aunque no es imprescindible.
Técnicamente no se necesita nada más. Claro que saber lo que se está haciendo ayuda mucho.
Cómo dockerizar una aplicación PHP
Teniendo todo en su lugar el proceso es simple, aunque, dependiendo de la aplicación, puede ser más laborioso que en otros casos.
Relevamiento
Lo primero es entender cuáles son las dependencias:
¿Qué versión de PHP usás en producción?
¿Qué extensiones de PHP necesitás instaladas y habilitadas?
¿Qué otros servicios de infraestructura se usan con tu aplicación? ¿MySQL? ¿Redis?
¿Qué versiones?
¿Cómo está la configuración del webserver?
¿Cómo está la configuración de PHP?
¿Qué permisos sobre archivos necesita la aplicación para funcionar?
Con las respuestasa a estas preguntas podrás darte una idea de lo que requerirás en tu imagen docker.
Instalación de las herramientas
Si no las tienes disponibles, el próximo paso será instalar las herramientas necesarias (docker engine, docker client y docker-compose al menos).
Adaptación de la aplicación
Luego deberás preparar la aplicación:
Reemplazar referencias a servicios externos (Bases de datos, colas de mensajes, etc…) de IPs o nombres conocidos en el host a nombres conocidos dentro de la red de Docker
Reemplazar rutas absolutas por relativas
Reemplazar enlaces simbólicos por archivos concretos
Creación de los archivos de Docker
Es muy probable que tu aplicación tenga algunos requisitos específicos que no encuentres en una de las imágenes disponibles en el dockerhub. No te preocupes, siempre podés tomar una imagen lo más parecida posible a tus necesidades para usar como base y agregar tus cambios particulares.
Si el sistema es suficientemente complejo (si requiere de otros componentes de infraestructura), es conveniente definir también un archivo docker-compose.yml para mayor comodidad.
Pruebas y ajustes
Una vez hayas pasado por los pasos anteriores será cuestión de probar la aplicación y validar que todo funciona.
Los comandos exactos dependerán de cómo hayas armado los archivos de configuración, pero será algo así como:
docker build . -t mi_php
docker run -it mi_php
O, si usas docker-compose:
docker-compose up --build
Una vez estén levantados los servicios podrás entrar a http://localhost:8000 (Asumo que el puerto 8000 está mapeado al 80 en el contenedor) y ya podrás verificar que toda la aplicación funciona.
Por qué dockerizar tus aplicaciones php
Si te parece mucho trabajo, tenés razón, dockerizar una aplicación PHP puede ser molesto, especialmente cuando lo tienes que hacer por primera vez pero, si queda bien, es muy probable que no tengas que volver a hacerlo por mucho tiempo y, a la vez, tu aplicación quedará blindada ante cambios en el ambiente, tanto local como de producción.
Me llegó esta pregunta que me pareció interesante compartir:
Estoy usando PHPStorm, y como sólo me pide el intérprete cuando trato de ejecutar en el navegador, todavía no lo he instaldo, quisiera saber si ya es mejor instalar el Xampp, si es recomendable y en caso de que no lo sea ¿por que?
Por si no sabés de qué se trata XAMPP, es un paquete que trae, todo lo que típicamente se requiere para desarrollar con PHP:
Necesitás trabajar con diferentes proyectos a la vez.
Son estos los momentos en te das cuenta que el salvavidas estaba hecho de plomo.
Cuál es el problema con XAMPP
La sencillez que aporta XAMPP lo hace la opción más difundida entre los desarrolladores menos experimentados. Pero esa sencillez tiene un costo.
El primero de los problemas es que, al ocultar la complejidad real que implica montar un servidor, se propicia el efecto «¡Te juro que en mi casa andaba!». Como no sabés realmente qué tenés instalado es difícil verificar que el hosting tenga lo mismo (Más detalle de qué es exactamente lo que deberías mirar acá).
El segundo de los problemas es que hace muy difícil trabajar en diferentes proyectos donde cada uno tiene requerimientos de infraestructura diferentes (Por ejemplo, versiones diferentes del intérprete de PHP).
Por supuesto que, si sos conciente de estas limitaciones y sabés trabajar con ellas XAMPP puede ser una opción aceptable.
Qué usar en lugar de XAMPP
Las opciones son varias, hay algunas mejores que XAMPP y otras peores:
En general, docker es la mejor opción cuando se trata de montar entornos locales ya que es muy simple luego llevarlos a producción y/o compartir con tu equipo.
Pero bueno, es cierto también que dominarlo no es una tarea muy sencilla.
Si recién estás empezando (Y quiero decir que apenas estás dando tus primeros pasos), está bien que uses XAMPP pero es importante que tengas la idea de migrar lo antes posible.
Vayamos a lo importante: ¿cómo tomaron la decisión de migrar a una versión más actualizada?
…la gente que instala los servidores sugirió ir a la última versión de PHP y Apache y Centos y postgresql pero no va
Bien, ahora está más claro.
Llegados a este punto estamos realmente en un problema: por un lado, el sitio tiene que seguir funcionando.
Por el otro, no podemos pedirle al hosting que habilite una versión de PHP que sabemos que es compatible con nuestro sistema.
¿Qué se puede hacer?
Lo mejor sería buscar una imagen de docker que tenga la versión que buscamos, instalarla en el servidor y seguir la vida como si tal cosa.
Si esa no es una posibilidad, habrá que hacerse a la idea de que la solución tomará algún tiempo… con todo lo que ello implica.
Saltarse versiones de php puede ser bastante arriesgado.
En este caso, ir de la 5.5 directo a la 8.1 implica dejar de lado una serie de cambios que se realizaron al intérprete a tavés de los años, algunos para hacerlo más eficiente y otros para quitarle funciones que ya pueden seguir soportándose.
El camino más seguro llegados a este punto sería ir bajando de versión del lado del hosting (8.1., 8.0, 7.4 y así) hasta llegar a una que, o bien sea compatible con el sistema, o sea la más antigua soportada por el proveedor.
Si se llega al primer escenario (Una versión superior a la que veníamos usando pero en la que el sistema funciona bien) perfecto, tema resuelto.
Si, en cambio, la versión más antigua con la que podemos contar no es compatible con el sistema, tenemos trabajo por delante.
Supongamos por tomar un ejemplo que la versión más baja que nos permite usar el proveedor es la 7.1.
Entre la versión 5.5 y la 7.1 hubo tres versiones intermedias (5.6, 7.0 y la propia 7.1).
La manera más segura de llegar de una versión de php a otra es pasar por todas las intermedias.
Antes de que me lo digas, sí, es un trabajo arduo y molesto, lo sé… pero me temo que es la posibilidad menos riesgosa, así que… más vale empezar a trabajar.
Lo que vas a necesitar es seguir este proceso:
Instalar en un ambiente de pruebas la versión a la que vas a intentar migrar (5.6 sería la primera en este caso)
Instalar y configurar el sistema en dicho ambiente de pruebas
Ejecutar las pruebas
Hacer los ajustes correspondientes al código
Pasar a la siguiente versión y volver al paso 1
Algunas herramientas que te van a ayudar
Existen algunas herramientas en las que te podés apoyar para hacer este proceso algo menos laborioso:
En general, migrar a una nueva versión de php no es precisamente una tarea sencilla pero si encima lo tenés que hacer a las apuradas… puede volverse una verdadera pesadilla.
Esto es algo que difícilmente puedas prevenir si estás en ambientes de hosting que no controlás.
Todo lo que querías era hacer una prueba sencilla. Como para comprobar por tus propios medios, si todo lo que te dijeron de Docker era realmente así y en cambio… tenés que ponerte a escribir archivos de texto inentendibles.
Te tengo buenas noticias: hay varias herramientas muy simples que podés usar para generar los dichosos Dockerfile.
Te presento algunas.
Devilbox
http://devilbox.org/ te permite crear un entorno moderno y altamente personalizado con soporte para LAMP sobre docker.
Su instalación es sencilla:
git clone https://github.com/cytopia/devilbox
cd devilbox
cp env-example .env
docker-compose up
Y listo.
En tus manos un LAMP con Redis, Memcached, MongoDB, phpMyAdmin y un montón de herramientas de administración disponibles en http://localhost para hacer todo bien fácil.
Ah, y claro, si querés ver qué hay detrás de la magia, el archivo docker-compose.yml está a tu disposición en el directorio donde clonaste el repo.
PHPDocker
https://phpdocker.io/ es un sitio donde podés, a través de un wizard, configurar la imagen Docker que querés generar:
Una vez tenés todo donde debe estar descargás el archivo comprimido, lo descomprimís en el directorio que más te guste, docker-compose up y, voilá, tu entorno php Dockerizado está disponible con todos los condimentos que hayas seleccionado.
Sail
Sail es una herramienta perteneciente al framework Laravel. Si creas una nueva aplicación desde cero no tendrás más que ejecutar ./vendor/bin/sail up dentro del directorio raíz de tu proyecto para comenzar.
Una vez descargado y configurado todo podrás entrar en http://localhost y disfrtuar de tu nuevo entorno de trabajo con Docker.
Y, como siempre, el archivo docker-compose.yml estará allí para investigar/modificar.
Y un par más…
Un par de herramientas más que vale la pena conocer son:
Deck: una aplicación de escritorio basada en electron.js con la que puedes crear un ambiente entero para desarrollo. La iba a incluir en este listado pero al probarla falló la instalación… tal vez más adelante cuando esté más madura la agregue.
Es una historia que se repite una y otra vez: el sistema funciona perfecto en tu XAMPP pero cuando lo subís al hosting misteriosamente nadie puede registrarse ni logearse.
Es el mismo codigo en el webserver y en localhost, no entiendo por qué da error en uno sí y otro no
Dentro de xampp no me presenta problema, sólo en el hosting
Estoy trabajando con laravel y estoy tratando de generar un sitemap.xml automatico, pero cuando lo genero no me lo guarda en la carpeta publica, solo logre que me lo guarde en el raiz del cpanel y no puedo hacer que me lo genere dentro del public_html
¿Qué pasó? El código es exactamente el mismo, lo viste funcionar con tus propios ojos.
La única diferencia es que la URL, en lugar de empezar por http://localhost empieza por http://dominio.com, ¿cierto?
Me temo que las diferencias pueden ir bastante más allá de eso.
Para evitarte unas cuantas vueltas y dolores de cabeza te voy a contar las razones más frecuentes que llevan a este comportamiento.
Intentaré ir de lo más frecuente hacia lo menos.
Aquí voy.
La versión de PHP
La primera gran dependencia que toda aplicación php tiene es el propio intérprete.
Si desarrollás en forma local tenés instalado algún paquete que permite transformar esto:
Generalmente esta responsabilidad le corresponde al servidor web que tenés instalado en tu computadora (¿XAMPP tal vez?).
En tu hosting hay otro servidor web con una configuración similar pero no necesariamente idéntica.
PHP es un lenguaje que está en constante evolución, año a año salen nuevas versiones y, si bien se hace un esfuerzo muy grande por mantener la compatibilidad con versiones viejas, esto no siempre se logra.
Es por eso que algunas cosas que funcionan en una versión dejan de funcionar en las siguientes.
En conclusión: lo primero que deberías verificar es que tu versión de PHP local es igual a la que tiene tu hosting.
El archivo php.ini
Además del intérprete, php dispone de un archivo de configuración especial: php.ini.
En este archivo se definen ciertos parámetros que determinan detalles de cómo el intérprete realiza esa transformación.
Las variables que pueden configurarse a través de este archivo son ciertamente muchas. Algunas son más críticas que otras.
Un ejemplo muy simple es el de la directiva short_open_tag. Si su valor es "1" php no tendrá problemas en interpretar algo como:
Sin embargo, si este no es el caso, php dejará el texto tal como está, con lo cual, en lugar de transformarlo al HTML que esperás, el visitante de tu sitio verá el código.
En conclusión: si las cosas no funcionan vale la pena comparar los cotenidos de tu php.ini y los de tu hosting.
Las extensiones php
PHP como tal trae una cantidad de funcionalidad incorporada pero es muy común que ciertas funciones se dejen como opcionales por razones de eficiencia.
Para esos casos en los que se necesitan funcionalidades extras, php cuenta con el mecanismo de extensiones.
Si tu aplicación depende de alguna en particular, por ejemplo si necesita generar gráficos, es muy probable que requiera de la presencia de una extensión en particular en el ambiente de ejecución.
Si tu hosting no tiene instalada dicha extensión las cosas no van a funcionar como lo esperás.
En conclusión: verificá si tu aplicación requiere alguna extensión especial y, en tal caso, asegurate de que esté presente en tu hosting.
Los permisos
Para que una aplicación php pueda ejecutarse es preciso que el usuario que ejecuta el servidor web tenga ciertos permisos sobre el sistema de archivos.
Como mínimo, debe ser capaz de leer los archivos .php que componen tu aplicación.
En muchos casos también será necesario que pueda escribir en ciertos directorios.
En conclusión: validá que el usuario que ejecuta el webserver tenga los permisos necesarios para realizar las tareas que debe.
La topología de red
Típicamente cuando desarrollas en forma local la red donde tu aplicación vive es bastante sencillita: sólo existe localhost.
Esto quiere decir que todos los servicios de los que depende son accesibles a través de la dirección de red 127.0.0.1 (O el nombre localhost, lo que es lo mismo).
Más en concreto, si tu aplicación usa un MySQL como base de datos, es casi seguro que en algún lugar de tu código habrá una línea que se parece a:
El tema es que, en un entorno de hosting compartido donde hay muchos sitios hosteados en una misma computadora, es muy común que los servicios estén distribuidos en diferentes nodos de la red del proveedor.
Esto quiere decir que, al intentar conectar al servidor de MySQL a través de la dirección 127.0.0.1 se producirá un fallo.
En conclusión: verifica que las conexiones a servicios satélites se estén realizando utilizando las direcciones que correspnden a la red de tu hosting.
Las rutas absolutas
Un problema similar al anterior se da cuando se utilizan rutas absolutas.
Por ejemplo:
<?php
require_once '/var/www/html/libreria.php';
Puede funcionar en tu computadora porque existe en tu disco un archivo llamado libreria.php en un directorio /var/www/html/
Si bien es posible que dicho directorio exista también en tu hosting, es bastante probable que no sea así.
Si ese archivo está dentro del árbol de directorios de tu aplicación, una mejor forma de lograr el mismo objetivo es utilizar rutas relativas:
<?php
require_once __DIR__.'/libreria.php';
En conclusión: usá rutas relativas en lugar de absolutas.
La configuración del Apache
Otro elemento que puede afectar negativamente al funcionamiento de la aplicación es la configuración del servidor web. No es que necesariamente se trate a Apache pero lo más probable es que así sea, especialmente en un ambiente de hosting compartido.
Estas directivas afectan la forma en que el servidor web atiende los pedidos de tus visitantes.
Muchas aplicaciones php dependen de ciertas configuraciones existentes en el archivo .htaccess (Reglas de re-escritura por ejemplo).
En conclusión: valida que la configuración de tu Apache coincida con la de tu hosting.
El sistema operativo
El último de los sospechosos, que muchas veces es pasado por alto, es el sistema operativo sobre el que está montado el servidor.
Si estás desarrollando en Windows y tu hosting es un Linux hay buenas chances de que no todo funcione a la primera.
La diferencia para referenciar archivos (Cosas como c:\progams\file.php vs. /var/www/html/file.php) o el hecho de que para Linux un archivo llamado file.php no es el mismo que File.php mientras que para Windows sí son sólo algunas de las sutiles diferencias que pueden darte la idea de que el sistema está listo para subirse cuando en realidad estás a punto de abrir la caja de Pandora.
En conclusión: verificá que tu código no dependa de características particulares de un sistema operativo.
Cómo usar esta información
A lo largo de este post te conté las razones que más comúnmente hacen que un despliegue de una aplicación PHP sea más traumático de lo que debería.
Ahora que las conocés te recomiendo que las revises nuevamente y las uses a modo de lista de verificación para planificar tus próximas entregas.
El tema del código limpio está de moda, ¿no? Sí, pero también tiene sus fundamentos.
El código limpio es más comprensible y, por ende, mantenible en el tiempo.
Esto es especialmente importante cuando trabajas como parte de un equipo. Cuanto más claro sea el código, más fácil será para nuevos miembros hacerse productivos.
Todo el mundo está de acuerdo en esto pero lo cierto es que limpiar un código que lleva años acumulando capas de mugre puede ser una tarea titánica.
La respuesta más comúnmente usada ante estos escenarios es «¿para qué intentarlo cuando sé que no lo lograré?»
Cuando en realidad el escenario debería ser más parecido a:
Es decir: la opción de no limpiar no es realmente una opción… es cuestión de definir por dónde empezar.
Pero ahora, volviendo al código, hay algunas técnicas de limpieza más sencillas (¡y menos riesgosas!) que otras.
No es lo mismo cambiar el nombre de una variable que modificar la arquitectura.
Así que… vamos al punto. Mi enfoque para pasar de algo como esto:
<?php
declare(strict_types=1);
namespace GildedRose;
final class GildedRose
{
/**
* @param Item[] $items
*/
public function __construct(
private array $items
) {
}
public function updateQuality(): void
{
foreach ($this->items as $item) {
if ($item->name != 'Aged Brie' and $item->name != 'Backstage passes to a TAFKAL80ETC concert') {
if ($item->quality > 0) {
if ($item->name != 'Sulfuras, Hand of Ragnaros') {
$item->quality = $item->quality - 1;
}
}
} else {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
if ($item->name == 'Backstage passes to a TAFKAL80ETC concert') {
if ($item->sellIn < 11) {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
}
}
if ($item->sellIn < 6) {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
}
}
}
}
}
if ($item->name != 'Sulfuras, Hand of Ragnaros') {
$item->sellIn = $item->sellIn - 1;
}
if ($item->sellIn < 0) {
if ($item->name != 'Aged Brie') {
if ($item->name != 'Backstage passes to a TAFKAL80ETC concert') {
if ($item->quality > 0) {
if ($item->name != 'Sulfuras, Hand of Ragnaros') {
$item->quality = $item->quality - 1;
}
}
} else {
$item->quality = $item->quality - $item->quality;
}
} else {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
}
}
}
}
}
}
Consiste en ejecutar, casi mecánicamente, los siguientes pasos
1. Eliminar el hard-coding
Expresiones del tipo if ($item->quality < 50) { son bastante peligrosas. A priori surgen dos preguntas importantes:
¿Por qué 50 es un número especial?
¿Todos los 50 significan lo mismo?
En general, este tipo de números mágicos, tienen un sentido claro dentro del contexto de una aplicación. Probablemente cuando se escribió este código dicho contexto era claro pero, al no hacelo explícito, se hace muy complicado comprenderlo para alguien que lo ve por primera vez.
Una forma de evitar este problema es, simplemente, reemplazar este valor clavado por una constante de clase:
const MAX_ITEM_QUALITY = 50;
De esta forma se logra:
Dar significado a un número que a simple vista parece arbitrario
Permitir que si ese valor llega a cambiar en el futuro no sea necesario rastrear todos los lugares donde se usó el valor. Bastará con modificar la definición de la constante.
Lo mismo vale para valores tipo string como en el caso de $item->name != 'Backstage passes to a TAFKAL80ETC concert'
2. Extraer condicionales
El siguiente paso que suelo dar en este proceso es extrear las condiciones a métodos propios. Por ejemplo:
if ($item->name == 'Backstage passes to a TAFKAL80ETC concert') {
La idea es la misma. En lugar de, cada vez que se lea el código haya que concluir que el hecho de que name sea 'Backstage passes to a TAFKAL80ETC concert' significa que el ítem es un Backstage pass, dispongo de un método re-utilizable que me responde lo que realmente quiero saber.
Este ejemplo puede parecer trivial al comienzo, pero no lo es tanto.
¿Qué pasaría si, más adelante, la determinación del tipo de item viniese dada por alguna otra propiedad?
Este pequeño truco cobra todavía mayor importancia cuando las condiciones son complejas (cuando hay &&, || y/o muchos paréntesis)
3. Extraer cuerpo de loops
Del mismo modo, extraer los cuerpos de los bucles a métodos logra un resultado similar. Por un lado se reduce la complejidad del método y por otro se gana la posibilidad de re-utilizar la operación en diversos contextos.
En el ejemplo se hace muy visible el ciclo principal:
foreach ($this->items as $item) {
if ($item->name != 'Aged Brie' and $item->name != 'Backstage passes to a TAFKAL80ETC concert') {
if ($item->quality > 0) {
if ($item->name != 'Sulfuras, Hand of Ragnaros') {
$item->quality = $item->quality - 1;
}
}
} else {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
if ($item->name == 'Backstage passes to a TAFKAL80ETC concert') {
if ($item->sellIn < 11) {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
}
}
if ($item->sellIn < 6) {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
}
}
}
}
}
if ($item->name != 'Sulfuras, Hand of Ragnaros') {
$item->sellIn = $item->sellIn - 1;
}
if ($item->sellIn < 0) {
if ($item->name != 'Aged Brie') {
if ($item->name != 'Backstage passes to a TAFKAL80ETC concert') {
if ($item->quality > 0) {
if ($item->name != 'Sulfuras, Hand of Ragnaros') {
$item->quality = $item->quality - 1;
}
}
} else {
$item->quality = $item->quality - $item->quality;
}
} else {
if ($item->quality < 50) {
$item->quality = $item->quality + 1;
}
}
}
}
Que se reemplaza por:
public function updateQuality(): void { foreach ($this->items as $item) { $this->updateItem($item); } }
4. Eliminar cláusulas else
Siempre que sea posible, deberías preferir prescindir las cláusulas else en tu código.
Una forma de lograrlo es utilizar early return.
Por ejemplo:
public function canSeeMovie(Person $person): bool
{
if ($person->getAge() >= 18) {
...
} else {
return false;
}
}
Bien podría ser reemplazado por:
public function canSeeMovie(Person $person): bool
{
if ($person->getAge() < 18) {
return false;
}
...
}
Esto facilita mucho la lectura. Primero se ponen todas las validaciones o condiciones que podrían hacer que el método no pueda ejecutarse por completo y luego se codifica para el camino feliz.
Otro caso bastante parecido es el de usar valores por defecto:
public function getMaxSpeed(Car $car): int
{
if (in_array($car->getName(), ["Ferrari", "Porsche"])) {
$maxSpeed = 300;
} else {
$maxSpeed = 200;
}
return $maxSpeed;
}
Se transformaría en:
public function getMaxSpeed(Car $car): int
{
$maxSpeed = 200;
if (in_array($car->getName(), ["Ferrari", "Porsche"])) {
$maxSpeed = 300;
}
return $maxSpeed;
}
5. Eliminar variables temporales
Las variables temporales suelen crearse para aclarar cosas pero muchas veces su efecto es precisamente el contrario:
public function showMovieTo(Movie $movie, Person $person): void
{
$canSeeTheMovie = $this->canSeeMovie($person);
if ($canSeeTheMovie) {
$this->playMovie($movie);
}
}
En este pequeño ejemplo vemos que al llegar a la línea if ($canSeeTheMovie) { se necesita volver hacia atrás buscando la última asignación realizada a la variable $canSeeTheMovie para determinar qué hará este método.
Mucho más fácil de comprender es:
public function showMovieTo(Movie $movie, Person $person): void
{
if ($this->canSeeMovie($person)) {
$this->playMovie($movie);
}
}
Esta técnica no siempre es aplicable. En todo caso, lo segundo mejor por hacer es traer la definición y/o asignación de la variable lo más cerca de su uso que se pueda.
6. Renombrar, renombrar y renombrar
Este es un paso algo más sutil pero no menos importante.
El código que escribimos debe ser legible por humanos… muy probablemente por nosotros mismos en un futuro no muy lejano, de modo que es casi un deber moral escribirlo de un modo legible.
Encontrar buenos nombres para los elementos de nuestro código es una de las partes más complejas de programar, principalmente porque no hay recetas.
Algunos lineamientos que te puedo dar:
Escribir todo el código en inglés (No usar Spanglish)
No usar abreviaturas (Preferir $unusedTableNames a $utNm)
Poner nombres que revelen intención, no implementación (Prefereir $emptyAccounts sobre $emptyAccountsArray)
En el caso de clases o funciones, poner nombres que coincidan con el código.
Este último es, probablemente el punto más delicado. Durante la vida de un proyecto de software, es muy probable que un elemento cambie su definición conforme va pasando el tiempo.
Cuando eso sucede, es decir, cuando te toca modificar el código del cuerpo de un método (O de una clase en su totalidad), vale la pena preguntarte si el nombre que tenía antes de tu intervención sigue reflejando lo que el método hace (o lo que aporta mejor dicho) y, en caso de no ser así, es una buena idea darle un nuevo nombre más ligado a la realidad.
Y ahora… ¿qué?
Casi todos los cambios que nombré en este post se pueden realizar de forma bastante simple y segura utilizando un IDE, más adelante tocará encarar el refactor propiamente dicho:
Pero… antes de intentar avanzar por este camino hay que asegurarse de contar con buena covertura de tests.
Si te interesa este tema te recomiendo leer el libro de Código Limpio de Robert C. Martin.
Por último te animo a que practiques tus habilidades de refactoring con la kata Gilded Rose, de la que saqué el código que usé para el ejemplo principal.