Skip to content

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:

PHP version: 8.3.15 fpm-fcgi
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:


<button  dotbridge-key="6ADGaqLv1Me5zf-1vt0qiFfPoI6iyPs" dotbridge-id="pb767bsXqBlZM4jUCofkDyDXOSL13RgoZHAlw8pDEiUUiw620260820235023502350090950232cfbf15dfef3984b2802e8edfc547d0f" dotbridge-event="click" dotbridge-data="ZKGnViZsrPUXST64MKpcl1VpSzB2MHh2Z2xYTFY0TmpMaG5iWVdaOUVLR1NBZGs3Z3BKMDhxN1NJTFU9" dotbridge-data-id="zmBFIbKXppa72/vc9GHDW2I5YWtPd0hUcm9SNElJb3EvVU4zU1NUQTUxdUFqSHpiU2ZsM1pTbTdxUUdnVGdneXZkTkZmeFJVTnVBQnNNd2N4dFJXWWFGMEdDNzlBM2g4eWxKTEwrKzNRZEQ5VkVGZVpwQjI3SjA1RU5FPQ==" dotbridge-function="newsletter.subscribe" dotbridge-inputs="newsletter.email">Subscribe</button>
            
HTML

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!