Skip to content

Your First Model

This guide walks you through creating a minimal model from scratch.

Step 1: Create the File

Create a new PHP file at App/Model/Task.php:

<?php

namespace App\Model;

use System\Model\DefaultModel;
use System\ModelMeta;
use System\StringProperty;
use System\TextProperty;
use System\DateProperty;
use System\BooleanProperty;

class Task extends DefaultModel
{
    public static function meta(): ?ModelMeta
    {
        return (new ModelMeta('Tasks', 'check-square'))
            ->withSingular('Task')
            ->enable();
    }

    protected function getAdditionalProperties(): array
    {
        return [
            (new StringProperty('title'))->autofocus(),
            new TextProperty('description'),
            new DateProperty('due_date'),
            (new BooleanProperty('is_done'))->asSwitch(),
        ];
    }

    public function getLabel(): string
    {
        return $this->title ?? '';
    }
}

Step 2: Refresh the Browser

That's it. Refresh the browser and you'll see:

  • A "Tasks" entry in the sidebar with an "All" folder
  • A list view showing your tasks (empty at first)
  • A "New" button to create a task
  • A detail form with title, description, due date, and a toggle switch
  • Search, sorting, pagination — all automatic

No database migration needed. The framework creates the table and columns on first access.

Understanding the Code

meta() — Model Metadata

1
2
3
4
5
6
public static function meta(): ?ModelMeta
{
    return (new ModelMeta('Tasks', 'check-square'))  // (1)
        ->withSingular('Task')                       // (2)
        ->enable();                                  // (3)
}
  1. 'Tasks' is the plural label shown in navigation. 'check-square' is a Phosphor icon name.
  2. 'Task' is the singular label used in "New Task", "Delete Task", etc.
  3. enable() makes the model visible. Without it, the model exists but is hidden.

getAdditionalProperties() — Field Definitions

Returns an array of property objects. Each property becomes a database column and a form field.

1
2
3
4
5
6
7
8
9
protected function getAdditionalProperties(): array
{
    return [
        (new StringProperty('title'))->autofocus(),  // single-line text, focused on load
        new TextProperty('description'),              // multi-line textarea
        new DateProperty('due_date'),                 // date picker
        (new BooleanProperty('is_done'))->asSwitch(), // toggle switch
    ];
}

Tip

The property name (e.g. 'title') becomes the database column name and is auto-humanized to a label ("Title"). Use ->label('My Label') to override.

getLabel() — Record Display Name

This string is shown in list views, dropdown selects, breadcrumbs, and anywhere a record needs a one-line identifier.

1
2
3
4
public function getLabel(): string
{
    return $this->title ?? '';
}

What DefaultModel Gives You for Free

By extending DefaultModel, your model automatically gets:

Feature What It Does
Soft delete Records go to trash instead of being permanently deleted
Record locking Checkout/check-in prevents edit conflicts
Audit: created Stores who created the record and when
Audit: modified Stores who last modified the record and when

You can override any of these by overriding the corresponding can*() methods:

1
2
3
4
5
6
// Disable soft delete for this model
public function canSoftDelete(): bool { return false; }

// Disable record locking
public function canCheckout(): bool { return false; }
public function canCheckin(): bool { return false; }

Next Steps