luany / framework
Luany Framework — Compiler-grade PHP MVC framework. Integrates luany/core and luany/lte.
Requires
- php: >=8.2
- luany/core: ^1.0
- luany/lte: ^1.0
- psr/log: ^3.0
- vlucas/phpdotenv: ^5.6
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
README
Application framework for Luany. IoC container, HTTP kernel, sessions, validation, config, CSRF, i18n.
Version: v1.0.0 | PHP: >= 8.2 | License: MIT Author: António Ambrósio Ngola | Org: luany-ecosystem
Table of Contents
- Overview
- Installation
- Application Container
- HTTP Kernel
- Config
- Session
- Validation
- CSRF Protection
- Exception Handling
- Helpers
- Service Providers
- Changelog
1. Overview
luany/framework is the application layer of the Luany ecosystem. It wires together the IoC container, HTTP lifecycle, session management, validation, configuration, and i18n into a coherent application runtime.
It depends on luany/core for routing, request and response primitives, and on luany/lte for the template engine.
2. Installation
composer require luany/framework
The framework is typically used through the application skeleton (luany/luany), which already configures everything correctly.
3. Application Container
The Application class is the IoC container and global registry.
use Luany\Framework\Application; $app = new Application(__DIR__); // pass the application root path // Bind a factory (new instance every call) $app->bind('mailer', fn($app) => new Mailer(env('MAIL_HOST'))); // Bind a singleton (resolved once, cached) $app->singleton('cache', fn($app) => new Cache(env('CACHE_DRIVER'))); // Store a pre-built instance $app->instance('db', $existingConnection); // Resolve from the container $cache = $app->make('cache'); $cache = app('cache'); // via helper
The Application instance is accessible globally via app().
Auto-resolution
Classes with no constructor dependencies are auto-resolved:
$handler = $app->make(SomeHandler::class);
Service Provider lifecycle
$app->register(new DatabaseServiceProvider()); // register() calls provider->register() immediately // boot() is deferred until $app->bootProviders() (called by Kernel)
Path helpers
$app->basePath() // /var/www/my-app $app->basePath('config') // /var/www/my-app/config $app->configPath() // /var/www/my-app/config $app->storagePath('logs') // /var/www/my-app/storage/logs $app->cachePath('views') // /var/www/my-app/storage/cache/views $app->viewsPath() // /var/www/my-app/views $app->routesPath() // /var/www/my-app/routes
4. HTTP Kernel
The Kernel orchestrates the full HTTP request lifecycle.
// public/index.php $app = new Application(__DIR__ . '/..'); $kernel = $app->make(Kernel::class); $kernel->boot(); $request = Request::fromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
Boot sequence
boot() runs in order:
registerConfig()— loadsconfig/*.phpregisterSession()— startsFileSessionregisterCsrf()— bindsCsrfTokenregisterLte()— binds the LTE view engineloadRoutes()— requiresroutes/http.phpbootProviders()— callsboot()on all registered providers
Global middleware
Override in your application kernel:
// app/Http/Kernel.php class Kernel extends \Luany\Framework\Http\Kernel { protected array $middleware = [ LocaleMiddleware::class, CsrfMiddleware::class, ]; }
Middleware runs on every request, before routing.
5. Config
Loads PHP files from config/ and provides dot-notation access.
// config/app.php return ['name' => 'My App', 'debug' => false]; // Usage config('app.name') // 'My App' config('app.missing', 'def') // 'def' config('app.debug') // false
Direct usage:
$config = app('config'); $config->get('app.name'); $config->set('app.debug', true); // runtime override $config->has('app.name'); // true $config->all(); // full array
6. Session
Cookie-based sessions backed by the filesystem (file-per-session in storage/sessions/).
// Via helper session() // SessionInterface instance session('user_id') // get value session('user_id', 0) // get with fallback // Via instance $session = app('session'); $session->set('user_id', 42); $session->get('user_id'); $session->has('user_id'); $session->forget('user_id'); $session->flash('success', 'Saved!'); // lives for one request $session->regenerate(); // new session ID $session->destroy();
Flash data is available on the next request only:
// In controller session()->flash('success', 'Record saved.'); // In view (next request) {{ session('success') }}
Old input is automatically available via old() after a failed validate():
<input name="email" value="{{ old('email') }}">
7. Validation
Validator directly
use Luany\Framework\Validation\Validator; $v = Validator::make($request->body(), [ 'name' => 'required|string|min:2|max:255', 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:8|confirmed', 'role' => 'required|in:admin,editor,viewer', ]); if ($v->fails()) { $errors = $v->errors(); // ['name' => ['The name field is required.']] } $data = $v->validated(); // only validated fields
validate() helper — recommended
The validate() helper removes all boilerplate from controllers. On failure it automatically flashes errors and old input to the session, then throws a ValidationException which the Kernel resolves as a redirect.
public function store(Request $request): Response { $data = validate($request->body(), [ 'name' => 'required|string|min:2|max:255', 'email' => 'required|email', ], '/users/create'); // redirect URL on failure User::create($data); return redirect('/users'); }
On failure the user is redirected to /users/create with errors and old input in session. The third argument defaults to $_SERVER['HTTP_REFERER'] if omitted.
In the view:
@ifempty(session('errors'))
{{-- no errors --}}
@else
@foreach(session('errors') as $field => $messages)
@foreach($messages as $message)
<p class="error">{{ $message }}</p>
@endforeach
@endforeach
@endisset
Available rules
| Rule | Description |
|---|---|
required |
Field must be present and non-empty |
string |
Must be a string |
email |
Must be a valid email address |
numeric |
Must be numeric |
min:N |
Minimum length (string) or value (numeric) |
max:N |
Maximum length (string) or value (numeric) |
in:a,b,c |
Must be one of the listed values |
confirmed |
Must match {field}_confirmation |
unique:table,col |
Must not already exist (requires setUniqueChecker) |
unique rule with database
Validator::setUniqueChecker(function (string $table, string $column, mixed $value): bool { return (bool) app('db')->table($table)->where($column, $value)->exists(); });
8. CSRF Protection
CsrfToken generates and validates tokens stored in the session.
$csrf = app('csrf'); $token = $csrf->token(); // get or generate token $csrf->validate($token); // throws if invalid
In LTE templates, use @csrf:
<form method="POST" action="/users"> @csrf ... </form>
The CsrfMiddleware automatically validates tokens on POST, PUT, PATCH, DELETE requests. It skips validation for GET, HEAD, OPTIONS.
9. Exception Handling
abort()
Abort the current request with an HTTP status code:
abort(404); abort(403, 'Forbidden'); abort(422, 'Unprocessable content');
Throws HttpException which the Kernel converts to the appropriate response.
Custom handler
// app/Exceptions/Handler.php class Handler extends \Luany\Framework\Exceptions\Handler { public function render(\Throwable $e): Response { if ($e instanceof SomeCustomException) { return Response::make(view('errors.custom'), 400); } return parent::render($e); } }
Register in a service provider:
$app->singleton(\Luany\Framework\Exceptions\Handler::class, fn() => new Handler( debug: (bool) env('APP_DEBUG', false) ));
Exception priority in Kernel
ValidationException→ flash is already done → redirect to$e->getRedirectTo()HttpException→Response::make($message, $statusCode)RouteNotFoundException→Response::notFound()- Everything else → custom
Handler::render()orResponse::serverError()
10. Helpers
All helpers are in src/Support/helpers.php and auto-loaded via Composer.
| Helper | Description |
|---|---|
app(?string $abstract) |
Resolve from container |
env(string $key, mixed $default) |
Get environment variable |
base_path(string $path) |
Absolute path from app root |
view(string $name, array $data) |
Render a view via LTE |
redirect(string $url, int $status) |
Create redirect Response |
response(string $body, int $status) |
Create Response |
config(string $key, mixed $default) |
Get config value |
session(?string $key, mixed $default) |
Get session or value |
csrf_token() |
Get current CSRF token |
old(string $key, mixed $default) |
Get previous request input |
validate(array $data, array $rules, string $back) |
Validate or throw |
abort(int $code, string $message) |
Abort with HTTP error |
__(string $key, array $replace) |
Translate a key |
locale() |
Get current locale |
11. Service Providers
Service providers are the recommended way to register application bindings.
use Luany\Framework\ServiceProvider; use Luany\Framework\Contracts\ApplicationInterface; class DatabaseServiceProvider extends ServiceProvider { public function register(ApplicationInterface $app): void { $app->singleton('db', fn() => new Connection( host: env('DB_HOST', '127.0.0.1'), database: env('DB_NAME', 'luany'), username: env('DB_USER', 'root'), password: env('DB_PASS', ''), )); } public function boot(ApplicationInterface $app): void { // Called after all providers are registered // Safe to resolve other bindings here } }
Register in bootstrap/app.php:
$app->register(new DatabaseServiceProvider());
Total: OK (174 tests, 223 assertions)
12. Changelog
next/v1 — Phase 6
New:
src/Exceptions/HttpException.php— thrown byabort(), carries status code and messagesrc/Exceptions/ValidationException.php— thrown byvalidate()on failure, carries errors + redirect URLhelpers.php— addedabort(int $code, string $message = ''): neverhelpers.php— addedvalidate(array $data, array $rules, string $back = ''): array
Modified:
src/Http/Kernel.php—handleException()handlesValidationExceptionandHttpExceptionbefore custom handler
v0.4.0 — Phase 2
IoC container (Application), HTTP Kernel, FileSession, Config, CsrfToken, CsrfMiddleware, Validator (9 rules), Translator, LocaleMiddleware. Full helpers.php: app(), env(), base_path(), view(), redirect(), response(), config(), session(), csrf_token(), old(), __(), locale().