Models & Repositories

When you want database rows to come back as real, typed PHP objects instead of associative arrays, Fast's models are the answer. They are deliberately plain: just a class with public properties and a table attribute. No base class to extend, no magic, no hidden query state living on the object.

Defining a model

A model is any class marked with the #[Table] attribute. Its public typed properties map to columns:

use Fast\Database\Table;

#[Table('users')]
final class User
{
	public int $id = 0;
	public string $name = '';
	public string $email = '';
	public bool $active = true;
}

That is the whole definition. The property names line up with your column names, and the types tell Fast how to hydrate each value.

Fast does not autoload your model classes for you. Make sure the class is loaded, either through your own autoloader or an explicit require, before you query it. This is called out in Directory Structure.

Finding and querying

The global helpers cover the common lookups. find() fetches one row by primary key, and all() fetches every row:

$user = find(User::class, $id);   // one User, or null
$users = all(User::class);        // every User

For anything more specific, query() gives you the full query builder, but hydrating typed objects instead of arrays:

$active = query(User::class)
	->where('active', true)
	->orderBy('name')
	->get();

$first = query(User::class)->where('email', $email)->first();

Everything you learned in the Query Builder section applies here, where, orderBy, limit, joins, and so on. The only difference is that get() and first() return User objects.

Saving and removing

Persisting a model is a single call. save() inserts a new record or updates an existing one, and remove() deletes it:

$user = new User();
$user->name = 'Ada';
$user->email = 'ada@example.com';
save($user);          // inserts, and fills in $user->id

$user->active = false;
save($user);          // updates the existing row

remove($user);        // deletes it

Because the model is just a plain object, there is no hidden dirty-tracking or lazy loading to trip over. What you set is what gets written.

Repositories under the hood

The query(), find(), all(), save(), and remove() helpers are a thin convenience layer over a Repository, which knows how to map between your class and its table. For most apps the global helpers are all you need, but the repository is there if you want to build higher-level data services on top of it.

Keeping models flat

The design intent here is that models stay simple data holders. Business logic lives in your own services, not on the model. This keeps hydration fast, avoids surprising side effects when you save, and means a model is safe to pass around and serialize. Resist the urge to hang query methods or lifecycle hooks on the class; put that behavior in a service or repository instead.

Now that you can store and load data, you need a way to shape the schema in the first place. That is Migrations & Schema.