Models
How Nibiru auto-generates model classes from your database schema, and how to extend them.
Nibiru’s model layer is schema-first. You don’t write models by hand — the framework reads your database and generates one PHP class per table on every boot.
How generation works
Section titled “How generation works”When [GENERATOR] database = true in your INI, the dispatcher runs new Model(false) on each request, which:
- Connects with the active driver.
- Lists tables in the configured database (
information_schema.tablesfor PG,SHOW TABLESfor MySQL). - For each table, writes
application/model/<table>.phpcontaining a class that extends the relevantDbadapter and embeds aTABLEconstant describing the columns.
Re-running is cheap: the generator overwrites only when the schema has changed, so checked-in models keep their handwritten methods if you turn database = false after the first run.
[GENERATOR]database = true ; regenerate models on each requestdatabase.overwrite = true ; if false, generator won't touch existing filesIn production set database = false so models aren’t regenerated on every hit.
A generated model
Section titled “A generated model”For a users table with columns user_id, user_login, user_pass, user_email:
<?phpnamespace Nibiru\Model;use Nibiru\Adapter\MySQL\Db;
class users extends Db{ const TABLE = [ 'table' => 'users', 'field' => [ 'user_id' => 'user_id', 'user_login' => 'user_login', 'user_pass' => 'user_pass', 'user_email' => 'user_email', ], ];
public function __construct() { self::initTable(self::TABLE); }}The Db adapter gives you simple CRUD helpers via Pdo:: (or the active driver):
$users = new \Nibiru\Model\users();
// Read by primary key$row = \Nibiru\Pdo::fetchRowInArrayById('users', ['user_id' => 42]);
// Read all$all = \Nibiru\Pdo::fetchAll('SELECT * FROM users WHERE user_account_active = 1');
// Insert\Nibiru\Pdo::insert('users', [ 'user_login' => 'marduk', 'user_email' => 'marduk@nibiru.local',]);
// Update\Nibiru\Pdo::update('users', ['user_email' => 'new@nibiru.local'], ['user_id' => 42]);
// Delete\Nibiru\Pdo::delete('users', ['user_id' => 42]);Custom queries
Section titled “Custom queries”The generated class is a plain PHP class — add methods freely:
namespace Nibiru\Model;use Nibiru\Adapter\MySQL\Db;use Nibiru\Pdo;
class documentation extends Db{ const TABLE = [ 'table' => 'documentation', 'field' => [ 'id' => 'id', 'title' => 'title', 'slug' => 'slug', 'content' => 'content', 'category' => 'category', 'version' => 'version', ], ];
public function __construct() { self::initTable(self::TABLE); }
public function getBySlug(string $slug): ?array { return Pdo::fetchRowInArrayById( self::TABLE['table'], [self::TABLE['field']['slug'] => $slug] ) ?: null; }
public function search(string $query): array { $sql = 'SELECT * FROM ' . self::TABLE['table'] . ' WHERE title LIKE :q OR content LIKE :q ORDER BY title'; return Pdo::fetchAll($sql, [':q' => '%' . $query . '%']); }}If the generator runs again with database.overwrite = true it will replace this file. Keep custom methods in a child class or set database.overwrite = false after first generation.
Pattern: thin model + module plugin
Section titled “Pattern: thin model + module plugin”For complex domains (auth, billing, analytics), the showcase apps put query methods on a module plugin rather than the bare model. The plugin owns the business rules; the model is just a typed handle to the table:
application/module/users/├── plugins/│ └── user.php # User::isAuthorized(), User::loginForm(), …└── traits/ └── userForm.phpnamespace Nibiru\Module\Users\Plugin;use Nibiru\Model\users;
class User { private users $usersModel; public function __construct() { $this->usersModel = new users(); }
public function isAuthorized(): bool { return isset($_SESSION['auth']['user_id']); }
public function findByLogin(string $login): ?array { return \Nibiru\Pdo::fetchRow( 'SELECT * FROM users WHERE user_login = :login', [':login' => $login] ) ?: null; }}This keeps controllers thin ($this->user->isAuthorized()) while leaving the generated model untouched.
Multi-driver gotchas
Section titled “Multi-driver gotchas”The generated Db adapter is driver-specific. If you switch from MySQL to PostgreSQL, regenerate models so they extend the right base class:
- MySQL / PDO →
Nibiru\Adapter\MySQL\Db - PostgreSQL (libpq) →
Nibiru\Adapter\PostgreSQL\Db - ODBC →
Nibiru\Adapter\Odbc\Db
Same query helpers, different adapter under the hood.
When to skip the generator
Section titled “When to skip the generator”Read-only databases, vendor systems, or any schema you don’t control: set database = false, hand-write minimal model classes that match the columns you actually use, and check them in.