Database (Porm)

This section is for Northwind Studio engineers building DeskFlow — the task board API on port 8000. Porm is how TaskService, MemberService, and ProjectService query tasks, team_members, and projects without Eloquent-style models.

What you will learn

  • Configure [db] and call table() from DeskFlow services
  • Filter, join, paginate, and aggregate Northwind tables
  • Pool PDO connections under RoadRunner and FPM
Before you start
  • API tutorial — DeskFlow services before persistence
  • Helperstable(), db(), and connectionManager()

How it works

flowchart LR TaskService --> table["table('tasks')"] MemberService --> tm["table('team_members')"] ProjectService --> proj["table('projects')"] table --> Porm[Porm / Piql] tm --> Porm proj --> Porm Porm --> SQLite[(SQLite / PostgreSQL)]

Pionia includes Porm (Pionia ORM) — a Medoo-inspired query builder, not a full ORM. There are no models or migrations in the framework; you work with tables, arrays, and a fluent API.

Quick start

// Global helpers (recommended)
$row = table('tasks')->get(1);
$rows = table('tasks')->filter(['status' => 'open'])->limit(10)->all();

// Named connection from environment/settings.ini
table('projects', null, 'db_pgsql')->save(['name' => 'Northwind rebrand']);

Porm is built into your Pionia app. Use table() or db() — not legacy Porm\Porm::from() patterns from older tutorials.

Your first database steps (DeskFlow)

In the DeskFlow tutorial you start with hardcoded tasks, then persist them in SQLite and add task.create:

  1. Add a tasks table migration or SQL file under database/.
  2. Configure [db] in environment/settings.ini (SQLite is fine for local DeskFlow).
  3. In TaskService::listAction, replace the array with table('tasks')->filter(['project_id' => $data->getInt('project_id')])->all().
$tasks = table('tasks')
    ->filter(['status' => 'open'])
    ->orderBy('created_at', 'DESC')
    ->limit(20)
    ->all();

Try it: Making queries walks through get(), save(), and update() on a single table.

Guide map

TopicPage
Configuration & entry pointsGetting started
CRUD & readsMaking queries
filter(), orderBy, limitFiltering
WHERE operators & clause keysWHERE DSL reference
Joins & aliasesRelationships & joins
count, sum, Agg builderAggregation
PaginationCore & list APIsPagination
Multi-DB & poolingConnections
Transactions & raw SQLTransactions & raw SQL
chunk, random, explainPerformance
Method cheat sheetAPI reference

Query modes

table('tasks')
  ├─ Direct mode   → get(), save(), update(), delete(), has(), random(), …
  ├─ filter()      → Builder (where, orderBy, limit, all, count, …)
  └─ join()        → Join (left, inner, right, full, all, count, random, …)

After filter() or join(), table-level write methods (save, get, etc.) are not available on the same chain — finish with all(), get(), or count() on the builder.

Common mistakes

  • Using Porm::from() or Db::from() in DeskFlow services — prefer the global table() helper wired in bootstrap.
  • Calling save() after filter() on the same chain — finish reads with all() / get() first, then start a new table('tasks') for writes.
  • Skipping [db] in settings.ini — DeskFlow on port 8000 still needs a default connection (SQLite is fine locally).
  • Opening a new PDO per query — reuse connectionManager(); do not call disconnect() between HTTP requests.

What’s next

Configuration

Wire SQLite for DeskFlow on port 8000.

Making queries

CRUD on tasks and projects.

API reference

Method cheat sheet for Porm.