10. Aplicaciones en capas
10.1. Introduction

Ahora veremos cómo estructurar una aplicación PHP en capas:

En este esquema, es la capa de la izquierda la que toma la iniciativa de utilizar la capa de la derecha. La función de las capas es la siguiente:
- [1]: la capa denominada [dao] (Objetos de Acceso a Datos) se encarga de las interacciones con almacenes de datos externos [4] (archivos, bases de datos, servicios web…). A veces, a esta capa se le denomina [DAL] (Capa de acceso a datos), un término que describe mejor su función. Esta capa puede leer datos de [3] o escribir datos en [2]. Es solicitada por la capa [métier] [6] y le devuelve resultados [7];
- [5]: una capa denominada [métier] que reúne procedimientos «de negocio» a los que se les proporcionan todos los datos que necesitan. Por lo general, es la capa más estable de un proyecto, ya que no depende de la forma en que se obtienen los datos. Estos tienen dos fuentes:
- [9]: los datos proporcionados por el script PHP;
- [6,7]: los datos solicitados a la capa [dao].
- [8]: el script principal es el que coordina todo. En una aplicación de consola, este:
- crea las capas [métier] y [dao];
- ejecuta el algoritmo de la aplicación. Este algoritmo es el de un director de orquesta: no contiene ningún código «de negocio» ni de acceso a datos. El script principal se limita a llamar a los procedimientos de las capas [métier] y [9]. Ignora por completo la capa [dao] y los datos externos. Puede proporcionar a la capa [métier] datos de la capa [9]. En una aplicación de consola, estos pueden provenir de archivos de configuración o del usuario del script. Recibe resultados [10] de la capa [métier]. Es posible que necesite almacenar ciertos resultados: para ello, vuelve a utilizar los procedimientos de la capa [métier], los cuales se dirigirán a la capa [dao], que se encargará de realizar el trabajo;
- entre los resultados, el script principal puede recibir excepciones. Su función es gestionar las excepciones que se remiten desde todas las capas;
Esta división en capas tiene como objetivo facilitar la evolución de la aplicación. Para ello, cada capa implementa una interfaz. Supongamos que:
- la capa [dao] está implementada por una clase [Dao] que implementa una interfaz [IDao];
- la capa [métier] está implementada por una clase [Métier] que implementa una interfaz [IMétier];
Los procedimientos de la capa [métier] utilizarán entonces la interfaz [IDao] en lugar de la clase [Dao]. Esto permite actualizar la capa [Dao] sin afectar la capa [Métier]. Supongamos que, en una versión 1, la capa [Dao1] utiliza datos de una base de datos. Tras una actualización, estos datos ahora son proporcionados por un servicio web en una versión 2, [Dao2]. Nos aseguraremos de que las dos clases [Dao1] y [Dao2] implementen la misma interfaz [IDao] y que lancen las mismas excepciones. Si esto se verifica, la capa [Métier], que trabaja con la interfaz [IDao], permanecerá sin cambios;
El mismo razonamiento se aplica a la capa [Métier].
Veamos una implementación con clases e interfaces:

La aplicación implementará la siguiente estructura en capas:

10.2. Objetos intercambiados entre capas
Normalmente, las capas intercambian diversos objetos. En este caso, intercambiarán la siguiente clase [Personne.php]:
<?php
// una persona
class Personne {
// identificador
private $id;
// creador
public function __construct(int $id) {
$this->id = $id;
}
// toString
public function __toString(): string {
return "[Personne($this->id)]";
}
}
Comentarios
- línea 6: la persona solo tiene un atributo, su identificador [$id]. Supondremos que este identifica a una persona única en un almacén de datos;
- líneas 9-11: el constructor que permite crear un tipo [Personne] con su identificador;
- líneas 14-16: el método [__toString] que muestra el identificador de la persona;
10.3. Capa [dao]
La interfaz [IDao] implementada por la capa [dao, 1] es la siguiente [IDao.php]:
<?php
// capa [dao] ------------------------
interface IDao {
// recogida de una persona en un almacén externo
// se ingresa la identificación de la persona
public function get(int $id): Personne;
// guardar a una persona en un almacén externo
public function save(Personne $p): void;
}
Esta interfaz es implementada por la siguiente clase [Dao1]: [Dao1.php]:
<?php
class Dao1 implements IDao {
// guardar a una persona en un almacén externo
public function save(Personne $p): void {
print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
}
// recuperación de una persona de un almacén externo
public function get(int $id): Personne {
print "[Dao1] : Récupération de la personne d'identité ($id) en base de données [locale]\n";
return new Personne($id);
}
}
Comentarios
- la clase no interactúa con un almacén de datos. Solo se muestran mensajes para seguir el desarrollo del código (líneas 7 y 12);
- línea 13: se devuelve correctamente una persona con el identificador pasado como parámetro al método (línea 11);
También implementamos la interfaz [IDao] con la siguiente clase [Dao2] [Dao2.php]:
<?php
class Dao2 implements IDao {
// guardar una persona en un almacén externo
public function save(Personne $p): void {
print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
}
// Recuperación de una persona de un almacén externo
public function get(int $id): Personne {
print "[Dao2] : Récupération de la personne d'identité ($id) en base de données [distante]\n";
return new Personne($id);
}
}
La clase [Dao2] es similar a la clase [Dao1], salvo que hemos modificado los mensajes que se muestran.
10.4. Capa [métier]
La capa [métier, 2] ofrece la siguiente interfaz [IMétier] [IMetier.php]:
<?php
// capa [métier] ------------------------
interface IMétier extends IDao {
// se procesa a una persona identificada por su ID
public function doSomething(Personne $p): void;
}
Comentarios
- La interfaz [IMétier] extiende la interfaz [IDao]. Esto no es en absoluto obligatorio. Lo hacemos aquí porque el ejemplo es sencillo;
- línea 7: el método [doSomething] es específico de la capa [Métier];
La interfaz [IMétier] será implementada por la siguiente clase [Métier]: [Métier.php]:
<?php
class Métier implements IMétier {
// capa [dao]
private $dao;
// getter / setter
public function setDao(IDao $dao): void {
$this->dao = $dao;
}
public function getDao(): IDao {
return $this->dao;
}
// se explota a una persona
public function doSomething(Personne $p): void {
// procesamiento
print "Métier : Traitement métier de la personne $p\n";
}
// guardar los datos de la persona
public function save(Personne $p): void {
print "[Métier] : Sauvegarde de la personne $p\n";
// se le solicita a la capa [dao] que realice el almacenamiento
$this->dao->save($p);
}
public function get(int $id): Personne {
print "[Métier] : récupération de la personne d'identifiant $id\n";
// se solicita la persona a la capa [dao]
$personne = $this->dao->get($id);
return $personne;
}
}
Comentarios
- línea 5: la capa [métier] debe tener una referencia a la capa [dao] para poder utilizar los métodos de esta última;
- líneas 8-10: el método [setDao] permite asignarle a la capa [métier] la referencia de la capa [dao]. Cabe señalar que el tipo del parámetro es [IDao]. De este modo, la capa [métier] podrá trabajar con cualquier clase que implemente la interfaz [IDao]. Si pasamos de una capa [Dao1] a una capa [Dao2] y ambas implementan la interfaz [IDao], no es necesario reescribir la capa [métier];
- líneas 17-20: implementación de [IMetier::doSomething];
- líneas 23-27: implementación de [IMetier::save]. Este método debe guardar un registro de persona en un almacén de datos. La capa [métier] no sabe hacer eso. Recurre a la capa [dao] para realizar este almacenamiento;
- líneas 29-34: implementación de [IMetier::get]. Este método debe recuperar de un almacén de datos el registro de la persona cuyo identificador se le pasa como parámetro. La capa [métier] no sabe hacer eso. Recurre a la capa [dao] para que realice la tarea (línea 32);
Conclusión
Cada vez que la capa [métier] necesite acceder a los datos almacenados en el almacén de datos, debe pasar por la capa [dao], que se creó precisamente para acceder a esos datos.
10.5. Script principal
Vamos a escribir dos scripts, que serán los coordinadores de esta aplicación. El primero, [main1.php], utilizará la capa [Dao1], mientras que el segundo, [main2.php], utilizará la capa [Dao2]. Queremos demostrar que esto no afecta en absoluto al código de la capa [Métier].
El script [main1.php] es el siguiente:
<?php
// Cumplimiento estricto de los parámetros de las funciones
declare (strict_types=1);
// Inclusión de clases e interfaces
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao1.php";
require_once __DIR__."/Métier.php";
// prueba ----------------
// creación de capas
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// uso de la capa [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Comentarios
- Recordemos algunos puntos: el script [main1.php] es el director de orquesta de la aplicación. Crea la estructura en capas de la aplicación (líneas 15-17) y luego comienza a interactuar con la capa [métier] (líneas 19-21). La estructura en capas es, de hecho, la siguiente:

Según este esquema, el script [main1.php] solo debe dirigirse a la capa [métier]. No debe dirigirse a la capa [dao], aunque teóricamente sea posible.
Los resultados de la ejecución son los siguientes:
[Métier] : récupération de la personne d'identifiant 4
[Dao1] : Récupération de la personne d'identité (4) en base de données [locale]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao1] : Sauvegarde de la personne [Personne(4)] en base de données [locale]
Comentarios
- la línea 10 del código provocó que se escribieran las líneas 1 y 2 de los resultados;
- la línea 20 del código provocó que se escribiera la línea 3 de los resultados;
- la línea 21 del código provocó que se escribieran las líneas 4 y 5 de los resultados;
El script [main2.php] es el siguiente:
<?php
// Cumplimiento estricto de los parámetros de las funciones
declare (strict_types=1);
// Inclusión de clases e interfaces
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao2.php";
require_once __DIR__."/Métier.php";
// prueba ----------------
// creación de las capas
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// uso de la capa [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Comentarios
- líneas 36-38: la estructura en capas ahora utiliza la capa [Dao2];
Los resultados de la ejecución son los siguientes:
[Métier] : récupération de la personne d'identifiant 4
[Dao2] : Récupération de la personne d'identité (4) en base de données [distante]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao2] : Sauvegarde de la personne [Personne(4)] en base de données [distante]
La capa [Dao1] simula un acceso a una base de datos local, mientras que la capa [Dao2] simula un acceso a una base de datos remota. Mientras estas dos capas cumplan con la interfaz [IDao], vemos que no fue necesario modificar el código de la capa [Métier].
Vamos a aplicar lo que acabamos de aprender al ejercicio de cálculo de impuestos que nos sirve de hilo conductor.