Database (Porm)

This section is for developers building Pionia Shop — the store API on port 8000. Porm is how ProductService, CustomerService, and OrderService query products, customers, and orders without Eloquent-style models.

What you will learn

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

How it works

flowchart LR
  ProductService --> table["table('products')"]
  CustomerService --> tm["table('customers')"]
  OrderService --> orders["table('orders')"]
  table --> Porm["Porm / Piql"]
  tm --> Porm
  orders --> Porm
  Porm --> SQLite[("SQLite / PostgreSQL")]

Pionia includes Porm (Pionia ORM) — a Medoo-inspired query builder, not a full Eloquent-style ORM. You work with tables, arrays, and a fluent API. Schema changes use PHP migrations (Schema + Blueprint) — see Migrations.

Quick start

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

// Named connection from environment/settings.ini
table('orders', null, 'db_pgsql')->save(['customer_id' => 1, 'status' => 'pending', 'total' => 49.0]);

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

Your first database steps (Pionia Shop)

In the Pionia Shop tutorial you start with a hardcoded catalog, then persist products in SQLite and add product.create:

  1. Create tables with php pionia make:table and php pionia migrate — see Migrations.
  2. Configure [db] in environment/settings.ini (SQLite is fine for local Pionia Shop).
  3. In ProductService::listAction, replace the array with table('products')->all().
$products = table('products')
    ->filter(['stock[>]' => 0])
    ->orderBy('created_at', 'DESC')
    ->limit(20)
    ->all();

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

Guide map

TopicPage
Schema & migrationsMigrations
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('products')
  ├─ 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 Pionia Shop 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('products') for writes.
  • Skipping [db] in settings.ini — Pionia Shop 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

Migrations

make:table, migrate, Blueprint columns.

Configuration

Wire SQLite for Pionia Shop on port 8000.

Making queries

CRUD on tasks and projects.