AI blog · DotApp PHP Framework 2.0
How app/config.php works in DotApp PHP Framework
app/config.php is the only framework file you edit. It registers databases, session/cache/logger drivers, application identity, secrets, and every Config::module override for Shop and the rest of the app.
index.php includes this file, then the file constructs new DotApp() and calls load_modules().
There is no global config() helper: read and write through Config::get, Config::set, Config::module, Config::session, Config::cache, and the other section helpers.
The @AUTOCONFIG block is empty. DotApper does not fill secrets there. You must set unique keys yourself.
Cookie flags in this file configure the session driver. Application state still goes through DSM::use('Shop') — never $_SESSION.
This article is a complete, copy-paste map of a typical app/config.php, plus Shop overrides you actually ship.
Common mistakes
| Wrong | Right |
|---|---|
Edit app/parts/Config.php or index.php to change defaults. |
Edit only app/config.php. Core stays untouched. |
| Leave shipped placeholder keys in production. | Set unique c_enc_key, rm_key, and rmrcm_key with bin2hex(random_bytes(32)). |
Put Shop production secrets only inside module.init.php. |
Fallbacks in initialize(), production values in Config::module here. |
Call a global config('Shop.prefix') helper. |
Use Config::module('Shop', 'prefix'). That helper does not exist. |
Expect /* @AUTOCONFIG */ to write keys for you. |
The block is empty. You paste keys yourself. |
Read the cart from $_SESSION because cookies were set here. |
Cookie flags configure the driver. App code uses DSM::use('Shop'). |
Invent app.name_hash by hand. |
Leave it empty. The framework derives it from app.name. |
Store module-owned runtime facts with Config::module and never write them back. |
User overrides: Config::module. Module-persisted values: $this->settings() in Shop. |
When to edit this file
Edit app/config.php when you name the application, rotate secrets, register a database, pick session/cache/logger drivers, harden cookie flags, or override a module key for this environment.
Do not edit it to add routes, views, or Shop business logic — those belong in app/modules/Shop/.
Do not copy secrets into git if you can keep them out of the repo. The file still needs some values so a fresh clone boots.
Config::module is the central override for all modules. Shop, Docs, Users — every portable default set in initialize() can be replaced here without touching the module folder.
What you may edit
| Path | Rule |
|---|---|
app/config.php |
The only framework file you edit. |
app/modules/Shop/** |
Your module. Fallbacks live in initialize(), not as the only copy of production secrets. |
app/parts/**, index.php, dotapper.php, app/DotApp.php |
Never edit. |
Where this file sits in boot
index.php sets __ROOTDIR__ and includes app/config.php.
This file must finish driver registration and Config::set / Config::addDatabase before new \Dotsystems\App\DotApp().
Then, unless maintenance mode is on, it calls $dotApp->load_modules().
Module boot after that: How module initialization works in DotApp PHP Framework.
App name and keys
Set a unique app.name. It feeds remember-me cookie naming. Do not invent app.name_hash — it is derived automatically.
On a new install, replace the three secret keys. Generate them on your machine and paste the hex into this file. Do not reuse the shipped placeholders.
php -r "foreach (['c_enc_key','rm_key','rmrcm_key'] as $k) echo $k.': '.bin2hex(random_bytes(32)).PHP_EOL;"
Config::set('app', 'name', 'MyUniqueAppName');
Config::set('app', 'c_enc_key', 'PASTE_64_HEX_CHARS');
Config::set('app', 'rm_key', 'PASTE_64_HEX_CHARS');
Config::set('app', 'rmrcm_key', 'PASTE_64_HEX_CHARS');
| Key | Purpose |
|---|---|
app.name |
Application identity. Keep it unique per install. |
app.c_enc_key |
Primary application secret. Set a unique value. Never commit a production copy into a module. |
app.rm_key |
Remember-me secret. Same rule: unique per install. |
app.rmrcm_key |
Remember-me cookie-name secret. Same rule. |
app.name_hash |
Do not fill. Auto-derived. |
Keep secrets out of version control when you can. Module fallbacks in Shop must still exist so a clone boots before this file is filled.
addDatabase
Register each connection by name. Modules read Config::db('maindb') (default main) to know which connection is primary.
Eight arguments, in this order:
Config::addDatabase(
'main', // connection name
'127.0.0.1', // host
'dbuser', // username
'dbpass', // password
'shop_db', // database
'UTF8', // charset
'MYSQL', // engine
'pdo' // driver
);
addDatabase returns nothing (void). It only writes the databases section. Skip it only if the request never touches the database — Users, Auth, and most Shop catalogs need it.
Keep Config::db('cache', false) unless you ship a custom cache driver that implements deleteKeys(). Turning query cache on breaks Entity::save() with the shipped drivers.
Database walkthrough: How to use the database in DotApp PHP Framework.
Session, cache, and logger drivers
Drivers are registered here, then selected with the matching section helper. Register before new DotApp().
use Dotsystems\App\Parts\SessionDriverDefault;
use Dotsystems\App\Parts\CacheDriverFile;
use Dotsystems\App\Parts\LoggerDriverDefault;
Config::sessionDriver('default', SessionDriverDefault::driver());
Config::session('lifetime', 3600);
Config::session('secure', true); // HTTPS
Config::session('httponly', true);
Config::session('samesite', 'Strict');
Config::cacheDriver('default', CacheDriverFile::driver());
Config::cache('lifetime', 36000);
Config::cache('driver', 'default');
Config::loggerDriver('default', LoggerDriverDefault::driver());
Config::logger('core_log_enabled', true);
Config::logger('driver', 'default');
Cookie flags configure the session driver. They do not give module code a license to touch $_SESSION. Shop still calls DSM::use('Shop').
Sessions: How to use sessions in DotApp PHP Framework (DSM).
Cache: How to use cache in DotApp PHP Framework.
Config::module is the override for every module
Shop ships fallbacks in initialize() so it is portable. This file wins for the current environment.
Two-argument calls are getters. Three-argument calls are setters. A missing key returns null, not false.
Config::module('Shop', 'prefix', '/store');
Config::module('Shop', 'itemsPerPage', 50);
Config::module('Shop', 'enckey', 'PRODUCTION_HEX_SECRET');
Config::module('Shop', 'public', true);
Inside Shop, keep the fallback pattern so a key that was never set here still has a value:
Config::module('Shop', 'prefix') ?? Config::module('Shop', 'prefix', '/shop');
Config::module('Shop', 'itemsPerPage') ?? Config::module('Shop', 'itemsPerPage', 20);
Config::module('Shop', 'enckey') ?? Config::module('Shop', 'enckey', bin2hex(random_bytes(16)));
Config::module('Shop', 'maxAttempts', 5, Config::IF_NOT_EXIST);
Nested arrays: merge defaults with whatever this file already set.
$defaults = ['enabled' => false, 'timeout' => 8];
Config::module('Shop', 'AI', array_replace($defaults, Config::module('Shop', 'AI') ?? []));
Shop developers do not open Users to change a prefix. The application owner sets Config::module('Shop', 'prefix', '/store') here.
Each module still ships fallbacks so a missing override does not break a fresh clone. That split is how two teams share one app without overwriting routes or defaults.
@AUTOCONFIG is empty
The marked block exists in the file. Nothing writes into it. No installer fills keys. Treat it as a no-op and keep setting secrets with Config::set above new DotApp().
/* DO NOT TOUCH THIS SECTION !!! */
/* @AUTOCONFIG */
/* @END[AUTOCONFIG] */
settings() versus Config::module
Config::module |
$this->settings() |
|
|---|---|---|
| Who sets it | App owner in app/config.php, or Shop fallbacks in initialize() |
The Shop module itself at runtime |
| Where it lives | In-memory config for this request | app/modules/Shop/settings.php on disk |
| Use for | Prefix, page size, feature flags, secrets the owner must change | Values Shop persists: last sync stamp, generated local ids |
| Read API | Config::module('Shop', 'prefix') → value or null |
$this->settings('apiUrl') → value or null |
Do not write owner configuration into settings.php. Do not persist Shop’s own runtime facts only in Config::module — they vanish when PHP exits.
No global config() helper
There is no config() function. There is no env() wrapper in this stack either. Read with the Config facade. The translator helper in this file is unrelated — it is only for translator().
Config return table
| Method | Args | Exact return |
|---|---|---|
Config::get($section) |
Section name only | That section’s array, or null if the section is missing |
Config::get($section, $key) |
Section + key | Value, or null if unset — never false for a miss |
Config::set($section, $key, $value) |
Section, key, value | void |
Config::set($section, $value) |
Two args: replace the whole section | void |
Config::module($name) |
Module name only | Whole module array, or null if none |
Config::module($name, $key) |
Getter | Value, or null if unset |
Config::module($name, $key, $value) |
Setter | The value just stored |
Config::module($name, $key, $value, Config::IF_NOT_EXIST) |
Set only if absent | Existing value if present, otherwise the new value |
Config::session($key) |
Getter | Value, or null if unset |
Config::session($key, $value) |
Setter (including false) |
void |
Config::cache($key) / Config::cache($key, $value) |
Same getter/setter shape as session | Getter: value or null. Setter: void |
Config::addDatabase(...) |
Eight arguments | void |
Config::sessionDriver($name) / cacheDriver / loggerDriver |
Getter | Driver array, or throws \Exception if not defined |
Config::sessionDriver($name, $driver) (and cache/logger) |
Setter | void, or throws if the callable map is incomplete |
You cannot store null as a module value with the three-argument setter: $value === null is the getter. Use a real default (empty string, false, 0) instead.
Complete typical app/config.php (trimmed)
File: app/config.php. Keep Composer autoload, drivers, secrets, new DotApp(), and load_modules(). The translator helper in a stock file can stay; it is not a substitute for Config.
<?php
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\SessionDriverDefault;
use Dotsystems\App\Parts\CacheDriverFile;
use Dotsystems\App\Parts\LoggerDriverDefault;
require_once __DIR__ . '/vendor/autoload.php';
if (!__MAINTENANCE__) {
Config::addDatabase(
'main',
'127.0.0.1',
'dbuser',
'dbpass',
'shop_db',
'UTF8',
'MYSQL',
'pdo'
);
}
Config::sessionDriver('default', SessionDriverDefault::driver());
Config::session('lifetime', 3600);
Config::session('secure', true);
Config::session('httponly', true);
Config::session('samesite', 'Strict');
Config::cacheDriver('default', CacheDriverFile::driver());
Config::cache('lifetime', 36000);
Config::loggerDriver('default', LoggerDriverDefault::driver());
Config::logger('core_log_enabled', true);
Config::set('app', 'name', 'MyUniqueAppName');
Config::set('app', 'c_enc_key', 'PASTE_64_HEX_CHARS');
Config::set('app', 'rm_key', 'PASTE_64_HEX_CHARS');
Config::set('app', 'rmrcm_key', 'PASTE_64_HEX_CHARS');
Config::module('Shop', 'prefix', '/store');
Config::module('Shop', 'itemsPerPage', 50);
Config::module('Shop', 'enckey', 'PRODUCTION_HEX_SECRET');
Config::module('Shop', 'public', true);
/* @AUTOCONFIG */
/* @END[AUTOCONFIG] */
$dotapp = new \Dotsystems\App\DotApp();
$dotApp = $dotapp;
if (!__MAINTENANCE__) {
$dotApp->load_modules();
}
set_error_handler([$dotApp, 'errhandler']);
Shop overrides in the same file
The complete excerpt above already sets prefix, page size, and secrets. Keep the same keys in Shop initialize() as fallbacks so a clone boots before this file is filled.
Scaffold and fallbacks: How to create a module in DotApp PHP Framework.
Gotchas
- Configuration must be filled before
new DotApp(). Config::get/Config::module/Config::session/Config::cachemiss withnull, notfalse. Use??or the three-argument setter.Config::db('cache', true)plus ORMEntity::save()throws: shipped cache drivers have nodeleteKeys().- Logger files appear only after
Config::logger('core_log_enabled', true). Default levels dropinfo/debuguntil you extendlog_levels. - Redis session construction throws if any required
session.redis_*value is empty. Fill them before switching the driver.
FAQ
Is app/config.php really the only framework file I may edit?
Yes. Modules under app/modules/Shop/ are yours. Everything in app/parts/, index.php, and dotapper.php is off limits.
Will @AUTOCONFIG ever fill my keys?
No. The markers are empty. Generate hex with bin2hex(random_bytes(32)) and paste it into Config::set.
Why does config('key') fail?
There is no global config() helper. Use Config::module, Config::get, Config::session, or Config::cache.
I set httponly and samesite. Can I use $_SESSION now?
No. Those flags configure the session driver. Shop state belongs in DSM::use('Shop').
When do I use settings() instead of Config::module?
Config::module for owner-facing keys (prefix, flags, secrets). $this->settings() for values Shop writes to app/modules/Shop/settings.php.
Can I set a module key to null?
Not with the three-argument setter. A null third argument is a getter. Store false or an empty string if you need an explicit empty.
Should I set app.name_hash?
No. Leave it empty. The framework derives it from app.name.
Can I register more than one database?
Yes. Call addDatabase again with a different name. Point Config::db('maindb', 'other') only if that connection should become the primary.