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:

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'),
)

schema is 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)

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, fromto
delete(...) volledige oude waarden, per aangetast record

Setup:

  1. Voeg security/audit toe aan de resources in data/app.json.
  2. 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:

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

We gebruiken essentiële cookies om deze site te laten werken. Met uw toestemming gebruiken we ook analytics om de site te verbeteren.