10. Applicazioni a livelli
10.1. Introduzione

Ora esploreremo come strutturare un'applicazione PHP a livelli:

In questo diagramma, il livello di sinistra avvia l'utilizzo del livello di destra. I ruoli dei livelli sono i seguenti:
- [1]: Il livello denominato [DAO] (Data Access Objects) gestisce le interazioni con gli archivi di dati esterni [4] (file, database, servizi web, ecc.). Questo livello è talvolta chiamato [DAL] (Data Access Layer), un termine che descrive meglio il ruolo del livello. Questo livello può leggere dati [3] o scrivere dati [2]. Viene richiamato dal livello [business] [6] e restituisce i risultati ad esso [7];
- [5]: un livello denominato [business] che contiene procedure "aziendali", alle quali vengono forniti tutti i dati necessari. Questo è generalmente il livello più stabile di un progetto perché non dipende da come vengono acquisiti i dati. I dati provengono da due fonti:
- [9]: dati forniti dallo script PHP;
- [6,7]: dati richiesti al livello [DAO].
- [8]: lo script principale funge da orchestratore. In un'applicazione console, esso:
- crea i livelli [business] e [DAO];
- eseguirà l'algoritmo dell'applicazione. Questo algoritmo agisce come un orchestratore: non contiene alcuna logica "business" né codice di accesso ai dati. Lo script principale si limita a richiamare le procedure del livello [business] [9]. Ignora completamente il livello [DAO] e i dati esterni. Può fornire dati al livello [business] [9]. In un'applicazione console, questi dati possono provenire da file di configurazione o dall'utente dello script. Riceve i risultati [10] dal livello [business]. Potrebbe essere necessario memorizzare determinati risultati: per farlo, utilizza nuovamente le procedure del livello [business], che a sua volta si rivolgerà al livello [DAO] per eseguire l'operazione;
- tra i risultati, lo script principale può ricevere delle eccezioni. È suo compito gestire le eccezioni che vengono propagate da tutti i livelli;
Questa architettura a livelli è progettata per facilitare l'evoluzione dell'applicazione. A tal fine, ogni livello implementa un'interfaccia. Supponiamo che:
- il livello [DAO] sia implementato da una classe [DAO] che implementa un'interfaccia [IDao];
- il livello [business] sia implementato da una classe [Business] che implementa un'interfaccia [IBusiness];
Le procedure nel livello [business] utilizzeranno quindi l'interfaccia [IDao] anziché la classe [Dao]. Ciò consente di aggiornare il livello [Dao] senza influire sul livello [Business]. Supponiamo che nella versione 1 il livello [Dao1] utilizzi dati provenienti da un database. A seguito di un aggiornamento, questi dati sono ora forniti da un servizio web nella versione 2, [Dao2]. Ci assicureremo che entrambe le classi [Dao1] e [Dao2] implementino la stessa interfaccia [IDao] e generino le stesse eccezioni. Se ciò viene verificato, il livello [Business] che opera con l'interfaccia [IDao] rimarrà invariato;
Lo stesso ragionamento si applica al livello [Business].
Diamo un'occhiata a un'implementazione che utilizza classi e interfacce:

L'applicazione implementerà la seguente struttura a livelli:

10.2. Oggetti scambiati tra i livelli
Normalmente, i livelli si scambiano vari oggetti. In questo caso, si scambieranno la seguente classe [Person.php]:
<?php
// a person
class Personne {
// identifier
private $id;
// manufacturer
public function __construct(int $id) {
$this->id = $id;
}
// toString
public function __toString(): string {
return "[Personne($this->id)]";
}
}
Commenti
- riga 6: la classe Person ha un solo attributo, il suo identificatore [$id]. Supporremo che questo identifichi una persona univoca in un data warehouse;
- righe 9–11: il costruttore che ci permette di creare un oggetto [Person] utilizzando il suo ID;
- righe 14–16: il metodo [__toString] che visualizza l'identificatore della persona;
10.3. Livello [DAO]
L'interfaccia [IDao] implementata dal livello [dao, 1] è la seguente [IDao.php]:
<?php
// layer [dao] ------------------------
interface IDao {
// recovery of a person from an external warehouse
// pass the person's login
public function get(int $id): Personne;
// saving a person in an external warehouse
public function save(Personne $p): void;
}
Questa interfaccia è implementata dalla seguente classe [Dao1] [Dao1.php]:
<?php
class Dao1 implements IDao {
// saving a person in an external warehouse
public function save(Personne $p): void {
print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
}
// recovery of a person from an external warehouse
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);
}
}
Commenti
- La classe non interagisce con un data warehouse. Ci limitiamo a visualizzare messaggi per monitorare l'esecuzione del codice (righe 7 e 12);
- Riga 13: restituiamo una persona con l'ID passato come parametro al metodo (riga 11);
Implementiamo anche l'interfaccia [IDao] con la seguente classe [Dao2] [Dao2.php]:
<?php
class Dao2 implements IDao {
// saving a person in an external warehouse
public function save(Personne $p): void {
print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
}
// recovery of a person from an external warehouse
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 classe [Dao2] è simile alla classe [Dao1], tranne per il fatto che abbiamo modificato i messaggi visualizzati.
10.4. Livello [Business]
Il livello [business, 2] fornisce la seguente interfaccia [IMetier] [IMetier.php]:
<?php
// business] layer ------------------------
interface IMétier extends IDao {
// we use a person identified by his id
public function doSomething(Personne $p): void;
}
Commenti
- L'interfaccia [IMétier] estende l'interfaccia [IDao]. Questo non è affatto obbligatorio. Lo facciamo qui perché l'esempio è semplice;
- riga 7: il metodo [doSomething] è specifico del livello [Business];
L'interfaccia [IMetier] sarà implementata dalla seguente classe [Métier] [Métier.php]:
<?php
class Métier implements IMétier {
// layer [dao]
private $dao;
// getter / setter
public function setDao(IDao $dao): void {
$this->dao = $dao;
}
public function getDao(): IDao {
return $this->dao;
}
// a person is exploited
public function doSomething(Personne $p): void {
// treatment
print "Métier : Traitement métier de la personne $p\n";
}
// safeguarding the individual
public function save(Personne $p): void {
print "[Métier] : Sauvegarde de la personne $p\n";
// the [dao] layer is asked to make the backup
$this->dao->save($p);
}
public function get(int $id): Personne {
print "[Métier] : récupération de la personne d'identifiant $id\n";
// we ask for the person in the diaper [dao]
$personne = $this->dao->get($id);
return $personne;
}
}
Commenti
- riga 5: il livello [business] deve avere un riferimento al livello [DAO] per poter utilizzare i suoi metodi;
- Righe 8–10: il metodo [setDao] consente al livello [business] di ricevere un riferimento al livello [DAO]. Si noti che il tipo di parametro è [IDao]. Ciò significa che il livello [business] sarà in grado di lavorare con qualsiasi classe che implementi l'interfaccia [IDao]. Se si passa da un livello [Dao1] a un livello [Dao2] ed entrambi implementano l'interfaccia [IDao], non è necessario riscrivere il livello [business];
- righe 17–20: implementazione di [IMetier::doSomething];
- righe 23–27: implementazione di [IMetier::save]. Questo metodo deve salvare una persona in un data warehouse. Il livello [business] non può farlo. Richiede al livello [DAO] di eseguire questo salvataggio;
- righe 29–34: implementazione di [IMetier::get]. Questo metodo deve recuperare da un data warehouse la persona il cui ID gli viene passato come parametro. Il livello [business] non sa come farlo. Richiede al livello [DAO] di svolgere il lavoro (riga 32);
Conclusione
Ogni volta che il livello [business] deve accedere ai dati memorizzati nel data warehouse, deve passare attraverso il livello [DAO], che è stato creato proprio per accedere a questi dati.
10.5. Script principale
Scriveremo due script che fungeranno da orchestratori per questa applicazione. Il primo [main1.php] utilizzerà il livello [Dao1], mentre il secondo [main2.php] utilizzerà il livello [Dao2]. Vogliamo dimostrare che ciò non ha alcun impatto sul codice nel livello [Business].
Lo script [main1.php] è il seguente:
<?php
// strict adherence to function parameters
declare (strict_types=1);
// inclusion classes and 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";
// test ----------------
// layer creation
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// using the [business] layer
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Commenti
- Ricapitoliamo alcuni punti: lo script [main1.php] funge da orchestratore dell'applicazione. Crea la struttura a livelli dell'applicazione (righe 15–17) e poi inizia a comunicare con il livello [business] (righe 19–21). La struttura a livelli è la seguente:

Secondo questo diagramma, lo script [main1.php] dovrebbe interagire solo con il livello [business]. Non dovrebbe interagire con il livello [DAO], anche se ciò è teoricamente possibile.
I risultati dell'esecuzione sono i seguenti:
[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]
Commenti
- La riga 10 del codice ha causato la scrittura delle righe 1 e 2 dei risultati;
- La riga 20 del codice ha causato la scrittura della riga 3 dei risultati;
- La riga 21 del codice ha causato la scrittura delle righe 4 e 5 dei risultati;
Lo script [main2.php] è il seguente:
<?php
// strict adherence to function parameters
declare (strict_types=1);
// inclusion classes and 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";
// test ----------------
// layer creation
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// using the [business] layer
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);
Commenti
- righe 36–38: l'architettura a livelli ora utilizza il livello [Dao2];
I risultati dell'esecuzione sono i seguenti:
[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]
Il livello [Dao1] simula l'accesso a un database locale, mentre il livello [Dao2] simula l'accesso a un database remoto. Finché questi due livelli aderiscono all'interfaccia [IDao], possiamo vedere che il codice nel livello [Business] non ha dovuto essere modificato.
Applicheremo quanto appena appreso all'esercizio di calcolo delle imposte che funge da filo conduttore.