10. Aplicaciones en capas
10.1. Introducción

Ahora vamos a ver 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] (Data Access Objects) se encarga de los intercambios con almacenes de datos externos [4] (archivos, bases de datos, servicios web…). Esta capa se denomina a veces [DAL] (Data Access Layer), un término que describe mejor la función de la capa. Esta capa puede leer datos [3] o escribir en [2]. Es solicitada por la capa [métier] [6] y le devuelve resultados [7];
- [5]: una capa denominada [métier] que agrupa procedimientos «de negocio» a los que se les proporcionan todos los datos que necesitan. Suele ser la capa más estable de un proyecto, ya que no depende de la forma en que se adquieren 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 director de orquesta. En una aplicación de consola, se encargará de:
- 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 los 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 [9]. En una aplicación de consola, estos pueden proceder de archivos de configuración o del usuario del script. Recibe resultados [10] de la capa [métier]. Es posible que necesite almacenar algunos resultados: para ello, vuelve a utilizar los procedimientos de la capa [métier], que a su vez se dirigirán a la capa [dao], la cual 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 modificar la capa [Métier]. Supongamos que, en una version 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 version 2, [Dao2]. Se asegurará 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 por 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;
// fabricante
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 {
// búsqueda de una persona en un almacén externo
// se introduce el identificador de la persona
public function get(int $id): Personne;
// guardar una persona en un almacén externo
public function save(Personne $p): void;
}
Esta interfaz está implementada por la siguiente clase [Dao1] [Dao1.php]:
<?php
class Dao1 implements IDao {
// guardar 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 en 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. Nos limitamos a mostrar 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 en 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] amplía 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 {
// tratamiento
print "Métier : Traitement métier de la personne $p\n";
}
// guardar la persona
public function save(Personne $p): void {
print "[Métier] : Sauvegarde de la personne $p\n";
// se solicita a la capa [dao] que realice el guardado
$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 asignar 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 una persona en un almacén de datos. La capa [métier] no sabe hacer esto. Se dirige a la capa [dao] para que realice este almacenamiento;
- líneas 29-34: implementación de [IMetier::get]. Este método debe recuperar de un almacén de datos 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
En cuanto la capa [métier] necesita acceder a los datos almacenados en el almacén de datos, debe pasar por la capa [dao], que se ha creado para acceder a dichos datos.
10.5. Script principal
Vamos a escribir dos scripts, que actuarán como 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 tiene ninguna incidencia en el 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, a continuación, comienza a interactuar con la capa [métier] (líneas 19-21). La estructura por 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ó la escritura de las líneas 1 y 2 de los resultados;
- la línea 20 del código provocó la escritura de la línea 3 de los resultados;
- la línea 21 del código ha provocado la escritura de 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 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 utiliza ahora 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. Siempre que estas dos capas respeten la interfaz [IDao], vemos que no ha sido 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.