Skip to content

10. Geschichtete Anwendungen

10.1. Einleitung

Image

Wir werden nun untersuchen, wie man eine PHP-Anwendung in Schichten strukturiert:

Image

In diesem Diagramm löst die linke Ebene die Nutzung der rechten Ebene aus. Die Rollen der Ebenen sind wie folgt:

  • [1]: Die als [DAO] (Data Access Objects) bezeichnete Schicht verwaltet die Interaktionen mit externen Datenspeichern [4] (Dateien, Datenbanken, Webdienste usw.). Diese Schicht wird manchmal als [DAL] (Data Access Layer) bezeichnet, ein Begriff, der die Rolle der Schicht besser beschreibt. Diese Schicht kann Daten lesen [3] oder schreiben [2]. Sie wird von der [Business]-Schicht [6] aufgerufen und gibt Ergebnisse an diese zurück [7];
  • [5]: eine Schicht namens [business], die „Geschäftsprozesse“ enthält, denen alle benötigten Daten zur Verfügung gestellt werden. Dies ist in der Regel die stabilste Schicht eines Projekts, da sie nicht davon abhängt, wie die Daten beschafft werden. Die Daten stammen aus zwei Quellen:
    • [9]: Daten, die vom PHP-Skript bereitgestellt werden;
    • [6,7]: Daten, die von der [DAO]-Schicht angefordert werden.
  • [8]: Das Hauptskript fungiert als Koordinator. In einer Konsolenanwendung wird es:
    • die Schichten [business] und [DAO] erstellen;
    • den Algorithmus der Anwendung ausführen. Dieser Algorithmus fungiert wie ein Koordinator: Er enthält keine „Business“-Logik oder Datenzugriffscode. Das Hauptskript ruft lediglich die Prozeduren der [Business]-Schicht [9] auf. Es ignoriert die [DAO]-Schicht und externe Daten vollständig. Es kann der [Business]-Schicht [9] Daten bereitstellen. In einer Konsolenanwendung können diese Daten aus Konfigurationsdateien oder vom Skriptbenutzer stammen. Es empfängt Ergebnisse [10] von der [Business]-Schicht. Möglicherweise muss es bestimmte Ergebnisse speichern: Dazu nutzt es erneut die Prozeduren der [Business]-Schicht, die wiederum die [DAO]-Schicht ansprechen, um die Aufgabe auszuführen;
    • Unter den Ergebnissen kann das Hauptskript Ausnahmen erhalten. Es ist seine Aufgabe, Ausnahmen zu behandeln, die aus allen Schichten weitergeleitet werden;

Diese Schichtenarchitektur ist darauf ausgelegt, die Weiterentwicklung der Anwendung zu erleichtern. Zu diesem Zweck implementiert jede Schicht eine Schnittstelle. Angenommen, dass:

  • die [DAO]-Schicht durch eine [DAO]-Klasse implementiert wird, die eine [IDao]-Schnittstelle implementiert;
  • die [Business]-Schicht durch eine [Business]-Klasse implementiert wird, die eine [IBusiness]-Schnittstelle implementiert;

Die Prozeduren in der [Business]-Schicht verwenden dann die [IDao]-Schnittstelle anstelle der [Dao]-Klasse. Dadurch kann die [Dao]-Schicht aktualisiert werden, ohne die [Business]-Schicht zu beeinträchtigen. Angenommen, in Version 1 verwendet die [Dao1]-Schicht Daten aus einer Datenbank. Nach einer Aktualisierung werden diese Daten in Version 2 nun von einem Webdienst, [Dao2], bereitgestellt. Wir stellen sicher, dass sowohl die [Dao1]- als auch die [Dao2]-Klasse dieselbe [IDao]-Schnittstelle implementieren und dieselben Ausnahmen auslösen. Wenn dies überprüft wurde, bleibt die [Business]-Schicht, die mit der [IDao]-Schnittstelle arbeitet, unverändert;

Die gleiche Argumentation gilt für die [Business]-Schicht.

Sehen wir uns eine Implementierung mit Klassen und Schnittstellen an:

Image

Die Anwendung wird die folgende Schichtstruktur implementieren:

Image

10.2. Zwischen Ebenen ausgetauschte Objekte

Normalerweise tauschen Ebenen verschiedene Objekte aus. Hier tauschen sie die folgende Klasse [Person.php] aus:


<?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)]";
  }
 
}

Kommentare

  • Zeile 6: Die Person hat nur ein Attribut, ihre Kennung [$id]. Wir gehen davon aus, dass diese eine eindeutige Person in einem Data Warehouse identifiziert;
  • Zeilen 9–11: Der Konstruktor, mit dem wir ein [Person]-Objekt anhand seiner ID erstellen können;
  • Zeilen 14–16: die Methode [__toString], die die Kennung der Person anzeigt;

10.3. [DAO]-Schicht

Die von der [dao, 1]-Schicht implementierte [IDao]-Schnittstelle sieht wie folgt aus [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;
}

Diese Schnittstelle wird von der folgenden [Dao1]-Klasse [Dao1.php] implementiert:


<?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);
  }
 
}

Kommentare

  • Die Klasse interagiert nicht mit einem Data Warehouse. Wir zeigen lediglich Meldungen an, um die Ausführung des Codes zu verfolgen (Zeilen 7 und 12);
  • Zeile 13: Wir geben eine Person mit der ID zurück, die als Parameter an die Methode übergeben wurde (Zeile 11);

Außerdem implementieren wir die Schnittstelle [IDao] mit der folgenden Klasse [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);
  }
 
}

Die Klasse [Dao2] ähnelt der Klasse [Dao1], mit dem Unterschied, dass wir die angezeigten Meldungen geändert haben.

10.4. [Business]-Schicht

Die [business, 2]-Schicht stellt die folgende [IMetier]-Schnittstelle [IMetier.php] bereit:


<?php
 
// business] layer ------------------------
interface IMétier extends IDao {
 
  // we use a person identified by his id
  public function doSomething(Personne $p): void;
}

Kommentare

  • Die Schnittstelle [IMétier] erweitert die Schnittstelle [IDao]. Dies ist keineswegs zwingend erforderlich. Wir tun dies hier, weil das Beispiel einfach ist;
  • Zeile 7: Die Methode [doSomething] ist spezifisch für die [Business]-Schicht;

Die Schnittstelle [IMetier] wird von der folgenden [Métier]-Klasse [Métier.php] implementiert:


<?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;
  }
 
}

Kommentare

  • Zeile 5: Die [Business]-Schicht muss über eine Referenz zur [DAO]-Schicht verfügen, um deren Methoden nutzen zu können;
  • Zeilen 8–10: Die Methode [setDao] ermöglicht es, der [Business]-Schicht eine Referenz auf die [DAO]-Schicht bereitzustellen. Beachten Sie, dass der Parametertyp [IDao] ist. Das bedeutet, dass die [Business]-Schicht mit jeder Klasse arbeiten kann, die die [IDao]-Schnittstelle implementiert. Wenn wir von einer [Dao1]-Schicht zu einer [Dao2]-Schicht wechseln und beide die [IDao]-Schnittstelle implementieren, muss die [Business]-Schicht nicht neu geschrieben werden;
  • Zeilen 17–20: Implementierung von [IMetier::doSomething];
  • Zeilen 23–27: Implementierung von [IMetier::save]. Diese Methode muss eine Person in einem Data Warehouse speichern. Die [business]-Schicht kann dies nicht tun. Sie ruft die [DAO]-Schicht auf, um diese Speicherung durchzuführen;
  • Zeilen 29–34: Implementierung von [IMetier::get]. Diese Methode muss aus einem Data Warehouse die Person abrufen, deren ID ihr als Parameter übergeben wird. Die [Business]-Schicht weiß nicht, wie dies zu bewerkstelligen ist. Sie beauftragt die [DAO]-Schicht mit der Ausführung dieser Aufgabe (Zeile 32);

Fazit

Wann immer die [Business]-Schicht auf Daten zugreifen muss, die im Data Warehouse gespeichert sind, muss sie die [DAO]-Schicht durchlaufen, die für den Zugriff auf diese Daten geschaffen wurde.

10.5. Hauptskript

Wir werden zwei Skripte schreiben, die als Orchestratoren für diese Anwendung dienen. Das erste [main1.php] wird die [Dao1]-Schicht verwenden, während das zweite [main2.php] die [Dao2]-Schicht verwendet. Wir möchten zeigen, dass dies keine Auswirkungen auf den Code in der [Business]-Schicht hat.

Das Skript [main1.php] lautet wie folgt:


<?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);

Kommentare

  • Lassen Sie uns einige Punkte zusammenfassen: Das Skript [main1.php] fungiert als Koordinator der Anwendung. Es erstellt die Schichtstruktur der Anwendung (Zeilen 15–17) und beginnt dann mit der Kommunikation mit der [business]-Schicht (Zeilen 19–21). Die Schichtstruktur sieht wie folgt aus:

Image

Gemäß diesem Diagramm sollte das Skript [main1.php] nur mit der [business]-Schicht interagieren. Es sollte nicht mit der [DAO]-Schicht interagieren, auch wenn dies theoretisch möglich ist.

Die Ergebnisse der Ausführung lauten wie folgt:


[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]

Kommentare

  • Zeile 10 des Codes führte dazu, dass die Zeilen 1 und 2 der Ergebnisse geschrieben wurden;
  • Zeile 20 des Codes führte dazu, dass Zeile 3 der Ergebnisse geschrieben wurde;
  • Zeile 21 des Codes führte dazu, dass die Zeilen 4 und 5 der Ergebnisse geschrieben wurden;

Das Skript [main2.php] lautet wie folgt:


<?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);

Kommentare

  • Zeilen 36–38: Die mehrschichtige Architektur nutzt nun die [Dao2]-Schicht;

Die Ergebnisse der Ausführung lauten wie folgt:


[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]

Die [Dao1]-Schicht simuliert den Zugriff auf eine lokale Datenbank, während die [Dao2]-Schicht den Zugriff auf eine Remote-Datenbank simuliert. Solange diese beiden Schichten die [IDao]-Schnittstelle einhalten, sehen wir, dass der Code in der [Business]-Schicht nicht geändert werden musste.

Wir werden das gerade Gelernte auf die Steuerberechnungsübung anwenden, die uns als Leitfaden dient.