Framework DotApp Updated: 2026-08-19
About the Author
My name is Štefan Miščík and I am a senior fullstack web developer at Dotsystems s.r.o. (WEB)
Design goals
DotApp is a PHP framework for applications of every size. Application logic lives in modules. Each module owns its routes, controllers, middleware, views, and assets.
Work on DotApp started in 2014 as a complete application architecture: routing, rendering, security, and the database layer share one runtime and one set of conventions.
Conventions
The public API uses facades, module controllers, and a small set of conventions:
Facades
Facades (Router, Route, DB, Request, Renderer::new()) are the public API for core services. Application routes are registered in each module’s module.init.php.
Router and Route Facades
Use the Router and Route facades (they are aliases of each other) in module.init.php. Example: 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 Facade
Use the DB facade. Query with DB::module('RAW').
DB::module('RAW')->q(function ($qb) {
$qb->select(['id', 'title'])->from('helloworld_notes')->where('id', '=', 1);
})->all();
Request Facade
In controllers, read the current request from the $request argument. The Request facade is available when you are outside a controller callback.
Incoming values are auto-protected. data() is the escaped copy (safe to print). data(true) is the original — use it for passwords, decrypt, and persist.
Channel fields: $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';
});
The DotApp instance
Prefer facades: Router::, DB::, Renderer::new(), Config::, Events::, Bridge::listen, DSM::use(). When you need the kernel itself (DI bind/resolve, unprotect, ajaxReply, reactive polling), resolve it with:
use \Dotsystems\App\DotApp;
$dotApp = DotApp::DotApp();
Register routes with Router::get (and the other verb methods) inside the module’s initialize($dotApp) method.
Key Features of DotApp
Simplicity Without Compromise
DotApp combines intuitive design with high performance. You don’t need complex setups or excessive configurations – just define routes and modules, and everything else manages itself. Routes are processed only where needed, and no extra steps are required to maintain performance – it’s all automatic and efficient.
// 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"
Focus on Low Resource Consumption
DotApp keeps memory demands to a minimum – instead of loading massive route structures and configurations, it processes only what’s currently needed. This means faster startup and great performance even on weaker servers.
Fast Route Processing
DotApp intelligently filters only relevant modules and their routes, eliminating unnecessary searches. The result is swift loading even with thousands of routes.
Example
Demonstration of routing speed: Before displaying this page, 1000 unique static and 1000 unique dynamic, deliberately unorganized routes were automatically added to the router at random. The goal was to showcase fast loading despite 2000 extra unnecessary routes. None of them match the current URL, ensuring that all must go through the router’s matching process.
$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
Displaying the page, including route creation, routing, and code generation using the templating system, took:
Script execution time: 0.057193 s
Memory used by the script: 981.27 KB
Peak memory usage: 1.57 MB
Loaded modules: 6
Modular Efficiency with Bidirectional Connectivity
DotApp processes only the routes of the active module, saving resources. Modules can load each other: one module’s listener can load another, and a parent module can activate children. Combinations stay explicit in module.listeners.php and initializeRoutes().
Cascading Module Loading
If a module depends on another (e.g., BBB needs XXX), DotApp automatically loads XXX before completing BBB. This ensures reliability – no errors due to missing dependencies – and keeps the system lightweight by loading only what’s necessary.
Dynamic Dependency Management via Triggers and Listeners
Each module has triggers like init.start, loading, loaded, and more, which listeners respond to. For example, the dotapp.module.Module1.loading listener can trigger the loading of module 2 if module 1 is active. The load() function ensures a module is loaded only once, whether cascading (top-down) or bidirectional (bottom-up).
Note: Trigger names are case-insensitive, so dotapp.module.Module1.loading and Dotapp.Module.Module1.Loading are equivalent, but we recommend using the format dotapp.module.ClassName.eventName for consistency.
use Dotsystems\App\DotApp;
use Dotsystems\App\Parts\Events;
Events::on("dotapp.module.Shop.loading", function () {
DotApp::DotApp()->module("Cart")->load();
});
Automatic Dependency Resolution and DI
Modules and their dependencies load automatically – just define the logic in initializeCondition() or listeners. Dependency Injection (DI) is simple and efficient – services are registered (e.g., singleton), and DotApp delivers them where needed without unnecessary overhead.
Register services in the module’s initialize($dotApp) with singleton / bind. Controllers are public static and take $request.
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";
}
}
First Callback Wins
For each URL, only the first matching callback is retained – subsequent registration attempts are ignored, boosting performance and preventing conflicts.
Router::get('/documentation/test1', "HelloWorld:Home@index!");
Router::get('/documentation/test1', function () { return "Ignored"; });
// Only the first definition is used
Scalability for Small and Large Projects
DotApp is ideal for small sites and complex applications alike – it maintains low demands and high speed regardless of project scope. Large modules can be split into smaller parts that load recursively as needed.
No Unnecessary Overhead
DotApp focuses on the essentials – fast routing, minimal resource usage, and ease of use. It doesn’t burden you with features you don’t need.
Template system
Views and layouts live in the module. Print with {{ var: $title }}, include fragments with {{ layout:partials/header }}, and render from a controller with Renderer::new()->module('HelloWorld')->setView('hello'). Full reference: Template system.
DotApp Bridge
Live connection – a bridge between frontend and backend. Just use simple code:
<button {{ dotbridge:on(click)="newsletter.subscribe(newsletter.email)" }}>Subscribe</button>
and on the PHP side, in the module’s initialize():
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);
The button is automatically linked to the PHP function, with rich possibilities to be introduced in the documentation.
Example of generated code:
DotApp is tailor-made for developers who want an efficient tool without fluff. It offers speed, low demands, and simplicity that makes work easier. It’s a framework that proves less can be more – with results that speak for themselves.
Try DotApp and see for yourself!