Pionia v3 — Release notes


Who this is for

You are planning a v3 upgrade for an existing Pionia app or starting fresh with DeskFlow. This page lists platform requirements, breaking changes, and links to detailed guides.

What you will learn

  • PHP 8.5 requirements and the pionia/pionia-app template split
  • Major v3 capabilities: AppRealm, native HTTP stack, RoadRunner, async Moonlight
  • Where to read migration steps vs feature deep dives

Before you start

Before you start

How it works

v3 preserves the Moonlight contract Northwind clients already know — { "service", "action" } on /api/v1/ with the same envelope. The platform underneath changed: bootstrap, routing, CLI, caching, and deploy tooling are now native to Pionia.

Pionia 3.0 is a major release that modernizes the framework kernel, reduces third-party dependencies, and adds first-class tooling for production deployment, persistent workers, and full-stack applications.

This document summarizes what is new, what changed, and where to read the detailed guides. For step-by-step migration from v2, see Upgrading from v2.

Release overview

Releasepionia/pionia-core 3.0
Application templatepionia/pionia-app
PHP requirement8.5+
DocumentationThis site (/)

v3 preserves the Moonlight contract: versioned API paths, { "service", "action" } dispatch, and a consistent response envelope. What changed is the platform underneath — bootstrap, HTTP stack, CLI, caching, exception handling, and deploy-time optimization are now native to Pionia.

Platform requirements

PHP 8.5

v3 targets PHP 8.5 as the minimum supported version. The framework uses modern language features throughout the kernel:

FeatureUsage in v3
#[\NoDiscard]Helpers such as response(), app(), and async()
Pipe operator |>Request preparation in WebKernel
array_find / array_anyCollection utilities
renderToString()Test-safe rendering without exit()

Upgrade PHP before updating Composer dependencies. Older runtimes are not supported on the 3.x line.

Package split

v3 introduces a clear separation between the framework library and the application template:

PackageRole
pionia/pionia-coreFramework library (Packagist)
pionia/pionia-appApplication scaffold (composer create-project)

Create a new project:

composer create-project pionia/pionia-app my-api
cd my-api && php pionia serve

Architecture

AppRealm bootstrap

PioniaApplication is replaced by AppRealm — a singleton application container booted once per process.

Concernv2v3
Bootstrapnew PioniaApplication(BASEPATH)return AppRealm::create(__DIR__)
HTTP entryhandleRequest()bootHttp() / handleRequest()
CLI entryad-hoc wiringbootConsole()
Helpersmixedapp(), realm(), container() → same instance

API switches register through [app_switches] in environment/settings.ini instead of imperative router wiring in bootstrap files.

Request lifecycle

HTTP handling separates boot from dispatch — required for RoadRunner workers and testable in isolation:

MethodPurpose
WebApplication::bootOnce()Run powerUp() once per process
WebApplication::handleRequest(Request)Match route and return Response (no send())
WebApplication::fly()FPM entry: globals → handle → send
WebApplication::resetBetweenRequests()Flush per-request state in worker mode

runtimeMode() reports fpm, cli, worker, or testing so components can adapt behavior without environment checks scattered through application code.

Naming conventions

Framework base types use short names — no Base* prefix. Application code uses descriptive names (MainSwitch, JwtAuthentication, RequestIdMiddleware).

LayerFramework typeApplication example
SwitchPionia\Http\Switches\ApiSwitchMainSwitch
ServicePionia\Http\Services\ServiceAuthService
Response envelopePionia\Http\Response\ApiResponseresponse() helper
HTTP responsePionia\Http\Response\ResponseResponse::fromEnvelope()
AuthenticationPionia\Auth\AuthenticationJwtAuthentication
MiddlewarePionia\Middlewares\MiddlewareRequestIdMiddleware
CommandPionia\Console\CommandSyncOrdersCommand
ProviderPionia\Base\Provider\ProviderAppProvider

ApiSwitch retains the Api prefix because Switch is a PHP reserved keyword.

Native HTTP stack

v3 removes Symfony Routing, HttpFoundation, and HttpKernel from the runtime dependency graph. Routing and dispatch are implemented in Pionia\Http\Routing and Pionia\Http.

Removed dependencyNative replacement
symfony/routingRouteTable, RouteMatcher, RouteDefinition
symfony/http-kernelRouteDispatcher
symfony/http-foundationRequest, Response, ParameterBag, UploadedFile
symfony/mimePionia\Http\Mime\MimeType
symfony/assetasset() helper
symfony/filesystemPionia\Utils\Filesystem
symfony/dotenvPionia\Utils\Dotenv
symfony/uidPionia\Utils\Ulid
symfony/event-dispatcherPionia\Events\PioniaEventDispatcher (PSR-14)
symfony/cacheCacheManager + PSR-16 adapters
symfony/processPionia\Process\Process
symfony/consolePionia\Console\Application

Switches still register under /api/{version}/ via [app_switches]. The kernel matches routes, serves static assets, applies SPA fallback, and dispatches Moonlight POST bodies to services.

Production deployments can compile routes into bootstrap caches with php pionia optimize --production. See HTTP routing.

RoadRunner and persistent workers

v3 adds first-class RoadRunner integration for persistent PHP workers — boot the framework once, serve many requests.

CapabilityDetail
Worker entryworker.php + .rr.yaml in the application template
CLIphp pionia runserver, stopserver, runserver:logs, rr:setup
Connection poolingConnectionManager reuses PDO across requests; disconnect() on worker shutdown only
Listen addressResolved from CLI flags → .envsettings.ini.rr.yaml → default 8000
Maintenance modeWorkers re-read settings.ini without restart

RoadRunner is optional. php pionia serve remains the zero-config development server.

See RoadRunner.

Background work and async Moonlight

v3 formalizes post-response work and durable job dispatch:

APIWhen to use
defer(Closure)Fire-and-forget after the HTTP response (logging, webhooks)
async(Closure)Same timing, with promise chaining
async('service', 'action', $payload)Durable work via RoadRunner Jobs
moonlight()->async(...)API-style 202 Accepted with job_id

All transports share the same { service, action, ...params } envelope — HTTP POST, programmatic dispatch, queued jobs, and WebSocket RPC (Centrifugo via RoadRunner) invoke the same service actions.

Enable jobs in settings.ini when RoadRunner is running with a jobs pool:

[jobs]
ENABLED = true
PIPELINE = moonlight
RPC = tcp://127.0.0.1:6001

See Background work.

Production performance

Deploy-time optimization is opt-in — readable PHP source ships by default; warm OPcache and bootstrap caches at cutover.

composer install --no-dev -o
php pionia optimize --production

The --production preset enables:

  • Authoritative Composer classmap
  • Bootstrap caches (storage/bootstrap/routes.php, providers.php)
  • Hybrid OPcache preload (framework manifest from Packagist + app paths + optional stats snapshot)

Framework maintainers generate a portable preload manifest at release time; consumer apps merge it during php pionia optimize. Monitor hit rates on /stats when STATS_ENABLED or DEBUG is active.

See Production performance.

Native CLI and code generation

v3 ships a native console at Pionia\Console\Application — no Symfony Console in the framework runtime.

AreaDetail
Entry./pioniabootConsole() (same bootstrap as HTTP)
CommandsExtend Pionia\Console\Command
REPLphp pionia shell (aliases: tinker, repl)
Generatorsmake:service, make:switch, make:command, make:middleware, make:auth, make:provider
Version bannerShows installed pionia/pionia-core version from Composer

See Commands (Pionia CLI).

Caching

v3 replaces Symfony Cache with a native PSR-16 layer (PioniaCache + CacheManager).

StoreSTORE valueBest for
Filesystemfilesystem (default)Single server
ArrayarrayTests, per-worker memory
NullnullDisable without code changes
DatabasedatabaseShared cache via PDO
APCuapcuRoadRunner / FPM on one host
RedisredisProduction clusters

Custom stores register through CacheManager::extend() in a provider. CLI: cache:clear, cache:prune, cache:delete {key}.

See Caching in Pionia.

Exception pipeline

All uncaught throwables flow through ExceptionPipeline — switches and the HTTP kernel do not use ad-hoc try/catch for normal errors.

Configure once at bootstrap or in a provider:

$app->exceptions()
    ->handler(App\ExceptionHandler::class)
    ->reportable(fn (Throwable $e) => /* Sentry */)
    ->dontReport(ValidationException::class)
    ->map(ResourceNotFoundException::class, fn ($e) => response(404, $e->getMessage()));
HelperPurpose
report($e)Log via the pipeline without rendering
pionia_handle_exception($e, $request)Full report + render
RenderableExceptionDomain errors that control their own HTTP response

Debug JSON includes exception details when DEBUG=true; production responses hide internals.

See Exceptions & error handling.

Security utilities

v3 adds Pionia\Security\Security — a single cryptography and randomness surface with matching global helpers:

$token = secure_token();
$hash  = secure_hash_password('secret');
$plain = secure_decrypt($cipher);

Covers CSPRNG tokens, OTPs, ULIDs, password hashing (Argon2id / bcrypt), HMAC, symmetric encryption (libsodium), and hybrid RSA for large payloads. Set APP_KEY in .env for encryption at rest.

See Security utilities.

Validation

Action methods support declarative validation via #[Validated] and #[ValidateField] attributes — rules run automatically before the action body. Imperative rules() and validate() chains remain available for complex cases.

ValidationException maps to HTTP 422 through the exception pipeline.

See Validations.

API documentation and observability

FeatureDetail
Moonlight OpenAPI@moonlight-* PHPDoc tags → php pionia api:docs
Interactive UI/docs (Scalar) when DOCS_ENABLED or DEBUG
JSON catalog/api/v1/__catalog and php pionia api:catalog
Request metrics/stats, /stats.json, php pionia stats:view
Token gatesDOCS_TOKEN, STATS_TOKEN for staging without DEBUG

See API reference (Moonlight) and Developer stats.

Frontend integration (Vite)

v3 scaffolds Vite SPAs alongside the Moonlight API:

composer create-project pionia/pionia-app my-app -- --react-ts
php pionia frontend:scaffold --framework=vue-ts --yes
php pionia frontend:dev    # dev proxy to /api
php pionia frontend:build  # dist/ → public/

Supported templates: React, Vue, and Svelte (TypeScript). When SPA_FALLBACK=true or public/index.html exists, client routes fall back to the SPA shell.

See Frontend integration (Vite).

Service providers

Packages and applications extend Pionia through Provider (ProviderContract):

HookUse for
middlewares()Global middleware chain
authentications()Auth backends
commands()CLI command registration
routes()Package API switches
configureLogging()Custom log channels
configureCaching()Custom cache stores
configureExceptions()Pipeline handlers and maps
configureValidations()Custom validation rules
onBooted()Container bindings after stack registration

Register in [app_providers] in settings.ini or via pionia()->addAppProvider() in bootstrap.

See App providers.

Operations

FeatureCommand / path
Maintenance modephp pionia maintenance:on / maintenance:off (aliases: down / up)
Framework assets/__pionia/{path} (favicon, welcome page CSS)
User static files/static/{path} from public/static/
Media uploads/media/{path} from storage/media/
Welcome pageGET /public/index.html if present, else framework welcome

Maintenance returns HTTP 503 for all routes except /__pionia/*. Bypass with ?bypass= or X-Maintenance-Bypass.

See Maintenance mode.

Breaking changes from v2

The following require attention when upgrading existing applications:

Areav2v3
BootstrapPioniaApplicationAppRealm::create()
Switch registrationImperative PioniaRouter wiring[app_switches] in settings.ini
registerServices() return typearrayArrayable (arr([...]))
Static assetsstatic/ at repo rootpublic/static/
HTTP routingSymfony RoutingNative RouteTable / RouteMatcher
ConsoleSymfony Console (transitive)Native Pionia\Console\Application
CacheSymfony CacheNative CacheManager (PSR-16)
PHP version8.x (varies)8.5+ required

Framework class renames follow the v3 naming table above (ApiSwitch, ApiResponse, etc.). A fresh scaffold plus porting services/, switches/, and environment/ is often faster than incremental migration.

See Upgrading from v2 for a complete checklist.

Documentation map

TopicGuide
First projectIntroduction
Folder layoutApplication structure
Moonlight modelMoonlight architecture
DatabasePorm (database)
Deploy checklistDocumentation hub — Production checklist

Getting help

  • Resources — community links and in-project commands
  • Enable DEBUG=true temporarily and inspect storage/logs/ for error details
  • Open an issue on the repository you installed from Packagist

Common mistakes

  • Upgrading Composer before PHP 8.5 — v3 will not boot on older runtimes.
  • Keeping Symfony route files — v3 uses native RouteTable; remove dead routes.php wiring.
  • Assuming v2 PioniaApplication still exists — rename to AppRealm::create() everywhere.
  • Skipping php pionia optimize --production — deploy works without it, but you miss preload and route caches.

What’s next

Upgrading from v2

Step-by-step migration checklist.

Introduction

Scaffold a fresh v3 DeskFlow app.

RoadRunner

Persistent workers in v3.


Pionia v3 release notes — last updated for pionia/pionia-core 3.0.