webpagestudio / wps-micro
Lightweight PHP framework for building web applications
Requires
- php: ^8.3
- ext-json: *
- ext-mbstring: *
- ext-pdo: *
- psr/container: ^2.0
- twig/twig: ^3.0
Requires (Dev)
- phpunit/phpunit: ^12.5
README
Lightweight PHP framework core for building focused web applications.
WPS Micro provides the reusable request lifecycle, container, routing, middleware, validation, sessions, Twig integration, database access, migrations, and console primitives. Application controllers, models, routes, views, frontend assets, and deployment files live in a separate application skeleton.
Requirements
- PHP 8.3 or higher
- Composer
- PDO and mbstring PHP extensions
The test suite runs against PHP 8.3, 8.4, and 8.5 in GitHub Actions.
Start A New Application
Use the application skeleton instead of installing the framework into an empty directory:
composer create-project webpagestudio/wps-micro-skeleton my-site
The skeleton requires this package and keeps application code outside
vendor/. Framework updates therefore do not replace controllers, models,
routes, migrations, or templates:
composer update webpagestudio/wps-micro
For an existing Composer project, install only the core:
composer require webpagestudio/wps-micro
Package Structure
src- framework runtime and public APIssrc/Console- console application and reusable commandssrc/Exceptions- framework and HTTP exceptionssrc/Middleware- built-in middlewaretests- framework unit and integration tests
All framework classes use the WpsMicro\Core\ namespace.
Application Boundary
The framework package owns infrastructure:
Request,Response,Router, andDispatcherContainerandKernel- middleware pipeline and CSRF protection
- validation, sessions, and error handling
- Twig rendering and Vite manifest integration
- PDO connection, models, migrations, and migrator
- console application and generator commands
The application owns product behavior:
- controllers and application middleware
- models, repositories, and business services
- routes and configuration
- database migrations
- Twig templates and frontend assets
- public entry point and deployment configuration
Core classes never import App\ classes or assume an application directory.
The application passes routes, middleware, paths, and error handlers through
configuration.
Request Lifecycle
Request -> Global Middleware -> Router -> Route Middleware -> Controller -> Response
The Kernel registers framework services in the PSR-11 container. The
Dispatcher executes the middleware pipeline, resolves a controller through
the container, invokes the matched action, and normalizes the result to a
Response.
Bootstrap
A minimal application bootstrap can load environment values and create a kernel from a PHP configuration file:
<?php declare(strict_types=1); use WpsMicro\Core\Env; use WpsMicro\Core\Kernel; $rootPath = dirname(__DIR__); require $rootPath . '/vendor/autoload.php'; Env::load($rootPath . '/.env'); return Kernel::fromConfigFile($rootPath . '/config/app.php');
When router.routes_path is configured, the file must be readable and return
a callable. Invalid configuration fails during kernel boot instead of leaving
the application with an empty router.
The public front controller handles globals and sends the response:
/** @var \WpsMicro\Core\Kernel $kernel */ $kernel = require dirname(__DIR__) . '/bootstrap/app.php'; $kernel->handleGlobals()->send();
Container Overrides
Framework services are registered as defaults. Explicit application bindings in a supplied container are preserved, so infrastructure can be replaced without changing the core:
use WpsMicro\Core\Config; use WpsMicro\Core\Container; use WpsMicro\Core\Kernel; use WpsMicro\Core\Router; $container = new Container(); $container->instance(Router::class, new Router()); $kernel = new Kernel(new Config($config), $container);
Use Container::bound() to check for an explicit factory or instance binding.
Container::has() also reports concrete classes that can be autowired.
Routing
Route files return a callable that receives the router:
use App\Controllers\ControllerProduct; use WpsMicro\Core\Router; return static function (Router $router): void { $router->get('/products/{id}', [ControllerProduct::class, 'actionShow']); $router->post('/cart/add', [ControllerCart::class, 'actionAdd']); $router->delete('/cart/{id}', [ControllerCart::class, 'actionRemove']); };
Explicit HEAD routes are supported. When no explicit route exists, a HEAD
request falls back to the matching GET route and returns the same status and
headers without a response body.
Controllers And Responses
Application controllers may extend the framework controller:
namespace App\Controllers; use WpsMicro\Core\Controller; use WpsMicro\Core\Response; final class ControllerProduct extends Controller { public function actionShow(string $id): Response { return $this->render('products/show.twig', ['id' => $id]); } }
Controller actions must return a Response instance. Returning strings or
using echo as the response body is not supported in v3. This keeps response
status, headers, middleware, and error handling deterministic.
Controller helpers return HTML, JSON, and redirects:
return $this->render('products/index.twig', ['products' => $products]); return $this->json(['products' => $products]); return $this->redirect('/products');
Response supports case-insensitive header lookup and multiple values:
$response ->setHeader('Cache-Control', 'no-store') ->addHeader('Set-Cookie', 'theme=dark; Path=/; SameSite=Lax') ->addHeader('Set-Cookie', 'locale=en; Path=/; SameSite=Lax');
Middleware
Middleware implements WpsMicro\Core\Middleware:
use WpsMicro\Core\Middleware; use WpsMicro\Core\Request; use WpsMicro\Core\Response; final class AuthMiddleware implements Middleware { public function handle(Request $request, callable $next): Response { return $next($request); } }
Middleware can be configured globally, as default route middleware, or attached to one route.
Validation
The validator supports:
required,nullable, andconfirmedstring,array, andbooleanemail,url,integer, andnumericmin,max, andin
Browser validation failures flash sanitized input and redirect only to a
same-origin location. JSON requests receive a 422 response without starting
or changing the session.
Database And Migrations
Database creates a configured PDO connection. Model provides that connection
to application persistence classes, while business workflows remain in
application services. The built-in migrator is tested with SQLite and MariaDB;
the MySQL-compatible migration path is also suitable for MySQL.
Migration files return a Migration instance and implement both directions:
use WpsMicro\Core\Migration; return new class extends Migration { public function up(PDO $db): void { $db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY)'); } public function down(PDO $db): void { $db->exec('DROP TABLE products'); } };
Console Commands
The framework provides migration commands and configurable generators. Applications decide where generated files are written:
$console ->add(new MakeControllerCommand($root . '/app/Controllers', 'App\\Controllers')) ->add(new MakeModelCommand($root . '/app/Models', 'App\\Models')) ->add(new MakeMigrationCommand($root . '/database/migrations'));
No generator writes inside the installed framework package.
Development
Install dependencies and run the framework suite:
composer install
composer test
Validate package metadata:
composer validate --strict --no-check-publish
The regular local suite skips the MariaDB integration test when its test environment variables are not configured. GitHub Actions runs that test against a real MariaDB service.
Versioning And Security
WPS Micro follows Semantic Versioning. Breaking public API changes are reserved for major releases, backward-compatible features for minor releases, and fixes for patch releases.
See CHANGELOG.md for release history, UPGRADING.md for migration instructions, and SECURITY.md for supported versions and private vulnerability reporting.