Skip to content

Philosophy Overview

The DotApp framework is designed to create portable and maintainable modules that work seamlessly across different environments. By following these practices, your modules will adapt to the user's session driver (e.g., Redis, database) and database driver (PDO, MySQLi) without requiring code changes. This ensures that your application remains flexible and shareable, aligning with DotApp’s core philosophy of modularity and adaptability.

A key aspect of DotApp’s philosophy is input security. Incoming request values are auto-protected (escaped) so a forgotten sanitizer does not open XSS / injection-era holes. $request->data() is that protected copy — safe to print. $request->data(true) is the original array; after a secure-channel unwrap, fields are $request->data(true)['data']. Use original data for passwords, decrypt, hashes, and any compare. DotApp::DotApp()->unprotect($variable) still exists for a value you already hold (string or array, by reference, recursively). Prefer data(true) on the request.


$safe = $request->data();                 // protected — OK to print
$raw  = $request->data(true);             // original values
$password = $raw['data']['password'] ?? '';

// Legacy: a value you already hold (by reference — do not reassign)
DotApp::DotApp()->unprotect($variable);
    

Call unprotect as DotApp::DotApp()->unprotect($variable) without assigning the return value (avoid $variable = DotApp::DotApp()->unprotect($variable)). Developers do not protect variables manually — they are safe by default — but they must explicitly ask when they need the original form.

Accessing DotApp Instance

The DotApp kernel is available as DotApp::DotApp(). Use it for unprotect, ajaxReply, and call. Routing, queries, views, config, and sessions use facades.


use Dotsystems\App\DotApp;

DotApp::DotApp()->unprotect($htmlFromEditor);
DotApp::DotApp()->ajaxReply(['status' => 1], 200);
DotApp::call('HelloWorld:Home@index!', $request);
    

initialize($dotApp) is the required signature. Inside it, register services with DotApp::DotApp()->bind / singleton / resolve (do not use the broken Injector facade). Prefer facades everywhere else.

Using Facades

Facades are the public API for core services.

For example:


Renderer::new()->module(self::moduleName())->setView("dotapper-cli.eng")->setViewVar("variables", $viewVars)->renderView();
    

The Renderer facade keeps the code concise. Custom renderers:


Renderer::add("Docs.code.replace", function($code) { /* logic */ });
    

Common Facades

  • Renderer::new(): Returns a resettable renderer object.
  • Renderer::add(): Adds a custom renderer.
  • Router::get(): Defines a GET route, e.g., Router::get(['/helloworld', '/helloworld/'], "HelloWorld:Home@index!", Router::STATIC_ROUTE);.

Using facades improves code readability and aligns with DotApp’s philosophy of clean, maintainable code.

Dependency Injection

Register your own services in initialize($dotApp):


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

Controllers render with Renderer::new():


public static function index($request) {
    return Renderer::new()->module('HelloWorld')->setView('hello')->renderView();
}
    

Database Practices

To ensure your modules are portable and driver-agnostic, DotApp’s philosophy requires using the DB::module() facade for database access. This facade uses configuration settings to automatically select the configured driver and database, ensuring consistency across the application.

Using DB::module()

Use DB::module("ORM") or DB::module("RAW") for database queries:


DB::module("RAW")->q(function ($qb) use ($token) {
    $qb
        ->select('user_id', Config::get("db","prefix").'users_rmtokens')
        ->where('token', '=', $token);
})->execute(
    function ($result) {
        // $result is an array of rows in RAW mode
    },
    function ($error) {
        \Dotsystems\App\Parts\Logger::use()->error('query failed', ['msg' => is_object($error) ? $error->getMessage() : (string) $error]);
    }
);
    

Using Callbacks

Always pass success and error callbacks to execute(). The success callback receives an array of rows in RAW mode.

  • Success callback: function($result, $db, $debug)$result is an array of rows in RAW mode.
  • Error callback: function($error, $db, $debug) — required so failures are handled.

DB::module("RAW")->q(function ($qb) use ($token) {
    $qb
        ->select('user_id', Config::get("db","prefix").'users_rmtokens')
        ->where('token', '=', $token);
})->execute(
    function ($result, $db, $debug) use (&$data) {
        if ($result === null || $result === []) {
            $data = [];
            setcookie('dotapp_'.Config::get("app","name_hash"), "", [
                'expires' => time() - 3600,
                'path' => Config::session("path"),
            ]);
        } else {
            $db->q(function ($qb) use (&$data, $result) {
                $qb
                    ->select(['username', 'password'], Config::get("db","prefix").'users')
                    ->where('id', '=', $result['user_id']);
            })->execute(function ($result, $db, $debug) use (&$data) {
                $data['username'] = $result[0]['username'];
                $data['passwordHash'] = $result[0]['password'];
                $data['stage'] = 0;
                \Dotsystems\App\Parts\Auth::login($data, true);
            }, function ($error, $db, $debug) {
                // Handle error, e.g., log or display error message
                $data['error'] = $error->getMessage();
            });
        }
    },
    function ($error, $db, $debug) {
        // Handle initial query error
        error_log("Database error: " . $error->getMessage());
    }
);
    

In this example:

  • The success callback processes the $result array, which is driver-agnostic (e.g., $result[0]['user_id']).
  • The nested query uses another execute with its own success and error callbacks to handle results or errors.
  • The error callback logs or handles any database errors, preventing uncaught exceptions.

If callbacks lead to complex code (callback hell), you can store results in a variable to simplify logic:


$dbreturn = null;
DB::module("RAW")->q(function ($qb) use ($token) {
    $qb
        ->select('user_id', Config::get("db","prefix").'users_rmtokens')
        ->where('token', '=', $token);
})->execute(
    function ($result, $db, $debug) use (&$dbreturn) {
        $dbreturn = $result;
    },
    function ($error, $db, $debug) {
        error_log("Database error: " . $error->getMessage());
    }
);
// Continue logic with $dbreturn
    

Important: Avoid returning raw driver objects (e.g., $returnDB = DB::module("RAW")->q(...)->execute()), as they are driver-specific (MySQLi or PDO). Using callbacks ensures your module works with any driver, aligning with DotApp’s philosophy.

Session Management with DSM

The DotApp Session Manager (DSM) is a required component for session handling, replacing raw $_SESSION usage. DSM abstracts the underlying session driver (e.g., default, file, database, Redis), ensuring your application or module remains portable across different environments.

Using DSM

Import and use DSM as follows:


use \Dotsystems\App\Parts\DSM;

$dsm = new DSM("MyModuleStorage");
$dsm->load();
$dsm->set('variable1', "hello");
    

Alternatively, use the DSM facade for cleaner code (recommended):


DSM::use("MyModuleStorage")->set('variable1', "hello");
echo DSM::use("MyModuleStorage")->get('variable1'); // Outputs: hello
    

Each module should create its own storage (e.g., MyModuleStorage) to avoid conflicts with other modules. Variables in different storages can share the same name without collisions.

Key DSM Methods

  • set($name, $value): Sets a session variable.
  • get($name): Retrieves a session variable.
  • delete($name): Removes a session variable.
  • clear(): Clears all variables in the storage.
  • start(): Automatically called in the constructor.
  • destroy(): Destroys the storage (optional).
  • session_id(): Returns the session ID.
  • load(): Loads the session (not needed with facade).
  • save(): Saves the session (automatic on destruction).

The most commonly used methods are:


DSM::use("MyModuleStorage")->set('variable1', "hello");
DSM::use("MyModuleStorage")->get('variable1');
DSM::use("MyModuleStorage")->delete('variable1');
DSM::use("MyModuleStorage")->clear();
    

Why DSM? Using DSM instead of $_SESSION ensures your module is independent of the session driver. The facade approach eliminates the need for manual load() calls, making code cleaner and more maintainable.

See Examples

To see practical examples of these recommended practices, including database queries with DB::module() and session management with DSM, visit the Examples section. These examples demonstrate how to apply these practices in real-world scenarios.