DotApp Recommended Practices
This section guides you through the best practices for developing with the DotApp PHP Framework
. Learn how to create portable, shareable modules that adapt seamlessly to any session or database driver, ensuring compatibility across diverse server environments.
Recommended Practices
This section outlines the recommended practices for developing modules and applications with the DotApp PHP Framework
. By adhering to these practices, you ensure that your modules are portable, shareable across applications, and adaptable to any server configuration, including different session drivers (e.g., Redis, file-based, database) and database drivers (PDO, MySQLi). Following the framework's philosophy guarantees consistent outputs regardless of the underlying drivers.
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.
Accessing DotApp Instance
The DotApp
instance is the core of the framework, providing access to all its components. To use it, import the DotApp
class and access the singleton instance anywhere in your code.
use \Dotsystems\App\DotApp;
$dotApp = DotApp::DotApp();
$dotApp->someMethod(); // Access any DotApp method
This ensures you can interact with the framework’s features (e.g., router, renderer, database) from any part of your application or module.
Using Facades
Facades provide a cleaner, more readable way to interact with DotApp components. While not mandatory, using facades is a recommended practice for writing elegant code.
For example, these two lines achieve the same result:
DotApp::DotApp()->renderer->module(self::moduleName())->setView("dotapper-cli.eng")->setViewVar("variables", $viewVars)->renderView();
Renderer::new()->module(self::moduleName())->setView("dotapper-cli.eng")->setViewVar("variables", $viewVars)->renderView();
The second example uses the Renderer
facade, making the code more concise. Similarly, for custom renderers:
DotApp::DotApp()->renderer->addRenderer("DotAppDoc.code.replace", function($code) { /* logic */ });
Renderer::add("DotAppDoc.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([$this->get_data("base_url")."intro(?:/{language})?"], "DotAppDoc:Page@documentation", Router::DYNAMIC_ROUTE);
.
Using facades improves code readability and aligns with DotApp’s philosophy of clean, maintainable code.
Dependency Injection
Dependency Injection (DI) is a recommended practice in DotApp to manage dependencies cleanly. The framework predefines several bindings for common classes:
$this->bind(Response::class, function() { return new Response($this->request); });
$this->bind(Renderer::class, function() { return new Renderer($this); });
$this->singleton(RouterObj::class, function() { return $this->router; });
$this->singleton(RequestObj::class, function() { return $this->request; });
$this->singleton(Auth::class, function() { return $this->request->auth; });
$this->singleton(Logger::class, function() { return $this->logger; });
For example, in a controller, you can inject the Renderer
class to access it directly:
public static function documentation($request, Renderer $renderer) {
$jazyk = $request->matchData()['language'] ?? "";
$viewVars = array();
$viewVars['last_update'] = "2025-05-04";
// Use $renderer directly
}
Using DI reduces manual instantiation and makes your code more modular and testable.
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();
Alternatively, you can use the longer but more explicit approach:
DotApp::DotApp()->DB->driver(Config::db('driver'))->return("RAW")->selectDb(Config::db('maindb'))->q(function ($qb) use ($token) {
$qb
->select('user_id', Config::get("db","prefix").'users_rmtokens')
->where('token', '=', $token);
})->execute();
Using Callbacks for Driver-Agnostic Code
To ensure portability, always use callbacks in the execute
method, especially for RAW queries. This avoids driver-specific handling of results (e.g., MySQLi vs. PDO objects).
Incorrect Example (Driver-Specific):
$returned = DotApp::DotApp()->DB->driver('mysqli')->return("RAW")->selectDb(Config::db('maindb'))->q(function ($qb) use ($token) {
$qb
->select('user_id', Config::get("db","prefix").'users_rmtokens')
->where('token', '=', $token);
})->execute();
while ($row = $returned->fetch_assoc()) {
// Process row, e.g., $row['user_id']
}
This code fails if the user switches to PDO, as fetch_assoc()
is MySQLi-specific. Instead, use callbacks:
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;
$this->login($data, true, true);
});
}
},
function ($error, $db, $debug) {
// Handle errors
}
);
In RAW mode, the $result
in the success callback is always an array (e.g., $result[0]
, $result[1]
), regardless of the driver. This ensures portability.
To avoid callback hell, you can store results in a variable:
$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;
});
// 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.