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 calltable()from Pionia Shop services - Filter, join, paginate, and aggregate Pionia Shop tables
- Pool PDO connections under RoadRunner and FPM
- API tutorial — Pionia Shop services before persistence
- Helpers —
table(),db(), andconnectionManager()
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:
- Create tables with
php pionia make:tableandphp pionia migrate— see Migrations. - Configure
[db]inenvironment/settings.ini(SQLite is fine for local Pionia Shop). - In
ProductService::listAction, replace the array withtable('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
| Topic | Page |
|---|---|
| Schema & migrations | Migrations |
| Configuration & entry points | Getting started |
| CRUD & reads | Making queries |
filter(), orderBy, limit | Filtering |
| WHERE operators & clause keys | WHERE DSL reference |
| Joins & aliases | Relationships & joins |
count, sum, Agg builder | Aggregation |
PaginationCore & list APIs | Pagination |
| Multi-DB & pooling | Connections |
| Transactions & raw SQL | Transactions & raw SQL |
chunk, random, explain | Performance |
| Method cheat sheet | API 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.
Related docs
- Generic services — CRUD over Porm via
GenericService - Advanced generic services — joins, column aliases, pagination from HTTP
- RoadRunner —
ConnectionManagerkeeps PDO alive across requests - Helpers —
table(),db(),connectionManager()
Common mistakes
- Using
Porm::from()orDb::from()in Pionia Shop services — prefer the globaltable()helper wired in bootstrap. - Calling
save()afterfilter()on the same chain — finish reads withall()/get()first, then start a newtable('products')for writes. - Skipping
[db]insettings.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 calldisconnect()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.