Marwa\Framework\Database\Model is the framework-friendly base model built on top of marwa-db ORM. It keeps the upstream ORM behavior, then adds a small layer of convenience methods for common CRUD work such as table access, pagination, attribute normalization, create-or-update flows, and model state inspection.
Use it when you want an application model that feels concise in day-to-day development without hiding the underlying ORM.
The base class inherits the core ORM from marwa-db, then adds framework-level helpers such as:
newQuery() as a clean entry point for query buildingtableName() to read the resolved table nameuseConnection() to switch the model connectionnewInstance() for creating model instances programmaticallypaginate() that returns hydrated model instancesfirstWhere() and findBy() for simple lookupsfirstOrCreate() and updateOrCreate() for common persistence flowsexists() as an instance checkforceFill() for bypassing fillable protection deliberatelysyncOriginal(), isDirty(), and isClean() for change trackingfresh() for reloading the current recordsaveOrFail() and deleteOrFail() for exception-based writesThat means you still get the ORM model behavior you expect, but the framework layer gives you a more ergonomic API for application code.
<?php
declare(strict_types=1);
namespace App\Models;
use Marwa\Framework\Database\Model;
final class User extends Model
{
protected static ?string $table = 'users';
protected static array $fillable = [
'name',
'email',
'active',
'meta',
];
protected static array $casts = [
'active' => 'bool',
'meta' => 'json',
];
}
This is the standard pattern:
$fillableUse $table when you want to be explicit about the backing table:
protected static ?string $table = 'users';
You can also read the resolved table name anywhere with:
User::tableName();
Use $fillable to control which attributes may be mass assigned:
protected static array $fillable = [
'name',
'email',
'active',
];
This matters for methods such as create(), fill(), and update() where arrays are applied to the model in bulk.
The framework model normalizes input attributes using the model cast map before persistence helpers run.
Supported framework-level normalization in this wrapper includes:
json and arrayboolintfloatExample:
protected static array $casts = [
'active' => 'bool',
'meta' => 'json',
'login_count' => 'int',
'score' => 'float',
];
Practical effect:
bool values are cast to booleansint values are cast to integersfloat values are cast to floatsjson and array values are JSON-encoded when arrays or objects are providedUse create() when you want to insert and return a model in one step:
$user = User::create([
'name' => 'Alice',
'email' => 'alice@example.com',
'active' => true,
'meta' => ['role' => 'admin'],
]);
You can also create an instance first and save it later:
$user = User::newInstance([
'name' => 'Bob',
'email' => 'bob@example.com',
]);
$user->save();
If you want an exception when the write fails, use:
$user->saveOrFail();
For primary-key lookup:
$user = User::find(1);
For a simple column lookup, use findBy() or firstWhere():
$user = User::findBy('email', 'alice@example.com');
$activeUser = User::firstWhere('active', true);
When you need more control, drop into the query builder:
$users = User::newQuery()
->where('active', true)
->orderBy('name')
->get();
newQuery() is just a convenience alias for query(), so use whichever reads better in your codebase.
These are useful when your code is driven by unique business keys such as email, slug, or external IDs.
firstOrCreate()If a matching row exists, it is returned. Otherwise, a new row is created.
$user = User::firstOrCreate(
['email' => 'alice@example.com'],
['name' => 'Alice', 'active' => true]
);
updateOrCreate()If a matching row exists, it is updated with the supplied values. Otherwise, a new row is created.
$user = User::updateOrCreate(
['email' => 'alice@example.com'],
['name' => 'Alice Updated']
);
This pattern is especially useful for sync jobs, import pipelines, and idempotent writes.
Once you have a model instance, update attributes and save:
$user = User::findBy('email', 'alice@example.com');
$user->fill([
'name' => 'Alice Smith',
]);
$user->save();
Or bypass fillable checks intentionally with forceFill():
$user->forceFill([
'name' => 'Internal Override',
]);
$user->saveOrFail();
forceFill() is useful for internal framework code, trusted imports, or maintenance jobs where mass-assignment rules should not apply.
Delete a loaded model in the usual way:
$user = User::find(1);
$user?->delete();
If a failure should raise an exception:
$user->deleteOrFail();
The framework wrapper provides a paginate() helper that returns a structured array with hydrated model instances in data.
$page = User::paginate(15, 1);
Returned structure:
[
'data' => [/* User instances */],
'total' => 120,
'per_page' => 15,
'current_page' => 1,
'last_page' => 8,
]
Example in a controller:
$users = User::paginate(15, 1);
return $this->view('users/index', [
'users' => $users['data'],
'pagination' => $users,
]);
The framework model includes helpers for understanding whether an instance already exists and whether its attributes have changed.
exists()Checks whether the model currently has a primary key:
if ($user->exists()) {
// Existing persisted record
}
isDirty() and isClean()Use these to detect changes before saving:
if ($user->isDirty('name')) {
// The name has changed
}
if ($user->isClean()) {
// Nothing has changed
}
syncOriginal()If you manually change attributes and want to treat the current state as the new baseline:
$user->syncOriginal();
Use fresh() when you want a new instance reloaded from the database:
$freshUser = $user->fresh();
This is useful after a write when database-level triggers, defaults, or other processes may have changed the stored row.
If your application uses more than one database connection, you can switch the model class to a named connection:
User::useConnection('reporting');
Use this deliberately. Since it changes the model’s static connection setting, it is best suited to well-bounded application flows.
<?php
declare(strict_types=1);
namespace App\Models;
use Marwa\Framework\Database\Model;
final class User extends Model
{
protected static ?string $table = 'users';
protected static array $fillable = [
'name',
'email',
'active',
'meta',
];
protected static array $casts = [
'active' => 'bool',
'meta' => 'json',
];
}
Example usage:
$user = User::updateOrCreate(
['email' => 'alice@example.com'],
[
'name' => 'Alice',
'active' => true,
'meta' => ['role' => 'admin'],
]
);
if ($user->isDirty()) {
$user->saveOrFail();
}
$page = User::paginate(10, 1);
$fillable consistently to avoid accidental mass assignment.findBy() and firstWhere() for straightforward lookups, and newQuery() when conditions become more complex.updateOrCreate() for idempotent writes instead of open-coded lookup-then-save sequences.saveOrFail() and deleteOrFail() in flows where silent failure would be a bug.forceFill() and useConnection() as deliberate tools, not defaults.