libxa / framework
LibxaFrame core framework β Foundation, Router, Atlas ORM, Blade-X, Reactive, Modules, Frontend Adapters.
Requires
- php: ^8.3
- nesbot/carbon: ^3.0
- nikic/php-parser: ^5.7
- psr/container: ^2.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/log: ^3.0
- symfony/console: ^7.0
- vlucas/phpdotenv: ^5.6
- workerman/workerman: ^4.1
Requires (Dev)
- phpstan/phpstan: ^1.10 || ^2.0
- phpunit/phpunit: ^11.5 || ^12.0 || ^13.0
- squizlabs/php_codesniffer: ^3.9
This package is auto-updated.
Last update: 2026-08-08 18:46:40 UTC
README
The modern, elegant, and lightning-fast PHP framework for the next generation of web applications. Built around developer happiness, performance, and scalability.
Features
- π Blazing Fast - Optimized for performance with minimal overhead
- π§ AI Integration - First-class AI/LLM integration out of the box
- β‘ Async/Fibers - Native PHP 8.1+ Fiber concurrency without extensions
- π WebSockets - Real-time event-driven WebSocket ecosystem
- ποΈ Modular Architecture - Package-based modular system
- π¨ Elegant Syntax - Clean, expressive code following modern PHP standards
- π‘οΈ Security - Built-in security features including LibxaSecure
- π¦ Package System - First-party and third-party package support
- ποΈ ORM & Query Builder - Powerful Atlas ORM with AI-powered queries
- π Multi-Tenancy - Zero-config multi-tenancy support
- π File Storage - Consistent filesystem API
- π§΅ Queue System - Asynchronous job processing
- π HTTP Client - Connection pooling for parallel requests
- π Helpers - Extensive utility functions
Installation
composer create-project libxa/framework your-app
cd your-app
php libxa serve
Quick Start
1. Configuration
Set your environment variables in .env:
APP_NAME=YourApp APP_ENV=local APP_DEBUG=true APP_URL=http://localhost:8000 DB_CONNECTION=sqlite DB_DATABASE=database.sqlite OPENAI_API_KEY=your-api-key
2. Database Setup
php libxa migrate
3. Run Development Server
php libxa serve
Visit http://localhost:8000 to see your application.
Framework Lifecycle
The LibxaFrame request lifecycle follows this flow:
1. HTTP Request β Public/index.php
2. HttpKernel receives request
3. Middleware Pipeline executes
4. Router matches route
5. Controller/Closure executes
6. Response generated
7. Middleware Pipeline processes response
8. Response sent to client
Detailed Lifecycle
- Entry Point -
public/index.phpboots the application - Application Boot - Service providers are registered and booted
- HTTP Kernel - Handles the request through middleware
- Middleware Pipeline - Global and route-specific middleware execute
- Router Dispatch - Matches URL to controller action
- Controller Execution - Business logic runs
- Response Generation - Response object created
- Termination - Middleware cleanup and final actions
Core Features
AI Integration
LibxaFrame provides first-class AI integration with OpenAI and compatible services:
// Generate text $response = AI::text("Write a short poem about coding."); // Classification $category = AI::classify("I love this framework!", ['positive', 'negative', 'neutral']); // Embeddings $vector = AI::embed("LibxaFrame is awesome."); // Data extraction $data = AI::extract("John Doe is a software engineer.", [ 'name' => 'string', 'occupation' => 'string' ]); // Natural language database queries $result = DB::ask("total revenue from users in Paris last month");
Configuration:
OPENAI_API_KEY=your-api-key AI_BASE_URL=https://api.openai.com/v1 ATLAS_AI_ENABLED=true ATLAS_AI_PROVIDER=openai ATLAS_AI_MODEL=gpt-4o-mini
Async & Fibers
True asynchronous behavior using native PHP 8.1+ Fibers:
use Libxa\Async\Parallel; // Run tasks concurrently [$users, $orders, $ads] = Parallel::run([ 'users' => fn() => User::all(), 'orders' => fn() => Order::recent(), 'ads' => fn() => Http::get('https://api.ads.com/serve'), ]); // HTTP connection pooling use Libxa\Http\Client; $responses = Client::pool([ fn() => (new Client())->get('https://api1.com/data'), fn() => (new Client())->get('https://api2.com/data'), fn() => (new Client())->get('https://api3.com/data'), ]); // Fiber queue workers Queue::fiber() ->batch($massiveArrayOfJobs) ->maxConcurrency(10) ->dispatch();
WebSockets
Event-driven WebSocket ecosystem built on Workerman:
php libxa ws:serve
WebSocket Controller:
use Libxa\WebSockets\WebSocketController; class ChatController extends WebSocketController { public function onMessage($connection, $data) { $this->broadcast($data); } }
Modular Package System
First-class package support with automatic discovery:
php libxa package:discover
php libxa vendor:publish --provider="Vendor\Package\ServiceProvider"
Package Structure:
packages/
βββ my-package/
βββ src/
β βββ MyServiceProvider.php
β βββ Routes/
β βββ Views/
β βββ Database/Migrations/
βββ composer.json
Atlas ORM & Query Builder
Powerful database abstraction with AI capabilities:
// Query builder $users = DB::table('users') ->where('active', true) ->orderBy('created_at', 'desc') ->get(); // Model $user = User::find(1); $user->name = 'John'; $user->save(); // AI-powered queries $result = DB::ask("find all users who signed up last week");
Multi-Tenancy
Zero-config multi-tenancy support:
TENANCY_ENABLED=true TENANCY_DRIVER=subdomain
// Automatic tenant resolution based on subdomain tenant()->id; // Current tenant ID tenant()->connection; // Tenant-specific database connection
File Storage
Consistent filesystem API:
// Store file Storage::put('avatars/user1.jpg', $fileContents); // Retrieve file $contents = Storage::get('avatars/user1.jpg'); // Check existence if (Storage::exists('avatars/user1.jpg')) { // ... } // Delete file Storage::delete('avatars/user1.jpg'); // File URLs $url = Storage::url('avatars/user1.jpg');
Helpers & Utilities
Extensive helper functions:
// String helpers str('Hello World')->slug(); // hello-world str()->limit('Long text...', 10); // Long te... // Array helpers collect([1, 2, 3])->avg(); // 2 collect([1, 2, 3])->sum(); // 6 // Path helpers app_path(); storage_path(); public_path(); // Time helpers now()->format('Y-m-d'); now()->addDays(7);
Queue System
Asynchronous job processing:
// Create a job class SendEmail implements ShouldQueue { public function handle() { Mail::to($this->user)->send(new WelcomeEmail()); } } // Dispatch job SendEmail::dispatch($user); // Process queue php libxa queue:work
HTTP Client
Powerful HTTP client with connection pooling:
use Libxa\Http\Client; $client = new Client(); $response = $client->get('https://api.example.com/data'); $data = $response->json(); // POST request $response = $client->post('https://api.example.com/users', [ 'name' => 'John Doe', 'email' => 'john@example.com' ]);
Routing
Basic Routes
$router->get('/', function () { return view('welcome'); }); $router->get('/users/{id}', function ($id) { return "User {$id}"; });
Controller Routes
$router->get('/users', [UserController::class, 'index']); $router->post('/users', [UserController::class, 'store']);
Route Groups
$router->group(['prefix' => 'admin', 'middleware' => 'auth'], function ($router) { $router->get('/dashboard', [AdminController::class, 'dashboard']); $router->get('/users', [AdminController::class, 'users']); });
Resource Routes
$router->resource('posts', PostController::class);
Controllers
<?php namespace App\Http\Controllers; use Libxa\Http\Request; use Libxa\Http\Response; class UserController extends Controller { public function index(): Response { $users = User::all(); return view('users.index', compact('users')); } public function store(Request $request): Response { $validated = $request->validate([ 'name' => 'required|string', 'email' => 'required|email|unique:users' ]); User::create($validated); return redirect('/users'); } }
Middleware
Creating Middleware
<?php namespace App\Http\Middleware; use Libxa\Http\Request; use Libxa\Http\Response; class LogRequest { public function handle(Request $request, callable $next): Response { Log::info("Request: {$request->url()}"); return $next($request); } }
Registering Middleware
// In routes $router->group(['middleware' => 'auth'], function ($router) { // ... }); // Or in HttpKernel protected array $middleware = [ LogRequest::class, ];
Views & Blade
LibxaFrame uses the Blade templating engine:
// Return view return view('welcome', ['name' => 'John']); // In view <h1>Hello, {{ $name }}</h1> @if($user) <p>Welcome back!</p> @else <p>Please login</p> @endif
Layouts
// layouts/app.blade.php <!DOCTYPE html> <html> <head> <title>@yield('title')</title> </head> <body> @yield('content') </body> </html> // page.blade.php @extends('layouts.app') @section('title', 'My Page') @section('content') <h1>My Content</h1> @endsection
Database
Migrations
php libxa make:migration create_users_table
Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->timestamps(); });
Models
php libxa make:model User
<?php namespace App\Models; use Libxa\Atlas\Model; class User extends Model { protected $fillable = ['name', 'email', 'password']; protected $hidden = ['password']; public function posts() { return $this->hasMany(Post::class); } }
Console Commands
Creating Commands
php libxa make:command SendEmails
<?php namespace App\Console\Commands; use Libxa\Console\Command; class SendEmails extends Command { protected static $defaultName = 'emails:send'; protected function configure(): void { $this->setDescription('Send pending emails'); } protected function execute(): int { // Your logic return Command::SUCCESS; } }
Security
LibxaSecure
Built-in security features:
// Encryption $encrypted = encrypt($data); $decrypted = decrypt($encrypted); // Hashing $hashed = Hash::make('password'); if (Hash::check('password', $hashed)) { // Valid } // CSRF protection (automatic)
Authentication
// Login Auth::attempt(['email' => $email, 'password' => $password]); // Get current user $user = Auth::user(); // Logout Auth::logout();
Deployment
Production Build
# Install dependencies composer install --no-dev --optimize-autoloader # Set environment cp .env.example .env php libxa key:generate # Run migrations php libxa migrate # Build assets npm install npm run build
Server Configuration
Nginx:
server { listen 80; server_name yourdomain.com; root /var/www/your-app/public; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } }
Background Processes
Supervisor for Queues:
[program:libxa-queue] process_name=%(program_name)s_%(process_num)02d command=php /var/www/your-app/libxa queue:work autostart=true autorestart=true user=www-data
Package Development
Creating a Package
php libxa make:package MyPackage
This creates:
packages/my-package/
βββ src/
β βββ MyPackageServiceProvider.php
β βββ Routes/
β βββ Views/
β βββ Database/Migrations/
βββ composer.json
Package Discovery
// In MyPackageServiceProvider class MyPackageServiceProvider extends ModuleServiceProvider { public function boot(): void { $this->loadRoutesFrom(__DIR__ . '/Routes/web.php'); $this->loadViewsFrom(__DIR__ . '/Resources/views', 'mypackage'); $this->loadMigrationsFrom(__DIR__ . '/Database/Migrations'); } }
Testing
php libxa test
<?php namespace Tests\Unit; use Tests\TestCase; class ExampleTest extends TestCase { public function test_basic_assertion(): void { $this->assertTrue(true); } }
Performance Optimization
Caching
// Cache value Cache::put('key', 'value', 3600); // Get cached value $value = Cache::get('key'); // Remember pattern $value = Cache::remember('key', 3600, function () { return DB::table('users')->get(); });
Query Optimization
// Eager loading $users = User::with('posts')->get(); // Select specific columns $users = User::select('id', 'name')->get(); // Chunking User::chunk(100, function ($users) { foreach ($users as $user) { // Process } });
Configuration
Environment Variables
APP_NAME=LibxaFrame APP_ENV=production APP_DEBUG=false APP_URL=https://yourdomain.com DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database DB_USERNAME=your_username DB_PASSWORD=your_password CACHE_DRIVER=file SESSION_DRIVER=file OPENAI_API_KEY=your-api-key
Configuration Files
Configuration files are located in src/config/:
// config/app.php return [ 'name' => env('APP_NAME', 'LibxaFrame'), 'env' => env('APP_ENV', 'production'), 'debug' => env('APP_DEBUG', false), 'url' => env('APP_URL', 'http://localhost'), ];
Contributing
Contributions are welcome. Read CONTRIBUTING.md first, and
see How this project works below for the branch and
release model. Branch from develop, never from main.
License
LibxaFrame is open-sourced software licensed under the MIT license.
Support
- Issues: GitHub Issues
- Questions: GitHub Discussions
- Security: SECURITY.md - never a public issue
Acknowledgments
Built with β€οΈ using PHP 8.3+
LibxaFrame - The Modern PHP Framework
---|---|
| CONTRIBUTING.md | Local setup, branch rules, commit convention, test policy |
| docs/BRANCHING.md | The branch model β main is what Packagist publishes |
| docs/RELEASING.md | Release and hotfix runbook |
| docs/REPOSITORY_SETUP.md | One-time GitHub settings: branch protection, tag rules, Packagist webhook |
| SECURITY.md | Private vulnerability disclosure and supported versions |
| CHANGELOG.md | What changed in each release |
| CHANGES.md | Engineering write-up of the July and August 2026 stability audits |
Contributions branch from develop, never from main.
How this project works
Everything below is the operating model: how the two repositories relate, how a change travels from a developer's machine to Packagist, and what is enforced automatically along the way. The reference documents (BRANCHING, RELEASING, REPOSITORY_SETUP) go deeper; this section is the complete picture in one place.
The two repositories
| Repository | Packagist | Type | What it is |
|---|---|---|---|
| libxa-framework/libxa | libxa/framework |
library | This repo. The framework itself. Installed into vendor/. |
| libxa-framework/LibxaStack | libxa/libxa |
project | The starter kit. What composer create-project produces. |
They version independently. A framework release does not require a starter kit release β but a framework minor release usually gets one, so new projects receive the new version.
ββββββββββββββββββββββββββββ
β libxa/framework β the library
β (this repository) β
βββββββββββββ¬βββββββββββββββ
β composer require
βΌ
ββββββββββββββββββββββββββββ
β libxa/libxa β the skeleton
β (LibxaStack) β
βββββββββββββ¬βββββββββββββββ
β composer create-project
βΌ
a user's new app
Branch model
main is what Packagist publishes. Nothing reaches main except through a
reviewed release/* or hotfix/* pull request.
feature/auth-guards ββ
fix/router-405 βββββββΌβββΆ develop βββΆ release/v0.9.0 βββΆ main ββ(tag v0.9.0)βββΆ Packagist
docs/contributing ββββ β² β
β β
βββββββ back-merge βββββββββββ€
β
hotfix/v0.8.1 βββββββββββββββββββββΆββ
| Branch | From | Into | Lifetime | Protected |
|---|---|---|---|---|
main |
β | β | permanent | β |
develop |
main |
β | permanent | β |
feature/* fix/* docs/* test/* refactor/* chore/* perf/* |
develop |
develop |
hoursβdays | β |
release/vX.Y.Z |
develop |
main and develop |
days | β |
hotfix/vX.Y.Z |
main |
main and develop |
hours | β |
develop is the default branch on GitHub, so new pull requests target it
automatically.
Day-to-day development
# 1. Start from develop, always git checkout develop git pull origin develop git checkout -b fix/csrf-array-token # 2. Work. Every bug fix needs a test that fails without the fix. composer test -- --filter CsrfMiddlewareTest # 3. Everything CI will run, locally composer check # lint + full suite + security audit # 4. Rebase and open a PR into develop git fetch origin git rebase origin/develop git push -u origin fix/csrf-array-token gh pr create --base develop
Commit messages follow Conventional Commits:
fix(router): match optional parameters without a trailing slash
feat(validation): add numeric-aware min/max sizing
feat(request)!: normalise header keys # ! marks a breaking change
Working on the framework and an app together
Most framework changes are best validated against a real application. Clone both repositories as siblings:
your-workspace/
βββ libxaframe/ # this repo: git clone .../libxa.git libxaframe
βββ LibxaStack/ # git clone .../LibxaStack.git
composer install inside LibxaStack then junctions
vendor/libxa/framework onto ../libxaframe. The vendor directory is your
framework working copy, so:
- framework edits take effect on the next request β no
composer update, nodump-autoload, not even for brand-new classes (PSR-4 resolves them live); - the two can never drift apart. They previously did, in both directions, and framework fixes silently had no effect on the app.
(cd libxaframe && composer check) (cd LibxaStack && composer check)
When no sibling checkout exists β CI, a normal install β the path repository's
glob matches nothing and libxa/framework resolves from Packagist instead.
What CI enforces
Every pull request into develop or main must pass:
| Job | Checks |
|---|---|
Tests Β· PHP 8.3 / 8.4 |
Full suite on both supported versions |
Tests (--prefer-lowest) |
The lower bound of every constraint actually works |
PSR-4 contract |
One class per file, no duplicate declarations |
Security audit |
No known vulnerabilities in dependencies |
Distribution archive |
src/ ships; tests/, docs/, tools/, .github/ do not |
The PSR-4 job is not style enforcement. A class the autoloader cannot find is a
fatal Class not found at runtime, and a class declared in two files is a
fatal Cannot redeclare class the moment both load. Both shipped in this
framework, which is why it is a build gate.
Releasing
Tags are created by a human; CI verifies rather than creates them. Full runbook: docs/RELEASING.md.
# 1. Cut the release branch β develop is now open for the version after next git checkout develop && git pull git checkout -b release/v0.9.0 # 2. Move [Unreleased] in CHANGELOG.md into a dated ## [0.9.0] section # 3. Verify composer check # 4. PR into main, get it reviewed, merge # 5. Tag on main β annotated, never lightweight git checkout main && git pull git tag -a v0.9.0 -m "Release v0.9.0" git push origin v0.9.0 # 6. Back-merge, or the next release silently reverts this one git checkout develop git merge --no-ff origin/main git push origin develop
release.yml refuses to publish a tag that is not annotated, not SemVer, not
an ancestor of main, or not documented in CHANGELOG.md β and re-runs the
full suite at the tagged commit before creating the GitHub Release. Packagist
publishes independently via its push webhook.
Version numbers
SemVer, prefixed v. Below 1.0, Composer treats the
minor number as the compatibility boundary β ^0.8.0 allows 0.8.9 but not
0.9.0:
| Change | Pre-1.0 | Post-1.0 |
|---|---|---|
| Breaking API change | 0.8.3 β 0.9.0 |
1.4.2 β 2.0.0 |
| New backward-compatible feature | 0.8.3 β 0.8.4 |
1.4.2 β 1.5.0 |
| Bug fix | 0.8.3 β 0.8.4 |
1.4.2 β 1.4.3 |
Because a pre-1.0 feature and a pre-1.0 fix share the same slot, anything that breaks a documented API needs a minor bump even when the diff looks small.
Hotfixes
The only branch that starts from main:
git checkout main && git pull git checkout -b hotfix/v0.8.1 # fix it, and add a regression test that fails without the fix # update CHANGELOG.md gh pr create --base main # merge β tag on main β back-merge into develop
A hotfix without a regression test is not finished β the reason it is urgent is that nothing caught it.
Reference
| Document | Covers |
|---|---|
| CONTRIBUTING.md | Setup, branch rules, commit convention, test policy |
| docs/BRANCHING.md | The branch model in full, plus branch-protection settings |
| docs/RELEASING.md | Release and hotfix runbook, and the two-repo release order |
| docs/REPOSITORY_SETUP.md | One-time GitHub setup: protection rules, tag rules, Packagist webhook |
| SECURITY.md | Private disclosure, supported versions, security-relevant defaults |
| CHANGELOG.md | What changed in each release |
| CHANGES.md | Engineering write-up of the stability audits |