Cómo escribir código PHP claro y fácil de mantener

El lenguaje PHP tiene la reputación de que los programas hechos en él son una masa de spaghetti: todo el código está mezclado y enrevesado, con funciones para extraer de la base de datos junto al código que visualiza cosas, etc. Lo malo es que, si te pones a descargar software hecho en PHP, muchos de esos programas son así. Sospecho que el motivo de esto es que los autores de esos programas no saben hacerlo mejor, no que no quieran, así que voy a intentar arreglarlo con este artículo.

(Hace años escribí otro titulado “MVC en tres pasos, en PHP y sin objetos (y nada más)”, pero ahora que he cambiado la web he preferido escribir este nuevo artículo, en el que no explico MVC, sino principios generales de separación de capas).

Introducción

Vamos a empezar con un resumen rápido:

  1. ¡No escribáis sentencias SQL directamente en el código! Aisladlas en funciones individuales, y llamadlas desde donde sea necesario.
  2. ¡No mezcléis código PHP y HTML! Primero obtened y calculad los datos que sean necesarios, almacenadlos en variables, y luego incluid las variables en el código HTML.

Es bastante corto, pero es mejor así, que cuanto más corto sea más fácil será de recordar :)

Aislar el código de la base de datos

En muchos programas en PHP, el código que accede a las bases de datos está mezclado con el resto del código. Allá se pone el programa a hacer sus cosas, y en el momento en el que necesita un dato, se incluyen las instrucciones necesarias para obtenerlo de la base de datos, y punto.

Esto conlleva bastantes problemas: es complicado seguir el flujo del programa, es muy difícil reutilizar así el código, y si se necesita modificar el programa para utilizar otra base de datos, puede ser muy complicado.

Para evitar esto, lo que hay que hacer es aislar los accesos a base de datos dentro de un conjunto de funciones determinado. Por ejemplo, si el programa es un weblog, tendríamos una función para obtener una lista de historias, otra función para obtener una historia determinada, otra para obtener una lista de comentarios de una historia, otra para grabar una historia, otra para borrar una historia, etc.

Es decir, en lugar de esto:

<h1>Historias</h1>
<?php
$sql = "SELECT titulo, fecha, texto FROM historias ORDER BY fecha DESC LIMIT 10";
$res = mysql_query($res);
while ($historia = mysql_fetch_obj($res)) { ?>
  <h2><?php print htmlentities($historia.titulo) ?></h2>
  <p>Publicado el <?php print strftime($historia.fecha) ?></p>
  <div><?php print $historia.texto ?></div>
<?php } ?>

Tendríamos esto:

<?php $historias = db_lista_historias(); ?>
<h1>Historias</h1>
<?php foreach ($historias as $historia) { ?>
  <h2><?php print htmlentities($historia.titulo) ?></h2>
  <p>Publicado el <?php print strftime($historia.fecha) ?></p>
  <div><?php print $historia.texto ?></div>
<?php } ?>

Como podéis ver, los accesos a base de datos se hacen íntegramente dentro de funciones dedicadas, que devuelven resultados en el formato más adecuado, y se procesan después, en lugar de procesar los datos según van llegando.

Separar el código PHP y el HTML

El otro vicio que veo muy a menudo en programas en PHP es mezclar el código que hace los cálculos propios del programa con el código que genera lo que va a ver el usuario. De aquí es de donde viene gran parte de la fama de “lenguaje spaghetti” de PHP.

No obstante, con un poquito de autodisciplina, es posible separar totalmente el código de la “vista”. Alguna gente utiliza un sistema de plantillas para forzar esa separación, pero es totalmente posible hacerlo sin utilizar más que PHP.

La idea es, en lugar de obtener y calcular los datos a medida que son necesarios para visualizarlos, éstos se obtienen y calculan de antemano y luego sólo se incluye el código necesario para visualizarlos. Por supuesto, este código puede incluir algunas instrucciones de control de flujo (if, while, for, ...) y funciones para formatear fechas y números, pero nada más complicado.

En lugar de:

<html>
<body>
<p>Sieve of Eratosthenes</p>
<p>Prime: 1<br /><?php $num = $_GET['num'];
if ($num < 1) $num = 100;
$sieve = array();
for ($i = 1; $i <= $num; $i++) {
  $sieve[$i] = 1;
}
for ($step = 2; $step < $num; $step++) {
  if ($sieve[$step]) {
    print "Prime: $step<br />";
  }
  for ($i = 2 * $step ; $i <= $num; $i += $step) {
    $sieve[$i] = 0;
  }
}
?></p>
</body>
</html>

Es mejor tener algo parecido a:

<?php
  $num = $_GET['num'];
  if ($num < 1) $num = 100;

  $sieve = array();
  for ($i = 1; $i <= $num; $i++) {
    $sieve[$i] = 1;
  }
  for ($step = 2; $step < $num; $step++) {
    for ($i = 2 * $step ; $i <= $num; $i += $step) {
      $sieve[$i] = 0;
    }
  }
?>
<html>
<body>
<p>Sieve of Eratosthenes</p>
<p><?php
for ($i = 1; $i <= $num; $i++) {
  if ($sieve[$i]) {
?>Prime: <?php print $i ?><br /><?php
  }
} ?></p>
</body>
</html>

Como podéis ver, en el primer caso se mezcla código PHP y HTML porque se visualizan los números primos a medida que se van calculando.

Sin embargo, en el segundo todos los cálculos se realizan antes de empezar a generar código HTML: el único código PHP en la segunda parte del programa sirve para recorrer la criba y visualizar sólo los números primos.

Este uso de "for" y de "if" es perfectamente legítimo, igual que lo sería de "while" o de funciones para formatear datos, tales como "strftime" o "htmlentities".

Mantenerlo todo bien separado

Una vez que sabemos cómo dividir bien el código entre la parte que accede a la base de datos, la parte que hace los cálculos y la parte que se encarga de visualizar cosas, podemos encargarnos de dividir esas tres partes aún más, disponiéndolas en ficheros separados. Al hacer eso, nos aseguraremos de que esa división de “papeles” sea permanente. Además, así será más fácil modificar el aspecto del programa, por ejemplo, sin tener que tocar sus “tripas”, y viceversa.

Imaginad que tenemos un script PHP con esta forma:

<?php
/* Programa que hace cosas, copyright yo */

function db_obtener_comentarios($sid) {
  // ... código que descarga los comentarios de la base de datos
  return $comentarios;
}

$sid = $_POST['sid'];
$comentarios = db_obtener_comentarios($sid);
?>

<html>
<head>
<!-- código que visualiza los comentarios -->
</head>
</html>

Para dividir el código en varios ficheros separados, nada más fácil que poner todo el código que gestiona la base de datos en un fichero, llamado (por ejemplo) basedatos.inc.php y el código que visualiza los comentarios en un fichero llamado (por ejemplo) comentarios.tpl.php. Con eso, sólo necesitaréis realizar “includes” de esos ficheros en el lugar oportuno, ¡y listo!

<?php
/* Programa que hace cosas, copyright yo */

include_once('basedatos.inc.php');

$sid = $_POST['sid'];
$comentarios = db_obtener_comentarios($sid);

include('comentarios.tpl.php');
?>

Para más sofisticación, poned los ficheros .tpl.php en un subdirectorio; así será más fácil implementar temas, o simplemente será más fácil actualizar el aspecto de una página web (te copias el directorio a tu ordenador, lo modificas, lo copias a un nuevo directorio en el servidor, y luego actualizas la configuración para que el software busque las plantillas en ese directorio o cambias el nombre del directorio, y listo: todo el aspecto del programa cambia a la vez).

Conclusión

Como habéis podido ver, la idea (muy simple) en mi artículo es que las distintas funciones del programa (acceso a la base de datos, cálculos, visualización del resultado) deberían estar separadas en capas bien definidas, e incluso esbozo una manera en que se pueda conseguir.

A ver si a partir de ahora veo menos programas en PHP con todo mezclado :)

Comentarios

Saludos Buen Articulo, php es

Saludos Buen Articulo, php es bueno apesar de lo que digan, casi siempre por ignorancia, siguiendo estos pasos tal vez ganemos criticas positivas. Tratemos de hacer codigo mas limpio amigos programadores.

Gracias

Hola,

gracias por el tutorial esta todo bastante bien explicado.

Pero.. ¿es a esto a lo que llaman el Modelo Vista Controlador?

Un saludo

No exactamente MVC

No, son sólo una colección de principios para la separación de capas que MVC cumple, pero no es exactamente MVC.

Buen Aporte

Para los que vienen programando con cosigo estructurado cuesta un poco de trabajo no juntar html y php pero es lo mejor. Gracias por el aporte!

INTERESANTE APORTE

Aporte sencillo y fàcil de entender para aquellos que empezamos a usar este tipo de patrones como MVC.
Agradecemos el post y esperamos mayor info en el futuro.