Portable routing
Declare routes once, with groups, constraints, middleware, and names. FastRoute or Sunrise matches them, chosen with one setting.
Read more about portable routingPHP 8.4+MIT licensed Latest release 0.3
Requests, middleware, the container, events, and logs all speak PSR. Choose the router, the view engine, and the ORM; your application code stays the same.
composer create-project crystoline/xpsr my-app
<?php
use App\Http\Controller\TaskController;
use App\Http\Middleware\RequireApiKey;
use Crystoline\Xpsr\Routing\RouteCollection;
return static function (RouteCollection $routes): void {
$routes->get('/tasks', [TaskController::class, 'index'])
->name('tasks.index');
$routes->get('/tasks/{id}', [TaskController::class, 'show'])
->where('id', '\d+')
->name('tasks.show');
$routes->post('/tasks', [TaskController::class, 'store'])
->middleware(RequireApiKey::class);
};
<?php
namespace App\Http\Controller;
use App\Tasks\TaskRepository;
use Crystoline\Xpsr\Http\Exception\NotFoundHttpException;
use Crystoline\Xpsr\Http\Response\ResponseBuilder;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
final class TaskController
{
// The container supplies both by type: no registration needed.
public function __construct(
private readonly TaskRepository $tasks,
private readonly ResponseBuilder $responses,
) {}
public function show(ServerRequestInterface $request): ResponseInterface
{
$task = $this->tasks->find((int) $request->getAttribute('id'))
?? throw new NotFoundHttpException();
return $this->responses->json($task);
}
}
<?php
namespace Tests\Application;
use Tests\TestCase;
final class TaskTest extends TestCase
{
// Requests run through the whole HTTP kernel, without a web server.
public function testAMissingTaskIsNotFound(): void
{
$response = $this->get('/tasks/99', ['Accept' => 'application/json']);
self::assertSame(404, $response->getStatusCode());
self::assertSame(['message' => 'Not Found'], json_decode((string) $response->getBody(), true));
}
}
Standards first
Each part of XPSR implements or consumes a PHP-FIG standard, so libraries written for those standards work without adapters.
What you get
The framework stays small. Routers, view engines, ORMs, queues, and mail are separate packages that you install when you need them.
Declare routes once, with groups, constraints, middleware, and names. FastRoute or Sunrise matches them, chosen with one setting.
Read more about portable routingConstructor injection by type, explicit bindings, and providers that load on first use. Any PSR-11 consumer can read it.
Read more about an explicit containerTwig by default, or Plates, Latte, or Blade. The same template functions, such as csrf_field() and can(), work in each.
Read more about your view engineSessions with CSRF protection, session and JWT guards, separate user tables such as admins, and policies for permissions.
Read more about sessions and sign-inQueued jobs on Symfony Messenger, email on Symfony Mailer, file storage on Flysystem, and scheduled tasks from one cron entry.
Read more about work in the backgroundApplication tests send requests through the whole HTTP kernel and run console commands in-process. Freeze the clock, or swap a service.
Read more about tests without a serverPackages
Every package shares one version number and is released together. Packages marked Next release are on the development branch.
XPSR framework library: application lifecycle, PSR-11 container, PSR-15 HTTP kernel, routing contracts, providers, and console
FastRoute adapter for XPSR's portable route definitions
Sunrise router adapter for XPSR's portable route definitions
PSR-16 and PSR-6 caching for XPSR applications, backed by Symfony Cache, with a cache:clear command
Database connections and migrations for XPSR applications, backed by Doctrine DBAL
Doctrine ORM for XPSR applications: an EntityManager on XPSR's database connection, attribute-mapped entities, and migrations generated from the mapping
Eloquent models for XPSR applications, on the XPSR database connection and its transactions, with XPSR's migrations
Request validation for XPSR applications: input classes checked with Symfony Validator rules, answered with 422 and field errors
Outgoing HTTP for XPSR applications: a configured PSR-18 client and named clients, backed by Symfony HttpClient, with a fake client for tests
Monolog logging for XPSR applications: stderr, file, daily, or syslog output, line or JSON format, and a request ID on every log line
JWT sign-in for XPSR APIs: the jwt guard driver on lcobucci/jwt, a token issuer, and a secret generator
Queued jobs for XPSR applications on Symfony Messenger: self-handling jobs, sync, database, Redis, AMQP, or SQS transports, retries, and a failed jobs table
Email for XPSR applications on Symfony Mailer: a DSN-configured transport, templates through the view layer, queued sending, and test transports
Scheduled tasks for XPSR applications: commands, closures, and queued jobs on cron schedules, run from one cron entry
File storage for XPSR applications on Flysystem: named disks, local and in-memory drivers, S3 through its adapter, uploads, and downloads
Plates views for XPSR applications: native PHP templates in place of Twig
Latte views for XPSR applications: context-aware escaping templates in place of Twig
Blade views for XPSR applications, standalone through illuminate/view, in place of Twig
The tutorial builds a small tasks API with middleware, a console command, and tests, then switches the router without touching a route.