Skip to content

10. Applicazioni a livelli

10.1. Introduzione

Image

Ora esploreremo come strutturare un'applicazione PHP a livelli:

Image

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:

Image

L'applicazione implementerà la seguente struttura a livelli:

Image

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:

Image

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.