Models
L'ORM di Flux: potente come Eloquent, pulito come Python
Active Record Pattern: Ogni model rappresenta una tabella del database.
Le istanze del model rappresentano righe, con metodi intuitivi per query e relazioni.
Definire un Model
I model si creano nella cartella app/models/:
# app/models/User.flux
model User {
# Tabella del database (opzionale, default: users)
table = "users"
# Campi fillable (mass assignment)
fillable = ["name", "email", "password"]
# Campi nascosti nella serializzazione
hidden = ["password", "remember_token"]
# Cast automatici
casts = {
"email_verified_at": "datetime",
"is_admin": "boolean",
"metadata": "json"
}
# Timestamps automatici (created_at, updated_at)
timestamps = True
}
CRUD Operations
Operazioni base per creare, leggere, aggiornare ed eliminare:
Create
# Metodo 1: create()
user = User.create({
"name": "Mario Rossi",
"email": "mario@example.com",
"password": hash("secret123")
})
# Metodo 2: new() + save()
user = User()
user.name = "Luigi Verdi"
user.email = "luigi@example.com"
user.save()
# Bulk insert
users = User.insert([
{"name": "Anna", "email": "anna@example.com"},
{"name": "Paolo", "email": "paolo@example.com"}
])
# FirstOrCreate
user = User.first_or_create(
{"email": "test@example.com"}, # Cerca per
{"name": "Test User"} # Crea con
)
Read
# Trova per ID
user = User.find(1)
user = User.find_or_404(1) # Lancia 404 se non trovato
# Prima/ultima riga
first_user = User.first()
last_user = User.latest().first()
# Query con condizioni
users = User.where("age", ">", 18).get()
user = User.where("email", "mario@example.com").first()
# Query multiple condizioni
users = User.where("active", True)
.where("role", "admin")
.order_by("name")
.get()
# Tutti i record
all_users = User.all()
# Count
total = User.count()
adults = User.where("age", ">=", 18).count()
Update
# Update singolo record
user = User.find(1)
user.name = "Nuovo Nome"
user.save()
# Update con dizionario
user.update({
"name": "Mario Bianchi",
"email": "mario.bianchi@example.com"
})
# Mass update
User.where("status", "pending")
.update({"status": "active"})
# Increment/Decrement
post = Post.find(1)
post.increment("views") # views + 1
post.decrement("stock", 5) # stock - 5
Delete
# Delete singolo
user = User.find(1)
user.delete()
# Mass delete
User.where("created_at", "<", "2020-01-01").delete()
# Soft Delete (se abilitato)
user.soft_delete() # Setta deleted_at
user.restore() # Ripristina
# Force delete (anche con soft delete)
user.force_delete()
Query Builder
Costruisci query complesse con metodi concatenabili:
# Where conditions
users = User.where("age", ">", 18).get()
users = User.where("name", "LIKE", "%mario%").get()
users = User.where_in("role", ["admin", "editor"]).get()
users = User.where_between("age", [18, 65]).get()
users = User.where_null("deleted_at").get()
# Or conditions
users = User.where("role", "admin")
.or_where("role", "superadmin")
.get()
# Complex where
users = User.where(lambda query:
query.where("age", ">", 18)
.where("city", "Roma")
).or_where("vip", True).get()
# Order e Limit
recent_posts = Post.order_by("created_at", "desc")
.limit(10)
.get()
# Offset e Pagination
users = User.order_by("id")
.offset(20)
.limit(10)
.get()
# Distinct
cities = User.select("city").distinct().get()
# Group By e Having
stats = Order.select("status", db.raw("COUNT(*) as total"))
.group_by("status")
.having("total", ">", 10)
.get()
Relazioni
Definisci relazioni tra model in modo elegante:
One to Many
# app/models/User.flux
model User {
def posts(self):
return self.has_many(Post)
}
# app/models/Post.flux
model Post {
def author(self):
return self.belongs_to(User, "user_id")
}
# Utilizzo
user = User.find(1)
posts = user.posts().get()
recent_posts = user.posts()
.where("published", True)
.latest()
.limit(5)
.get()
# Accesso inverso
post = Post.find(1)
author = post.author().first()
Many to Many
# app/models/User.flux
model User {
def roles(self):
return self.belongs_to_many(Role, "user_roles")
}
# app/models/Role.flux
model Role {
def users(self):
return self.belongs_to_many(User, "user_roles")
}
# Attach/Detach
user = User.find(1)
user.roles().attach(1) # Aggiungi ruolo con ID 1
user.roles().attach([1, 2, 3]) # Multipli
user.roles().detach(1) # Rimuovi
user.roles().sync([1, 3]) # Sincronizza (solo 1 e 3)
# Con pivot data
user.roles().attach(1, {
"assigned_at": datetime.now(),
"assigned_by": current_user.id
})
Has One
# app/models/User.flux
model User {
def profile(self):
return self.has_one(Profile)
}
# app/models/Profile.flux
model Profile {
def user(self):
return self.belongs_to(User)
}
# Utilizzo
user = User.find(1)
profile = user.profile().first()
# Create related
user.profile().create({
"bio": "Developer",
"website": "example.com"
})
Polymorphic Relations
# app/models/Comment.flux
model Comment {
def commentable(self):
return self.morph_to()
}
# app/models/Post.flux
model Post {
def comments(self):
return self.morph_many(Comment, "commentable")
}
# app/models/Video.flux
model Video {
def comments(self):
return self.morph_many(Comment, "commentable")
}
# Utilizzo
post = Post.find(1)
comments = post.comments().get()
# Create polymorphic
post.comments().create({
"content": "Great post!"
})
Eager Loading
Ottimizza le query caricando le relazioni in anticipo:
# N+1 problem (evita!)
users = User.all()
for user in users:
print(user.posts().count()) # Query per ogni user!
# Eager loading (ottimizzato)
users = User.with("posts").get()
for user in users:
print(len(user.posts)) # Nessuna query aggiuntiva
# Multiple relations
posts = Post.with("author", "comments", "tags").get()
# Nested relations
posts = Post.with("comments.user").get()
# Conditional loading
users = User.with({
"posts": lambda query: query.where("published", True)
}).get()
# Lazy eager loading
users = User.all()
users.load("posts") # Carica dopo
Scopes
Definisci query riutilizzabili:
# app/models/Post.flux
model Post {
# Local scope
def scope_published(self, query):
return query.where("published", True)
.where("published_at", "<=", datetime.now())
def scope_by_author(self, query, author_id):
return query.where("author_id", author_id)
# Global scope (sempre applicato)
def boot(self):
self.add_global_scope("active",
lambda query: query.where("deleted_at", None)
)
}
# Utilizzo
published = Post.published().get()
user_posts = Post.published().by_author(1).get()
# Disabilita global scope
all_posts = Post.without_global_scope("active").get()
Mutators & Accessors
Modifica automaticamente i valori in get/set:
model User {
# Accessor (get)
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
# Mutator (set)
def set_password(self, value):
self.attributes["password"] = hash(value)
# Accessor per attributo esistente
def get_email(self):
return self.attributes["email"].lower()
}
# Utilizzo
user = User.find(1)
print(user.full_name) # Chiama get_full_name()
user.password = "newpass" # Chiama set_password()
Events
Reagisci agli eventi del model:
model User {
# Eventi disponibili:
# creating, created, updating, updated,
# saving, saved, deleting, deleted
def boot(self):
# Prima di creare
self.creating(lambda user:
user.uuid = generate_uuid()
)
# Dopo aver salvato
self.saved(lambda user:
Cache.forget(f"user_{user.id}")
)
# Prima di eliminare
self.deleting(lambda user:
user.posts().delete() # Elimina posts correlati
)
}
# Observer pattern
class UserObserver:
def created(self, user):
send_welcome_email(user)
def updated(self, user):
if user.is_dirty("email"):
send_verification_email(user)
# Registra observer
User.observe(UserObserver)
Advanced Features
Soft Deletes
model Post {
use_soft_deletes = True # Abilita soft delete
deleted_at = "deleted_at" # Colonna timestamp
}
# Utilizzo
post = Post.find(1)
post.delete() # Soft delete (setta deleted_at)
# Query
posts = Post.all() # Solo non eliminati
all_posts = Post.with_trashed().get() # Tutti
deleted = Post.only_trashed().get() # Solo eliminati
# Restore
post.restore() # Rimuove deleted_at
# Force delete
post.force_delete() # Elimina davvero dal DB
Query Caching
# Cache query results
users = User.where("active", True)
.cache(minutes=10)
.get()
# Cache con tag
posts = Post.with("author")
.cache_tags(["posts", f"user_{user_id}"])
.cache(minutes=30)
.get()
# Invalida cache
Cache.tags(["posts"]).flush()
Database Transactions
# Transaction automatica
with db.transaction():
user = User.create(user_data)
profile = user.profile().create(profile_data)
# Commit automatico se tutto ok
# Rollback automatico in caso di errore
# Transaction manuale
db.begin_transaction()
try:
# Operazioni
db.commit()
except Exception as e:
db.rollback()
raise e
Model Factory
Genera dati fake per testing:
# database/factories/UserFactory.flux
factory User {
"name": fake.name(),
"email": fake.unique().email(),
"password": hash("password"),
"created_at": fake.date_between("-1 year", "now")
}
# Utilizzo
user = User.factory().create()
users = User.factory().count(10).create()
# Override attributi
admin = User.factory().create({
"role": "admin",
"email": "admin@example.com"
})
# Stati
factory User {
def admin(self):
return self.state({
"role": "admin",
"permissions": ["all"]
})
}
admin = User.factory().admin().create()
Best Practices
📝 Naming Conventions
- Model: singolare, PascalCase (User, Post)
- Tabelle: plurale, snake_case (users, posts)
- Foreign key: model_id (user_id)
- Pivot table: singolari ordinati (role_user)
🚀 Performance
- Usa eager loading per evitare N+1
- Seleziona solo campi necessari
- Indicizza foreign keys
- Usa cache per query costose
🔒 Sicurezza
- Definisci sempre $fillable
- Nascondi campi sensibili con $hidden
- Valida input prima di salvare
- Usa scope per filtri di sicurezza
✨ Prossimo passo: Scopri come creare interfacce dinamiche con il sistema di
Templates di Flux.