Zum Inhalt springen

Framework DotApp Aktualisiert: 2026-08-27


Über den Autor

Mein Name ist Štefan Miščík und ich bin Senior-Fullstack-Webentwickler bei Dotsystems s.r.o. (WEB)

Designziele

DotApp ist kein weiteres gewöhnliches PHP-Framework, von denen es Hunderte gibt. Es ist von Grund auf geschrieben, ohne extra Composer-Abhängigkeiten und ohne Laravel- oder Symfony-Stack darunter. Der Kernel ist ultraleicht und hoch skalierbar: Routing, Rendering, Sicherheit und die Datenbankschicht teilen sich eine Laufzeitumgebung und ein gemeinsames Regelwerk.

Die Anwendungslogik liegt in Modulen. Jedes Modul besitzt seine Routen, Controller, Middleware, Views und Assets — damit Teams ohne Kollisionen arbeiten und AIRULES einem KI-Agenten denselben Vertrag geben. Sicherheit steckt in der Laufzeit (geschützte Eingaben, fo-rm, verschlüsselte Kennungen), nicht in einem Zusatzpaket.

Die Arbeit an DotApp begann 2014 als vollständige Anwendungsarchitektur, nicht als Hülle um fremden Vendor-Code.


Neuigkeiten

Kernel-Updates seit DotApp 2.0. Alte Module bleiben funktionsfähig. Datumsliste: What's new in DotApp PHP Framework.

URL {not:} — Präfixe vor dem Match ausschließen (NEW – 26. 8. 2026)

Der Router hat den Operator {not:mask|mask}. Ausschlüsse laufen vor dem positiven Muster (strpos / substr auf einem Prefix mit abschließendem *). Ein öffentlicher Catch-all bleibt ein String und bleibt trotzdem außerhalb von /admin, /api/v1 und /assets.


Router::get('/{path*}{not:/admin*|/api/v1*|/assets*}', 'Shop:Public@page!');
            
  • Die Reihenfolge zählt: /admin/login gegen /{path*}{not:/admin*} endet am Exclude, nicht an {path*}.
  • /admin* vs. /admin/*: /admin/* trifft nicht genau /admin. Wenn auch der Admin-Index draußen bleiben muss, {not:/admin*} verwenden.
  • Dieselbe Syntax auf den Wake-Listen von Module::initializeRoutes() / Listeners::initializeRoutes(). Ein öffentliches /{path*}, das /admin erst in initializeCondition streicht, weckt das Modul trotzdem — {not:} gehört auf den Wake-String.

Docs: AIRULES/03-MODULES-AND-ROUTING.md (Pfadparameter). Anleitung: URL-Selektoren {not:}.

Trigger, Extender und unabhängige Listener-Routen (NEW – 22. 8. 2026)

Event-Bus und Modullader sind gewachsen, ohne alte Module zu brechen. Passende Listener registrieren sich immer, bevor passende Module initialisieren.

  • Events::triggerWithVeto() und die neue Klasse Dotsystems\App\Parts\Veto — ein expliziter Stopp vor einer umkehrbaren Aktion. Gewöhnliches trigger() ignoriert Listener-Rückgaben weiterhin, einschließlich eines Veto-Objekts.
  • Dotsystems\App\Parts\Extender — bewusst gewählte, optionale Ersetzung einer relevanten Ausgabe für diesen Request. Der Eigentümer verwendet exists() / call(); ein gewöhnliches Ergebnis ersetzt sie, während isOriginal() nur beim eindeutigen original()-Marker mit der ursprünglichen Logik fortfährt. In Listeners::register() nur dotapp.module.shop.loading abonnieren; extend() in diesem Callback mit einem Controller-String wie 'Loyalty:Pricing@quote!' aufrufen.
  • Listeners::initializeRoutes() — ein Listener kann auf eigenen URL-Masken aufwachen, ohne initialize() des Moduls auszuführen. Lassen Sie die Methode weg, erbt er die Modul-Map.
  • php dotapper.php --optimize-modules schreibt Optimizer-Format v2 ($modules, $listeners, $modulesAutoLoaderVersion = 2). Eine v1-Datei, die nur $modules exportiert, bleibt kompatibel.
  • dotapp.catchall läuft weiterhin zuerst bei jedem anderen trigger() (und bei triggerWithVeto()), damit Sie alle Events an einer Stelle sehen. Nur Debug — von catchall nicht persistieren.

Anleitungen: Trigger with veto · unabhängige Listener-Routen · Extender · dotapp.catchall.


Konventionen

Die öffentliche API verwendet Fassaden, Modul-Controller und eine kleine Menge an Konventionen:

Fassaden

Fassaden (Router, Route, DB, Request, Renderer::new()) sind die öffentliche API für Kerndienste. Anwendungsrouten werden in der module.init.php jedes Moduls registriert.

Router- und Route-Fassaden

Verwenden Sie die Fassaden Router und Route (sie sind Aliase voneinander) in module.init.php. Beispiel: Router::get('/helloworld', 'HelloWorld:Home@index!', Router::STATIC_ROUTE).


// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get('/helloworld', fn() => 'Hello World', Router::STATIC_ROUTE);
Route::get('/helloworld', fn() => 'Hello World', Router::STATIC_ROUTE);
Router::get('/helloworld', 'HelloWorld:Home@index!', Router::STATIC_ROUTE);
Route::get('/helloworld', 'HelloWorld:Home@index!', Router::STATIC_ROUTE);
                    
DB-Fassade

Verwenden Sie die DB-Fassade. Abfragen erfolgen mit DB::module('RAW').


DB::module('RAW')->q(function ($qb) {
    $qb->select(['id', 'title'])->from('helloworld_notes')->where('id', '=', 1);
})->all();
                    
Request-Fassade

In Controllern lesen Sie die aktuelle Anfrage aus dem Argument $request. Die Request-Fassade steht zur Verfügung, wenn Sie sich außerhalb eines Controller-Callbacks befinden. Eingehende Werte sind automatisch geschützt. data() ist die escapte Kopie (sicher zum Ausgeben). data(true) ist das Original — Passwörter, Entschlüsselung, Persistenz. Kanal-Felder: $request->data(true)['data'].


Request::getPath(); // Get the current request path
Request::getMethod(); // Get the HTTP method (e.g., GET, POST)
Request::data();        // protected/escaped copy — OK to print
Request::data(true);    // original values — passwords, decrypt, compare
$request->form(['POST'], 'myForm', function ($request) {
    return 'Form submitted!';
}, function () {
    return 'Invalid form';
});
                    


Die DotApp-Instanz

Bevorzugen Sie Fassaden: Router::, DB::, Renderer::new(), Config::, Events::, Bridge::listen, DSM::use(). Wenn Sie den Kernel selbst brauchen (DI bind/resolve, unprotect, ajaxReply, reaktives Polling), holen Sie ihn mit:


use \Dotsystems\App\DotApp;
$dotApp = DotApp::DotApp();
            

Registrieren Sie Routen mit Router::get (und den übrigen Verb-Methoden) innerhalb der Methode initialize($dotApp) des Moduls.



Wichtige Funktionen von DotApp

Einfachheit ohne Kompromisse

DotApp verbindet intuitives Design mit hoher Leistung. Sie benötigen keine komplexen Setups oder übermäßige Konfiguration – definieren Sie einfach Routen und Module, und alles andere organisiert sich selbst. Routen werden nur dort verarbeitet, wo sie benötigt werden, und es sind keine zusätzlichen Schritte erforderlich, um die Leistung zu halten – alles läuft automatisch und effizient.


// Example of simplicity when working with the DotApp framework
namespace Dotsystems\App\Modules\HelloWorld\Controllers;

class TestController1 extends \Dotsystems\App\Parts\Controller {
    public static function testMiddlewareFn($request) {
        return "Hello " . $request->body(); // Adds text at the beginning
    }
    
    public static function mainFn($request) {
        return $request->body() . "World"; // Adds text at the end
    }
}

// Simple controller call
// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get("/home", "HelloWorld:TestController1@mainFn!", Router::STATIC_ROUTE)
    ->before("HelloWorld:TestController1@testMiddlewareFn!");

// Result for /home: "Hello World"
            
Fokus auf geringen Ressourcenverbrauch

DotApp hält den Speicherbedarf auf einem Minimum – statt massive Routenstrukturen und Konfigurationen zu laden, verarbeitet es nur, was gerade benötigt wird. Das bedeutet schnelleren Start und gute Leistung auch auf schwächeren Servern.

Schnelle Routenverarbeitung

DotApp filtert intelligent nur relevante Module und deren Routen und vermeidet unnötige Suchvorgänge. Das Ergebnis ist schnelles Laden selbst bei Tausenden von Routen.

Beispiel

Demonstration der Routing-Geschwindigkeit: Vor der Anzeige dieser Seite wurden automatisch 1000 eindeutige statische und 1000 eindeutige dynamische, absichtlich unsortierte Routen zufällig zum Router hinzugefügt. Ziel war es, das schnelle Laden trotz 2000 zusätzlicher, unnötiger Routen zu zeigen. Keine davon passt zur aktuellen URL, sodass alle den Matching-Prozess des Routers durchlaufen müssen.


$p = rtrim((string) Config::module('Docs', 'prefix'), '/') ?: '/documentation';

for ($i = 0; $i < 1000; $i++) {
    $path = $p . '/_routa' . $i;
    Router::any($path, function () use ($path) {
        return "This is route: " . $path;
    }, Router::STATIC_ROUTE);
}

for ($i = 0; $i < 1000; $i++) {
    $path = $p . '/_routa' . $i . '(?:/{language})?';
    Router::any($path, function () use ($path) {
        return "This is route: " . $path;
    });
}

// Try it out: /documentation/_routa7
                            


Die Anzeige der Seite inklusive Routenerstellung, Routing und Codegenerierung über das Vorlagensystem dauerte:

PHP version: 8.3.15 fpm-fcgi
Script execution time: 0.068665 s
Memory used by the script: 1.05 MB
Peak memory usage: 1.66 MB
Loaded modules: 6

Modulare Effizienz mit bidirektionaler Verbindung

DotApp verarbeitet nur die Routen des aktiven Moduls und spart so Ressourcen. Module können einander laden: Der Listener eines Moduls kann ein anderes laden, und ein übergeordnetes Modul kann Kindmodule aktivieren. Kombinationen bleiben in module.listeners.php und initializeRoutes() explizit. Ein Listener kann eigene Masken deklarieren und so ohne initialize() des Moduls aufwachen.

Kaskadierendes Laden von Modulen

Wenn ein Modul von einem anderen abhängt (z. B. BBB benötigt XXX), lädt DotApp XXX automatisch, bevor BBB abgeschlossen wird. Das gewährleistet Zuverlässigkeit – keine Fehler durch fehlende Abhängigkeiten – und hält das System schlank, indem nur das Nötige geladen wird.

Dynamische Abhängigkeitsverwaltung über Trigger und Listener

Jedes Modul hat Trigger wie init.start, loading, loaded und weitere, auf die Listener reagieren. Zum Beispiel kann der Listener dotapp.module.Module1.loading das Laden von Modul 2 auslösen, wenn Modul 1 aktiv ist. Die Funktion load() stellt sicher, dass ein Modul nur einmal geladen wird, unabhängig davon, ob kaskadierend (von oben nach unten) oder bidirektional (von unten nach oben).

Hinweis: Triggernamen sind unabhängig von Groß- und Kleinschreibung, daher sind dotapp.module.Module1.loading und Dotapp.Module.Module1.Loading gleichwertig. Für Konsistenz empfehlen wir jedoch das Format dotapp.module.ClassName.eventName.

Jeder Events::trigger() und Events::triggerWithVeto() außer dotapp.catchall selbst löst zuerst dieses Debug-Event aus, damit Sie alle Trigger an einer Stelle sehen. Nur zum Debuggen verwenden. Eine Exception dort überspringt das benannte Event. Vertrag und Beispiel: Events and listeners — dotapp.catchall. Stopp vor der Aktion: Trigger with veto (Klasse Veto). Listener können eigene Routen haben: unabhängige Listener-Routen.


use Dotsystems\App\DotApp;
use Dotsystems\App\Parts\Events;

Events::on("dotapp.module.shop.loading", function () {
    DotApp::DotApp()->module("Cart")->load();
});
            
Automatische Auflösung von Abhängigkeiten und DI

Module und ihre Abhängigkeiten werden automatisch geladen – definieren Sie die Logik einfach in initializeCondition() oder in Listenern. Dependency Injection (DI) ist einfach und effizient – Dienste werden registriert (z. B. singleton), und DotApp liefert sie dort, wo sie benötigt werden, ohne unnötigen Overhead.

Registrieren Sie Dienste in der initialize($dotApp) des Moduls mit singleton / bind. Controller sind public static und nehmen $request entgegen.


public function initialize($dotApp) {
    \Dotsystems\App\DotApp::DotApp()->singleton('cache', function () {
        return new CacheService();
    });
}

namespace Dotsystems\App\Modules\HelloWorld\Controllers;

class Home extends \Dotsystems\App\Parts\Controller {
    public static function index($request) {
        $cache = \Dotsystems\App\DotApp::DotApp()->resolve('cache');
        return "Hello World";
    }
}
            
Der erste Callback gewinnt

Für jede URL wird nur der erste passende Callback behalten – nachfolgende Registrierungsversuche werden ignoriert. Das steigert die Leistung und verhindert Konflikte.


Router::get('/documentation/test1', "HelloWorld:Home@index!");
Router::get('/documentation/test1', function () { return "Ignored"; });
// Only the first definition is used
            
Skalierbarkeit für kleine und große Projekte

DotApp eignet sich gleichermaßen für kleine Websites und komplexe Anwendungen – es behält niedrige Anforderungen und hohe Geschwindigkeit unabhängig vom Projektumfang bei. Große Module können in kleinere Teile aufgeteilt werden, die bei Bedarf rekursiv geladen werden.

Kein unnötiger Overhead

DotApp konzentriert sich auf das Wesentliche – schnelles Routing, minimaler Ressourcenverbrauch und einfache Nutzung. Es belastet Sie nicht mit Funktionen, die Sie nicht brauchen.

Vorlagensystem

Views und Layouts liegen im Modul. Geben Sie mit {{ var: $title }} aus, binden Sie Fragmente mit {{ layout:partials/header }} ein, und rendern Sie aus einem Controller mit Renderer::new()->module('HelloWorld')->setView('hello'). Vollständige Referenz: Vorlagensystem.

DotApp Bridge

Live-Verbindung – eine Brücke zwischen Frontend und Backend. Verwenden Sie einfach folgenden Code:

<button {{ dotbridge:on(click)="newsletter.subscribe(newsletter.email)" }}>Subscribe</button>

und auf der PHP-Seite in der initialize() des Moduls:


use Dotsystems\App\Parts\Bridge;
use Dotsystems\App\Parts\Router;

$urls = ['/newsletter', '/newsletter/'];
Bridge::listen($urls, "newsletter.subscribe", function ($request) {
    $email = $request->data(true)['data']['newsletter.email'] ?? '';
    return ['ok' => true, 'email' => $email];
}, Router::STATIC_ROUTE);
            

Die Schaltfläche wird automatisch mit der PHP-Funktion verknüpft; die Dokumentation stellt die vielfältigen Möglichkeiten vor.

Beispiel für generierten Code:


<button  dotbridge-key="dQd4RCMpSjMTcicsV-rRuhaHK3h9nSJ" dotbridge-id="Lf9osqlyEtMLYxEihTqTqbmZ8XeG6sLt6s4xcLNhu86Oic5202610041200120012000101001223de2084f74693256d6735bd008969bd" dotbridge-event="click" dotbridge-data="vq0uWzh3271Yg/wcD+xqEXJDZ1p2MGFIWThpMmlBRGRteDlvVWs4bHJvdmc3bHBERW5ORGlqaDZLOW89" dotbridge-data-id="dCjhIYrk5T13Mcfq2kEPyXdnTzk2eFQ0VURTbXR6ZkZBQ21hd1NvVlc2RVp4cVZWd1NIcGlHT0F6VU5Za3AxSjdoUlQwdDFGRjlGRTAyV28wbXA0NVdWMDNXdS91R1pMRTQ4NEtJQnBPOUd0Tm5BUDBzUmF2Vzg0cXQ0PQ==" dotbridge-function="newsletter.subscribe" dotbridge-inputs="newsletter.email">Subscribe</button>
            
HTML

DotApp ist maßgeschneidert für Entwickler, die ein effizientes Werkzeug ohne Ballast wollen. Es bietet Geschwindigkeit, geringe Anforderungen und eine Einfachheit, die die Arbeit erleichtert. Es ist ein Framework, das beweist, dass weniger mehr sein kann – mit Ergebnissen, die für sich sprechen.

Probieren Sie DotApp aus und überzeugen Sie sich selbst!