HTTP Applikationsstruktur
Table of Contents
SetaFramework bietet eine Struktur an damit mehrere Applikationen nebeneinander her auf der selben Webseite koexistieren und zusammen arbeiten können.
HTTP PSRs
Das SetaFramework verwendet folgende HTTP bezogene PSRs:
- "psr/http-message" - PSR-7: HTTP Message Interfaces
- "psr/http-server-middleware" - PSR-15: HTTP Server Request Handlers
- "psr/http-server-handler" - PSR-15: HTTP Server Request Handlers
- "psr/http-factory" - PSR-17: HTTP Factories
Um PSR-7 zu implementieren verwenden wir momentan "zendframework/zend-diactoros" (verwenden es jedoch nur im HttpMessageHandler direkt).
Den PSR-15 Request Handler implementieren wir momentan selber in RequestHandler und ausgeführt wird der Request Handler von "zendframework/zend-httphandlerrunner" (wird ebenfalls nur im HttpMessageHandler verwendet).
Die PSR-17 HTTP Factories kommen aus "zendframework/zend-diactoros" und können über die Methode HttpMessageHandler::registerPSR17Aliases($di) im Applikation-Di registriert werden (mehr dazu unter Di). Zusätzlich zu den HTTP Factories aus PSR-17 haben wir noch eine eigene HTTP Factory die sämtliche Interfaces aus PSR-17 implementiert und noch einige weitere Helfermethoden anbietet: HttpFactory.
index.php
Das SetaFramework verwendet einen einzigen Einstiegspunkt für alle Requests die "index.php" (im "public" - Ordner vom Projekt). In den meisten Fällen wird dort grundlegend lediglich HttpBootstrap::start() aufgerufen. Dort wird hauptsächlich der Pfad zur Konfiguration mitgegeben (mehr zur Konfiguration im nächsten Kapitel bootstrap.ini) und eventuelle Middlewares.
bootstrap.ini
Die bootstrap.ini liegt normalerweise in dem Config-Ordner von dem Projekt. Die Position der bootstrap.ini muss in dem HttpBootstrap-Objekt bei der Erstellung mitgegeben werden (für üblich in der public/index.php) und weitere ConfigReader-Parameter, wie z.B. die Sections die gelesen werden sollen.
[global] ; request path prefix, web path to your public dir ; if public is your root dir leave it empty ; otherwise the path need to start with a slash and must not end with a slash bootstrap.requestPathPrefix = ; defines whether errors/exceptions will be shown bootstrap.debug = 0 ; defines whether the bootstrap should be logged bootstrap.logger.enabled = 1 ; when enabling this channel all php errors will no longer be logged over the 'bootstrap' channel ; instead 'phpError' will be used; default 0 bootstrap.phpError.logger.enabled = 1 bootstrap.unexpectedOutput.logger.enabled = 1 bootstrap.unexpectedOutput.logger.handler = unexpectedOutput ; Error controller for uncatched errors and bootstrap errors. ; Need to be an instance of AbstractController and need the method "indexAction". ;bootstrap.errorController.file = @../ErrorController.php ;bootstrap.errorController.className = \test\Simple\ErrorController ; Default values for the router if no route matches ;bootstrap.defaultRouterValues.application = ;bootstrap.defaultRouterValues.controller = ;bootstrap.defaultRouterValues.action = cache.handlers = @@cacheHandlers.ini logger.handlers = @@logHandlers.ini ; Applications - Start ; Applications - End [development : global] bootstrap.debug = 1 [productive : global]
Config\Reader\Ini
Der IniConfigReader unterstützt einige Besonderheiten:
- Sections können voneinander erben - "[productive : global]" beinhaltet alle Konfigurationen aus "[global]"
- test = @./config.ini - wird aufgelöst zu dem Absoluten Pfad ausgehend von der Position der Config-Datei
- test = @@./config.ini - wird die Konfigurationsdatei lesen und in diese Konfiguration integrieren unter dem angegeben Key
- @@ = @@./config.ini - wird die Konfigurationsdatei lesen und in diese Konfiguration integrieren direkt in der aktuellen Ebene
- dsn = "$MYSQL_DSN$" - Füllt dsn mit dem Inhalt aus der Umgebungsvariable "MYSQL_DSN"
Request Path Prefix
Es ist möglich SetaFramework-Projekte in einem Unterordner laufen zu lassen, damit jedoch Pfade richtig generiert und ausgelesen werden ist es jedoch wichtig, dass der Unterordner mit konfiguriert wird. Wenn also z.B. die URI zur Index-Datei "http://hello.world/test/project/public/index.php" ist der RequestPathPrefix "/test/project/public".
Der RequestPathPrefix muss entweder komplett leer sein ODER mit einem "/" anfangen und ohne "/" enden.
Logger
Die Konfiguration für den Logger ist hier beschrieben.
Cache
Die Konfiguration für den Cache ist hier beschrieben.
Applikationen
SetaFramework bestehen aus mehreren (meist voneinander unabhängigen) Applikationen. Jede Applikation muss einzeln in der bootstrap.ini konfiguriert werden.
Die Reihenfolge der Applikationen in der bootstrap.ini ist entscheidend für die Reihenfolge der Initialisierung (also auch die Reihenfolge im Router).
; Klassenname der Applications-Klasse - muss die Klasse "\com\setasign\SetaFramework\Application\AbstractHttp" erweitern applications.SimpleApp.applicationClassName = \com\setasign\SimpleApp\SimpleApp ; Pfad zum Applikationsordner - wird nur benötigt, wenn die Applikation nicht über den Autoloader gefunden werden kann applications.SimpleApp.applicationPath = @../applications/SimpleApp/ ; Konfiguration der Applikation - optional applications.SimpleApp.config = @@./simpleApp.ini
Desweiteren ist zu beachten dass der Key unter "applications" wichtig ist. Also in den oberen Beispiel "SimpleApp". Unter diesem Namen wird die Application im GlobalDi registriert und dieser Name muss einmalig sein.
Applikationen
Eine Applikation muss eine Applikationsklasse besitzen, die AbstractHttpApplication implementiert. Folgende Methoden können implementiert werden (die abstract-Methoden müssen natürlich implementiert werden):
getApplicationDirectory()
Path to application dir, usally equal to DIR
getDi()
getViewHelperNamespaces()
setupRoutes()
Setup Routes for the Application
Router
In AbstractHttp::setupRoutes() können die Routen konfiguriert werden, welche die Applikation benötigt. Folgende Route stehen zur Verfügung:
- File - einfaches matchen eines Pfades auf ein festes Ziel
- Regex - matches eines Pfades über einen Regex auf ein dynamisches Ziel
- Param - matchen über POST oder GET Parameter auf ein festes Ziel
- Multi - zusammenfassen von mehreren Routen zu einer (beinhaltet selber auch einen Router)
Hier ein einfaches Beispiel zur Verwendung:
/**
* @inheritdoc
*/
public function setupRoutes(Router $router): void
{
$router->appendRoute('testRoute', new File('download.pdf', [
'application' => $this->getName(),
'controller' => 'Export',
'action' => 'downloadPdf'
], $this->getRequestPathPrefix()));
}
Dieses Beispiel würde dafür sorgen, dass ein Request auf "/download.pdf" den "Export"-Controller mit der Methode "downloadPdfAction" aufrufen würde.
Der Router würde folgende Werte zurück geben: ['application' => 'TestApp', 'controller' => 'Export', 'action' => 'downloadPdf']
Das Auswerten und Weiterleiten des Requests vom Ergebnis des Routers zu dem Controller selber passiert im ControllerDispatcher.
Controller
Ein Controller muss das ControllerInterface implementieren. Falls der Controller lediglich JSON ausgibt, kann direkt der AbstractJsonController erweitert werden. Wie bereits oben im Router-Beispiel zu sehen, müssen Actions die von außen aufgerufen werden mit "Action" aufhören und sollten nichts zurückgeben (void).
In Controllern ist es möglich sich automatisch vom Di Objekten injecten zu lassen. Dies geht im Konstruktor und in der Action selber. Entscheidend ist dabei der Klassen-TypeHint.
