8: ORM
Phlo wordt geleverd met een krachtige ingebouwde ORM waarmee je databasetabellen kunt definiëren als klassen. Modellen kunnen snel worden gedefinieerd via columns of volledig via een declaratieve schema. Records worden behandeld als instances, met ondersteuning voor props, methods, views, relaties en meerdere database-engines.
8.1: Basisprincipes
Een ORM-model is een .phlo-bestand met:
@ class:de naam van het model (en tabel)@ extends: modelstatic tableencolumnsofschema- Optioneel: relaties (
parent,child,many) - Props, methods en views werken per record instance
Voorbeeld:
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static columns = 'id,name,email,active,created'8.2: Modellen definiëren
7.2.1 Plat met columns (snel en lichtgewicht)
Gebruik columns voor eenvoudige tabellen:
@ class: shipment
@ extends: model
view: $this->destination ($this->user)
static table = 'shipments'
static order = 'changed DESC'
static columns = 'id,user,destination,costs,valid,weight,shipped,created,changed'
static objParents = ['user' => 'user']
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static order = 'changed DESC'
static columns = 'id,name,email,level,active,created,changed'
static objChildren = ['shipments' => 'shipment']
7.2.2 Met schema en field(...) (rijk en declaratief)
Met schema definieer je velden, relaties en UI op één plek:
@ class: shipment
@ extends: model
view: $this->destination ($this->user)
static table = 'shipments'
static schema => arr (
id: field (type: 'token', length: 4, title: 'ID'),
destination: field (type: 'text', required: true, search: true),
user: field (type: 'parent', obj: 'user', required: true),
costs: field (type: 'price', prefix: '€ '),
valid: field (type: 'bool'),
attachments: field (type: 'child', obj: 'attachment', list: true),
)
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static schema => arr (
id: field (type: 'token'),
name: field (type: 'text', search: true, required: true),
email: field (type: 'email', required: true),
shipments: field (type: 'child', obj: 'shipment'),
groups: field (type: 'many', obj: 'group', table: 'user_groups'),
)
schemais vooral krachtig in combinatie met PhloCMS, maar werkt ook zelfstandig.
8.3: CRUD
Ophalen:
$user = user::record(id: 1)
$list = shipment::records(order: 'created DESC')
Aanmaken:
$shipment = shipment::create(destination: 'Paris', user: 1)
Bewerken en opslaan:
$shipment->destination = 'Lyon'
$shipment->objSave
Verwijderen:
shipment::delete('id=?', $shipment->id)
record(...)→ enkel record (of null)records(...)→ array van records (klasse-instanties)create(...)→ invoegen + directe ophalingobjSave→ sla de instantie op (invoegen/bijwerken afhankelijk van id)delete(...)→ statische verwijdering met een SQL where-clausule
8.4: Relationele navigatie
Relaties zijn beschikbaar als eigenschappen:
| Type | Declaratie | Gebruik |
|---|---|---|
| parent | type: parent |
$shipment->user |
| child | type: child |
$user->shipments |
| many | type: many |
$user->groups |
Veel-op-veel
type: many gebruikt een pivot-tabel:
groups: field (
type: 'many',
obj: 'group',
table: 'user_groups',
)
Navigatie:
$user = user::record(id: 1)
foreach ($user->groups as $group)
echo $group->title
Relaties worden batch-geladen voor prestaties. Er zijn geen cross-DB joins; elke klasse laadt vanuit zijn eigen engine.
8.5: Instantiedynamiek
Elke record is een echte instantie van je modelklasse.
Je kunt props, methods en views gebruiken om virtuele velden, berekeningen of representaties toe te voegen:
@ class: shipment
@ extends: model
prop summary => $this->destination.' ('.$this->user.')'
method tax => $this->costs * 0.21
view:
<p>$this->summary</p>
<p>$this->tax</p>
Gebruik:
$shipment = shipment::record(id: 'AB12')
echo $shipment->summary
echo $shipment
Het gebruik van een record als een string roept de view-representatie op.
Props en methods werken altijd op de record instantie, nooit statisch.
8.6: Filteren en queries
Alle query-methoden accepteren benoemde argumenten en SQL-achtige filters:
shipment::records(destination: 'Paris')
shipment::records(where: 'valid=1 AND weight>10')
shipment::pair(columns: 'id,destination')
Ondersteund: where, order, group, joins, caching en schema-bewuste kolommen.
8.7: Caching en prestaties
De ORM gebruikt interne buffers (objRecords, objLoaded) voor relationele opzoekingen en optionele APCu caching via:
static objCache = true
Of een aantal seconden:
static objCache = 600
Records en relaties worden in batches geladen. Gebruik records() voor bulkselecties in plaats van record() in lussen.
8.8: Meerdere engines
Phlo ondersteunt meerdere backends via prop DB. De standaard is %MySQL, maar je kunt elke engine per model instellen.
SQLite
prop DB => %SQLite(data.'users.db')
@ class: notes
@ extends: model
prop DB => %SQLite(data.'notes.db')
static table = 'notes'
static columns = 'id,title,body'
PostgreSQL
prop DB => %PostgreSQL
@ class: invoices
@ extends: model
prop DB => %PostgreSQL
static table = 'invoices'
static columns = 'id,customer_id,total,created'
Tabellen op verschillende engines kunnen worden gecombineerd in relaties; elke klasse haalt zijn eigen gegevens op.
/data/creds.ini
Voor engines zoals MySQL en PostgreSQL, plaats je inloggegevens in:
/data/creds.ini
[mysql]
host = localhost
database = db_name
user = db_user
password = db_password
[postgresql]
host = localhost
database = my_pg_db
user = pg_user
password = pg_pass
Phlo laadt deze automatisch via %creds->....
8.9: Opt-in functies: audit, validatie, aangepaste PK
Een @ extends: model biedt je CRUD + identity map + relaties + soft delete direct uit de doos. Drie extra opt-ins zijn per model ingeschakeld met een statische vlag.
X.9.1 Audit log
static objAudit = true
Vanaf dat moment wordt elke create, objSave (update) en delete gelogd naar een audit tabel via de security/audit resource:
| Operatie | Wat wordt gelogd |
|---|---|
create(...) |
volledige nieuwe waarden |
objSave (update) |
diff: alleen gewijzigde velden, from → to |
delete(...) |
volledige oude waarden, per aangetast record |
Setup:
- Voeg
security/audittoe aan de resources indata/app.json. - Importeer de schema SQL eenmaal:
mysql <database> < /srv/phlo/resources/security/audit.sql.
Uitsluiten van gevoelige velden:
method afterCreate => %audit->log($this, 'create', [], (array)$this, exclude: ['password_hash'])
Schakel per omgeving in, alleen dev:
static objAudit => debug
Of alleen bij release:
static objAudit => !debug
(debug is de runtime constante van phlo_app(debug: ...).)
X.9.2 Validatie
static objValidate = true
Voor create() voert Phlo de bijbehorende objValidate($value) uit voor elk veld in static schema(). Bij fouten: create() retourneert null, met fouten beschikbaar via Class::objErrors():
if (!user::create($args)){
return apply(errors: user::objErrors())
}
Veldregels in schema():
static schema => arr (
email: field (type: 'email', required: true),
name: field (type: 'text', length: 100, required: true),
slug: field (type: 'text', pattern: '^[a-z0-9-]+$'),
status: field (type: 'text', enum: ['draft', 'sent', 'paid']),
)
Aangepaste veldvalidatie: overschrijf method objValidate($value) in je eigen veldsubklasse.
X.9.3 Aangepaste primaire sleutel
De standaard is id (auto-increment integer). Overschrijf het voor andere PK's:
static idColumn = 'sku'
static idType = 'string'
Effect:
- De identity map gebruikt
skuals zijn sleutel Class::record(sku: 'XYZ-123')(nietid:)- Met
create(): je levert zelf de PK-waarde (geen auto-increment) $record->idwerkt niet, gebruik$record->sku
X.9.4 Combineren
De drie opt-ins zijn onafhankelijk en kunnen worden gecombineerd:
@ class: giftcard
@ extends: model
static objAudit = true
static objValidate = true
static idColumn = 'sku'
static idType = 'string'
Elke functie is standaard uit (false, 'id', 'int'). Een model zonder opt-ins blijft een gewoon model.
8.10: Functiebeschrijving
| Functie / Eigenschap | Type | Beschrijving |
|---|---|---|
record(...) |
static | Haalt 1 record (of null) op |
records(...) |
static | Haalt meerdere records (array) op |
create(...) |
static | Invoegen + ophalen |
objSave |
instance | Invoegen of bijwerken |
delete(where, …) |
static | Verwijderen |
pair, item, column |
static | Snelle query-hulpmiddelen |
objParents / schema: parent |
declarative | Ouderrelaties |
objChildren / schema: child |
declarative | Kindrelaties |
schema: many |
declarative | Veel-op-veel |
objCache |
static | Optionele APCu-caching |
prop DB |
static | Per-model engine |
8.11: Best practices
- Gebruik
columnsvoor snelle, eenvoudige modellen. - Gebruik
schemavoor rijke definities en CMS-integratie. - Definieer een
viewvoor stringrepresentaties. - Gebruik props voor virtuele velden.
objSavein plaats vansave.- Gebruik
records()voor bulklaadbeurten. - Scheid modellen per engine met
prop DB. - Bewaar inloggegevens in
/data/creds.ini. - Houd modellen declaratief; plaats logica in props/methods.