Your first Anvil model
Anvil models are regular CodeIgniter models with additional, enforced rules:
public UUIDs, tenant isolation and auditing. This page creates a Project
model. The rules themselves are explained in
Data rules.
The quick way: generate it
Section titled “The quick way: generate it”php spark make:anvil-model Project --fields "name!:string:150,budget:decimal:12.2,starts_on:date" --soft-deletesphp spark migrateThis writes the migration, the model and the entity described below.
name! marks a required column; the types are string[:length], text,
int, decimal[:12.2], bool, date, datetime, json and
belongs_to[:table]. Add --resource to also generate the resource,
--no-tenant for a table shared by all tenants. All options:
Commands.
The rest of this page shows the same files written by hand, so you know what to keep when you adjust them.
1. Migration
Section titled “1. Migration”Every Anvil table needs a uuid column of type BINARY(16) with a unique
key. Tenant scoped tables also need a tenant_id column.
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateProjects extends Migration{ public function up(): void { $this->forge->addField([ 'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true], 'uuid' => ['type' => 'BINARY', 'constraint' => 16], 'tenant_id' => ['type' => 'INT', 'unsigned' => true], 'name' => ['type' => 'VARCHAR', 'constraint' => 150], 'budget' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => true], 'starts_on' => ['type' => 'DATE', 'null' => true], 'created_at' => ['type' => 'DATETIME', 'null' => true], 'updated_at' => ['type' => 'DATETIME', 'null' => true], 'deleted_at' => ['type' => 'DATETIME', 'null' => true], ]); $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('uuid'); $this->forge->addKey('tenant_id'); $this->forge->addForeignKey('tenant_id', 'anvil_tenants', 'id', 'CASCADE', 'CASCADE'); $this->forge->createTable('projects', false, ['ENGINE' => 'InnoDB']); }
public function down(): void { $this->forge->dropTable('projects', true); }}2. Entity
Section titled “2. Entity”<?php
namespace App\Entities;
use Anvil\Data\Entity;
class Project extends Entity{}The entity leaves internal fields (id, tenant_id) out of
toPublicArray() and JSON. List further internal columns in
protected array $internal = [...]. Models may also return arrays
($returnType = 'array'); Anvil works with both.
3. Model
Section titled “3. Model”<?php
namespace App\Models;
use Anvil\Data\Model;use App\Entities\Project;
class ProjectModel extends Model{ protected $table = 'projects'; protected $returnType = Project::class; protected $useSoftDeletes = true; protected $useTimestamps = true; protected $allowedFields = ['name', 'budget', 'starts_on'];}Do not add uuid or tenant_id to $allowedFields. Anvil sets them
and ignores changes to them. The uuid cast is registered automatically.
4. Use it
Section titled “4. Use it”service('tenantContext')->set($tenantId); // in panels the tenant filter does this
$projects = model(ProjectModel::class);
$projects->insert(['name' => 'Website relaunch']);$uuid = $projects->getInsertUuid(); // Anvil\Data\Uuid
$project = $projects->findByUuid($uuid); // only finds records of the current tenantecho json_encode($project); // {"uuid":"0192...","name":"Website relaunch",...}Without a tenant in the context, the model throws a TenancyException
instead of reading or writing data of all tenants.
The insert has also written an entry to the audit log, in the same transaction:
| event | subject_type | changes |
|---|---|---|
record.created |
projects |
{"name":[null,"Website relaunch"]} |
Next step
Section titled “Next step”Create a resource for the model, so users can list and edit projects in the panel.