ORM
Models follow Eloquent's API. Because Dart has no magic properties, each
model has a static ModelDefinition that describes its table, attributes
and relations; Database shows a complete model and the query
builder.
Definition#
final class User extends Model {
static final ModelDefinition<User> definition = ModelDefinition<User>(
table: 'users',
factory: User.new,
fillable: ['name', 'email', 'password'],
hidden: ['password', 'remember_token'],
casts: {'email_verified_at': Cast.dateTime, 'settings': Cast.json},
softDeletes: true,
relations: {
'posts': () => HasMany<Post>(() => Post.definition, foreignKey: 'user_id'),
'roles': () => BelongsToMany<Role>(
() => Role.definition,
pivotTable: 'role_user',
foreignPivotKey: 'user_id',
relatedPivotKey: 'role_id',
),
},
);
@override
ModelDefinition<User> get modelDefinition => User.definition;
}
The static is definition and the instance getter modelDefinition
because Dart forbids a static and an instance member with the same name.
Keep the explicit ModelDefinition<User> type and write relations as
functions: models that refer to each other would otherwise form an
inference cycle.
Other options: primaryKey, keyType (auto-increment, UUID, ULID or string),
connection, guarded, visible, appends, timestamps and the
createdAt / updatedAt / deletedAt column names, perPage, touches,
withRelations (Eloquent's $with) and morphClass (the Laravel class
name stored in polymorphic columns, App\Models\User by default).
Attributes#
Casts: Cast.integer, decimal, boolean, string, dateTime, date,
json, Cast.enumeration(MyEnum.values) and Cast.custom(...).
accessors: and mutators: replace Laravel's attribute methods, and
dartisan model:sync writes typed getters and setters from the table, so
user.email is a String checked by the compiler.
Relations#
HasOne, HasMany, BelongsTo, BelongsToMany (with withPivot and
withTimestamps) and HasManyThrough.
final users = await User.query()
.with_(['posts', 'roles']) // eager loading; `with` is reserved in Dart
.withCount('posts')
.whereHas('posts', (q) => q.where('published', true))
.paginate();
final posts = users.items.first.related<List<Post>>('posts');
final drafts = await user.relatedQuery<Post>('posts').where('published', false).get();
await user.attach('roles', [1, 2]);
await user.sync('roles', [2, 3]);
await user.detach('roles', [3]);
load and loadMissing load relations onto models you already have.
Model.preventLazyLoading turns an unloaded relation into an error, which
catches N+1 queries in tests.
Scopes, soft deletes and events#
- Local scopes (
scopes:) are called withquery.scope('published');globalScopes:apply to every query. - With
softDeletes: true,delete()setsdeleted_at, queries skip deleted rows, andwithTrashed(),onlyTrashed(),restore()andforceDelete()work as in Laravel. - Observers (
observers:) receivecreating,created,updating,updated,saving,saved,deleting,deleted,restoring,restoredand the force-delete events; returningfalsefrom a*inghook cancels the operation.
Not yet#
Polymorphic relations (morphTo, morphMany) and nested whereHas
('posts.comments') are planned.