Changelog
All XPSR packages share one version number. See docs/VERSIONING.md for what each kind of release promises.
Unreleased
0.4.0 (2026-10-29)
One change can affect existing code (see Breaking). Upgrade steps are in UPGRADE.md.
Breaking
- A missing view throws
Crystoline\Xpsr\View\ViewNotFoundExceptioninstead of Twig'sLoaderError. Code that catchesTwig\Error\LoaderErrorfor a missing template must catch the new exception instead.
Added
- View engines next to Twig, chosen with
app.view_engine(VIEW_ENGINE):crystoline/xpsr-view-plates(native PHP templates),crystoline/xpsr-view-latte(context-aware escaping), andcrystoline/xpsr-view-blade(Blade throughilluminate/view, with@csrfand the template functions on$xpsr), or aViewHandlerInterfaceclass of your own. See docs/VIEWS.md. Crystoline\Xpsr\View\ViewFunctionsInterface, through which the view provider gives every enginecsrf_token(),csrf_field(),session(),auth_user(), andcan();ViewNotFoundException; andCrystoline\Xpsr\Testing\ViewHandlerContractTestCase, the behavior every engine must have.crystoline/xpsr-storage: file storage on Flysystem 3. Named disks inconfig/storage.php, each aFilesystemOperatorfromStorageManager::disk(); driverslocal,memory, ands3(withleague/flysystem-async-aws-s3, for S3 and compatible services), and an application's own asstorage.driver.<name>.store()saves an upload under a random name with an extension from its content;download()streams a file as a response;fake()swaps a disk for memory in tests. See its README.crystoline/xpsr-scheduler: scheduled tasks from one cron entry.routes/schedule.phplists console commands, callbacks (services injected by type), and queued jobs with frequencies such asdaily('03:00'),weekly('monday', '08:00'),everyMinutes(15), orcron(), limited withweekdays(),weekends(),on(),when(), andskip(), inapp.timezoneor a task's owntimezone().withoutOverlapping()locks through Symfony Lock (files invar/locksby default, or a shared store).php xpsr schedule:run,schedule:list, andschedule:work. See its README.crystoline/xpsr-mail: email on Symfony Mailer, configured bymail.dsn(MAIL_DSN): SMTP, sendmail, provider bridges,log://(the default, written to the application log), andarray://for tests.Mailer::compose()renders a view as the HTML body with a text version;send()fills inmail.from;queue()sends from acrystoline/xpsr-queueworker.php xpsr mail:test. See its README.crystoline/xpsr-queue: queued jobs on Symfony Messenger. Jobs implementJobInterfaceand handle themselves (handle()gets services by type);QueueInterface::dispatch()with a delay or transport; transportssync://,in-memory://,database://, and Redis, AMQP, or SQS; retries with growing delays, then afailed_jobstable;php xpsr queue:work,queue:failed,queue:retry,queue:forget,queue:restart,make:job, andmake:queue-table;FakeQueuefor tests. See its README.TableMigrationCommandgainsstubPath()andreplacements(), for table migrations in other packages.crystoline/xpsr-orm-eloquent: Eloquent models on XPSR's database connections, sharing their transactions, withphp xpsr make:modeland theeloquentuser provider forconfig/auth.php. Schema changes stay XPSR migrations. See its README.StubGenerator::withStubDirectory(), formake:*commands in other packages.- Authentication in the framework (
Crystoline\Xpsr\Auth\):AuthManagerwithattempt(),verify(),login(),logout(), andintended(); guards configured inconfig/auth.php; the session guard for browsers;PasswordHasher(Argon2id or bcrypt, upgraded at sign-in);Identity::of($request); the middlewareAuthenticate(required, optional, or per guard),Guest, andRequireScopes; 401 and 403 errors withWWW-Authenticatechallenges. See docs/AUTH.md. - Several kinds of user: named providers in
auth.providers, aproviderand its ownlogin_pathandhome_pathper guard, a sign-in per session guard (attempt(..., guard: 'admin'),logout($request, 'admin'),Guest::guard('admin')), and API tokens bound to their guard (the JWTguardclaim and the token table'sguardcolumn). Policy methods typed for one kind of user refuse the others. - Authorization:
Gatewith policies per model class,before(), defined abilities, andauthorize()(403); the Twig functionsauth_user()andcan(). crystoline/xpsr-auth-jwt: JWTs for APIs onlcobucci/jwt, the default driver of theapiguard, withJwtIssuer, HS256 to HS512, RS256, and ES256, andphp xpsr auth:jwt-secretandauth:jwt. See its README.- In
crystoline/xpsr-database: users from a table (auth.usersdriverdatabase), and revocable API tokens (guard drivertoken) withTokenRepository,php xpsr make:token-table,auth:token, andauth:token-revoke. - In
crystoline/xpsr-orm-doctrine: users as entities (auth.usersdriverdoctrine). TestCase::actingAs()in application tests.- Sessions in the framework (
Crystoline\Xpsr\Session\):SessionMiddlewareandSession::of($request)with values, flash values,regenerate(), andinvalidate(); file and array stores;config/session.php;SessionServiceRegistry; andphp xpsr session:prune. Sessions do not use PHP'ssession_start(), store only JSON data, and set a cookie only once they hold something. See docs/SESSIONS.md. - CSRF protection:
CsrfMiddleware(403 without the session's token in_tokenorX-CSRF-Token), and the Twig functionscsrf_field(),csrf_token(), andsession(). - Session stores in packages:
CacheSessionStoreincrystoline/xpsr-cache(SESSION_STORE=cache), andDatabaseSessionStorewithphp xpsr make:session-tableincrystoline/xpsr-database(SESSION_STORE=database).Crystoline\Xpsr\Testing\SessionStoreContractTestCasechecks custom stores. Crystoline\Xpsr\Http\Cookie, forSet-Cookieheaders with safe defaults.ResponseBuilder::redirect(), 303 by default, andViewHandler::addFunction().- Application tests keep cookies between requests, and
TestCasegainspostForm(),csrfToken(),withSession(),session(), andsend().
Changed
- New projects have
view_engineinconfig/app.php. - New projects list
AuthServiceRegistryinapp.registry, haveconfig/auth.php, and runAuthenticate::optional()in theroutes/web.phpgroup..env.examplehasAUTH_API_DRIVERandAUTH_JWT_SECRET. - New projects list
SessionServiceRegistryinapp.registry, androutes/web.phpputs its routes in a group withSessionMiddlewareandCsrfMiddleware. The skeleton's exampleApp\Http\Middleware\SessionMiddlewareandApp\Registry\SessionServiceRegistry, which did not work, are removed, andconfig/session.phptakes new options. crystoline/xpsr-cacheandcrystoline/xpsr-databaserequirepsr/clock.- Every package's
composer.jsonlinks to the documentation website, xpsr.crysto.name.ng.
0.3.0 (2026-10-28)
Nothing breaks; upgrade steps are in UPGRADE.md.
Added
crystoline/xpsr-log-monolog: an optional package that makes Monolog the application logger, writing to stderr, a file, daily files, or syslog (LOG_CHANNEL), as lines or JSON (LOG_FORMAT, JSON by default in production), with a request ID on every line fromRequestIdMiddleware, which also echoes it inX-Request-Id. See its README.- Rate limiting in
crystoline/xpsr-cache: named sliding-window limits inconfig/rate_limit.php, attached with->middleware(RateLimit::named('login'))or throughapp.middleware. Clients are counted by address through the trusted proxies, or by a custom key resolver. Over the limit, 429 withRetry-After;X-RateLimit-LimitandX-RateLimit-Remainingon every response. See its README. - Request validation. The framework defines
Crystoline\Xpsr\Validation\ValidatorInterface,ValidationException(422 with field errors, shown in every environment), andInputMapper, which maps the body, query string, or route parameters onto input classes: field names from#[Field]and#[Input(naming: 'snake')], filters (Trim,Lowercase,Uppercase,EmptyToNull,Replace, custom#[Filter]), and type conversion reported as field errors. The newcrystoline/xpsr-validationpackage implements the rules with Symfony Validator and registersmake:input. See its README. Crystoline\Xpsr\Http\Exception\PublicErrorInterface: an error whose message and details the exception handler shows to clients in every environment.Crystoline\Xpsr\Http\ClientIp: the client address of a request, read through the trusted proxies'X-Forwarded-Forchain.
0.2.0 (2026-10-28)
Upgrade steps are in UPGRADE.md.
Breaking
- Every class moves from
Crystoline\toCrystoline\Xpsr\, for exampleCrystoline\ApplicationtoCrystoline\Xpsr\Application,Crystoline\Cache\CacheServiceRegistrytoCrystoline\Xpsr\Cache\CacheServiceRegistry, andCrystoline\Router\FastRoute\FastRouteRoutertoCrystoline\Xpsr\Router\FastRoute\FastRouteRouter. The vendor namespace is no longer taken by one product, and it matches thecrystoline/xpsr-*package names. There are no aliases for the old names;UPGRADE.mdhas a command that rewrites them. - The
database_path()helper is removed. It pointed at adatabase/directory XPSR does not use; migrations live inmigrations/. The 0.1 skeleton'sconfig/database.phpcalls it, so replace or delete that file when upgrading.
Added
- PSR-14 events:
Crystoline\Xpsr\Events\EventDispatcher,ListenerProvider, and the optionalStoppableEventbase class, registered byCrystoline\Xpsr\Registry\EventServiceRegistry. Listeners are listed inconfig/events.phpor added by providers with a priority. See docs/EVENTS.md. php xpsr event:list,make:event, andmake:listener.- New projects list
EventServiceRegistryinapp.registryand includeconfig/events.php. Existing projects can add both to use events. crystoline/xpsr-http-client: an optional package providing a configured PSR-18 client, named clients (http_client.<name>) with their own base URL and headers, Symfony'sHttpClientInterface, andTesting\FakeClientfor tests. Options live inconfig/http_client.php. See its README.swap($id, $replacement)in the applicationtests/TestCase.php, to replace a service in a test.crystoline/xpsr-database: an optional package providing Doctrine DBAL connections configured inconfig/database.php, and migrations withmigrate,migrate:rollback,migrate:status, andmake:migration. In production, migrations need--forceor a confirmation. See its README.- Database extension points: the migrate commands use
MigratorInterface, chosen withdatabase.migrations.runner, so another runner can replace XPSR's;Testing\MigratorContractTestCasechecks a runner;Testing\DatabaseTransactionsrolls back each test's database writes. crystoline/xpsr-orm-doctrine: an optional Doctrine ORM 3 adapter. TheEntityManagerruns on XPSR's connection, with attribute-mapped entities inapp/Entity, snake-case columns, PHP 8.4 native lazy objects instead of proxy classes, and the application cache for metadata in production. Includes Doctrine's read-only and cache commands, an XPSRorm:validate-schema, andClearEntityManagerMiddlewarefor worker servers. See its README.php xpsr make:migration <name> --diffwrites a migration from the difference between the database and an expected schema, such as Doctrine's entity mapping. Only tables the schema names are compared.Schema\SchemaProviderInterfacelets an ORM adapter supply the schema;Testing\OrmAdapterContractTestCasechecks ORM adapters.- CORS:
Crystoline\Xpsr\Http\Middleware\CorsMiddleware, configured inconfig/cors.phpandCORS_ALLOWED_ORIGINS. It answers preflight requests, adds the headers to every response from an allowed origin, error responses included, and supports subdomain patterns. New projects list it first inapp.middleware; it is inactive until an origin is allowed. See docs/HTTP.md. - PSR-20 clock:
Psr\Clock\ClockInterfaceis registered as asymfony/clockNativeClockinapp.timezone. The applicationTestCasegainsfreezeTime().
Changed
- The skeleton's
config/database.phpis replaced: the previous file was unused and came from another framework. The new one configures SQLite, MySQL, and PostgreSQL connections forcrystoline/xpsr-database.
Fixed
- Route building is 18 to 48 times faster. Route patterns were parsed again on every use, and constraints re-validated each time, so 300 routes took about 276 ms per request; they now take about 6 ms.
RouteCollection::named()and URL generation no longer scan every route.
0.1.1 (2026-10-28)
Fixed
php xpsr aboutandphp xpsr --versionreport the installed framework version, such as0.1.1, instead of always sayingdevelopment.
Deprecated
Crystoline\Console\ConsoleKernel::VERSION. UseConsoleKernel::version(). The constant still exists and still equalsdevelopment.
0.1.0 (2026-10-28)
First release of the rebuilt framework. It replaces the earlier prototype, which was published as crystoline/xpsr v1.0.0; there is no upgrade path from it. Start a new project and move application code across.
Added
- Packages:
crystoline/xpsr(skeleton),crystoline/xpsr-framework,crystoline/xpsr-router-fastroute, andcrystoline/xpsr-router-sunrise. - Application bootstrap with
.envloading and per-environment configuration overrides (config/{APP_ENV}/). - PSR-11 container with explicit bindings, autowiring, aliases, and eager or deferred service providers with register/boot stages.
- PSR-15 HTTP kernel, global and route middleware, body parsing for JSON and non-POST forms, and a response emitter.
- Portable route definitions with groups, names, constraints, defaults, and URL generation, served by the FastRoute or Sunrise adapter. HEAD, OPTIONS, 404, and 405 are handled.
- JSON or HTML error rendering by content negotiation; exception details only with
APP_DEBUG=true. Server errors are logged through PSR-3 (error_log()by default). - Forwarding headers trusted only from
TRUSTED_PROXIES. xpsrconsole withabout,route:list,serve,setup, and themake:controller,make:middleware,make:command, andmake:providergenerators.- Application test scaffold (
tests/TestCase.php) with request and console helpers. - Router contract test suite for adapter authors (
Crystoline\Testing\RouterContractTestCase). - Documentation: getting started, deployment, HTTP, routing, container, console, packages, and versioning.
Cache
crystoline/xpsr-cache: an optional package providing the application cache as PSR-16 and PSR-6, backed by Symfony Cache, withfile,array,null, andservicedrivers and acache:clearcommand.
HTTP exceptions
Crystoline\Http\Exception\*:HttpException,HttpExceptionInterface, and classes for 400, 401, 403, 404, 405, 409, 413, 415, 422, 429, and 503. The framework no longer depends onsymfony/http-kernelorsymfony/http-foundation. Symfony's HttpKernel exceptions are still rendered with their status when that package is installed.
Optional dependencies
twig/twig(views) andfilp/whoops(debug error pages) are suggestions of the framework, not requirements. The skeleton requires Twig for its web example and installs Whoops for development only. Without Whoops, debug mode shows a plain built-in error page.
Requirements
- PHP 8.4 or 8.5.