Search by

sailantis / azera-framework

lumielis

A lightweight, fast PHP framework for building modern web and cli applications. Azera combines the best ideas from frameworks like Phalcon, CodeIgniter, Laravel and Twig into a minimal yet powerful toolkit.

Package info

github.com/sailantis/azera-framework

pkg:composer/sailantis/azera-framework

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.1.0 2026-09-28 22:58 UTC

This package is auto-updated.

Last update: 2026-10-10 16:45:10 UTC


README

Azera Logo

Lightweight by Design. Powerful in Practice.

A lightweight, fast PHP framework for building modern Web applications and CLI tools. Azera combines the best ideas from frameworks like Phalcon, CodeIgniter, Laravel and Symfony into a minimal yet powerful toolkit.

Why Azera?

Lightweight & Fast - Minimal dependencies and overhead. No bloat, just what you need.

Modern PHP - Built for PHP 8.2+, embracing type hints, named arguments, and modern patterns.

Unified Query Builder - One consistent, fluent API for all database operations, whether you're using models or raw queries.

Flexible Architecture - Use as much or as little as you need. Mix and match components freely.

Secure by Default - SQL injection protection via prepared statements, session-safe cookie encryption, and hardened defaults throughout.

Developer Friendly - Intuitive APIs, clear error messages, and comprehensive documentation.

Features

MVC Stack

  • Router - Fast pattern matching with named routes, parameter validation, and middleware support
  • Controllers - Clean action-based controllers with dependency injection
  • Dispatcher - Flexible request dispatching with middleware pipeline
  • ViewEngine - Clarity template engine with auto-escaping, template inheritance, and a filter pipeline. Other engines can be used as well, including Twig, Plates, and plain PHP templates.

Database & ORM

  • Query Builder - Unified fluent interface for SELECT, INSERT, UPDATE, DELETE
  • Active Record Models - Expressive model API with state tracking and typed property support
  • Prepared Statements - SQL injection protection by default
  • Read/Write Splitting - Built-in support for master/replica database setups
  • Connection Pooling - Automatic reconnection and connection management
  • Schema Introspection - Introspect database schema for dynamic models and migrations

HTTP Utilities

  • Request - Normalized access to GET, POST, headers, and file uploads
  • Response - Fluent response building with JSON, redirects, and status codes
  • Session - Simple session management with pluggable storage handlers
  • Cookies - Easy cookie handling with encryption support
  • Middleware - Composable request/response filters

CLI Tools

  • Console - Powerful CLI dispatcher with:
    • Auto-discovery of tasks from namespaces
    • Flexible task grouping and custom namespaces
    • Rich color output and styled help pages
    • Option parsing and argument separation
    • Built-in help and task listing
  • ModelSync Task (model-sync) - Built-in CLI task for synchronizing PHP models with the database schema, optionally applying changes and scaffolding new models.

Additional Features

  • Validation - Fluent field rules with type coercion, nested list/object validation, and error collection
  • Security - CSRF middleware, rate limiting, password hashing, encryption (Sodium/OpenSSL)
  • Logging - PSR-3 logging, typed PSR-14 database events
  • Pagination, AOP and queues - Built-in pagination, attribute-driven interceptors, task queues

Benchmarks

Azera is measured against Laravel, Symfony, Spiral, CodeIgniter 4 and CakePHP 5 on an identical full-stack workload — routing → controller → ORM query (SQLite) → template render → response — running on a real server. Two summaries are published beside the docs, one per deployment model:

As nginx + PHP-FPM — the framework boots again for every request, as PHP usually runs in production:

Total response times, PHP-FPM

As a resident worker (RoadRunner) — the framework boots once and serves every request:

Total response times, RoadRunner

Each chart sums one pass over all 21 benchmarked endpoints. The full per-endpoint tables, the per-feature races and the memory charts are in the PHP-FPM and RoadRunner summaries, and the complete report covers all six frameworks.

Requirements

  • PHP >= 8.2
  • PDO extension (ext-pdo)
  • Multibyte String extension (ext-mbstring)
  • Optional: Sodium or OpenSSL extension for advanced encryption features, MongoDB extension for MongoDB ORM support

(PDO driver support is implemented for MySQL, PostgreSQL and SQLite. Other drivers may work but are not officially tested.)

Installation

Install via Composer:

composer require sailantis/azera-framework

Quick Start

Web Application (MVC)

<?php
require_once __DIR__ . '/vendor/autoload.php';

use Azera\AppContext;
use Azera\Db\Database;
use Azera\Http\Response;
use Azera\Core\Dispatcher;
use Azera\Core\Router;

// AppContext holds shared services: database connections, the request,
// the view engine — accessed by models, queries, and controllers alike.
$ctx = AppContext::instance();

// Lazy database registration: the connection is created on first use.
$ctx->dbManager()->set('default',
    fn() => new Database('mysql:host=localhost;dbname=myapp', 'user', 'pass')
);

// Routing: named parameters become action arguments.
$router = $ctx->router();
$router->add('GET', '/hello/{name}', 'IndexController::helloAction');

// Match and dispatch
$path = $ctx->request()->getPath();
$method = $ctx->request()->getMethod();
$route = $router->match($path, $method);

if ($route === null) {
    Response::status(404)->send();
    exit;
}

$dispatcher = new Dispatcher();
$dispatcher->dispatch($route)->send();

Controller example:

<?php
namespace App\Controllers;

use Azera\Core\Controller;

class IndexController extends Controller
{
    public function helloAction(string $name): string
    {
        return "Hello, {$name}!";
    }
}

Working with Models

Active Record style models over Azera\Orm\Model:

class User extends \Azera\Orm\Model
{
    public int $id;
    public string $username;
    public string $email;
    public string $status;
}

// Find by primary key
$user = User::find(1);

// Update and save
$user->email = 'john@example.com';   // diffed — the UPDATE sends only the change
$user->save();

// Create new record
$newUser = User::create([
    'username' => 'jane',
    'email' => 'jane@example.com',
]);

$newUser->delete();

$user->count(['status' => 'active']);
$user->exists(['email' => 'john@example.com']);

// Query with conditions
$users = User::query()
    ->where('status', 'active')
    ->orderBy('created_at DESC')
    ->limit(10)
    ->select();

Validating Input

Fields are required by default; call ->optional() or ->default() where needed.

use Azera\Validation\Validator;

$v = new Validator($ctx->request()->post());

$v->field('name')->required()->string()->min(2)->max(100);
$v->field('email')->required()->email()->max(255);
$v->field('age')->optional()->int()->min(18);
$v->field('role')->default('viewer');   // optional with a default value

if ($v->fails()) {
    return Response::json(['errors' => $v->errors()], 422);
}

$data = $v->validated(); // only validated, coerced fields
User::create($data);

Paginating Results

Paginator wraps any Query, handles LIMIT/OFFSET, and runs an automatic total-count query.

$paginator = User::query()
    ->where('status', 'active')
    ->orderBy('name')
    ->paginate(page: (int)($_GET['page'] ?? 1), pageSize: 20);

$users = $paginator->entities();  // identity-mapped User instances

$totalItems  = $paginator->totalItems();
$lastPage    = $paginator->lastPage();
$currentPage = $paginator->currentPage();

Advanced Query Builder

Build complex queries with joins, subqueries, and aggregations.

Using Models and Sql Functions

use Azera\Db\Sql;

// Subquery: select the latest order date for each user
$latestOrder = Sql::subQuery(
    Order::query('o2')
        ->where('o2.user_id = u.id')
        ->orderBy('o2.created_at DESC')
        ->limit(1)
        ->select('o2.created_at')
)->as('latest_order');

$results = Order::query('o')
    ->join(User::class, 'u', 'o.user_id = u.id')
    ->where('o.status', 'completed')
    ->where('o.total >', 100)
    ->groupBy('u.id')
    ->having('COUNT(*) >', 5)
    ->select([
        'u.username',
        Sql::raw('COUNT(*)')->as('order_count'),
        Sql::func('SUM', 'o.total')->as('total_spent'),
        $latestOrder,
    ]);

Subqueries as Sources

A Query instance can be passed directly to ->from() or to any join method. The subquery is wrapped in parentheses automatically and its bind parameters are propagated to the outer query — no manual merging required.

use Azera\Db\Query;

// Build the subquery independently
$completedOrders = Order::query()
    ->where('status', 'completed')
    ->where('created_at > :since', ['since' => '2025-01-01'])
    ->groupBy('user_id')
    ->columns(['user_id', 'SUM(total) AS total_spent']);

// Use it as a derived table with ->from()
$topBuyers = Query::raw()
    ->from($completedOrders, 'co')
    ->where('co.total_spent >', 500)
    ->orderBy('co.total_spent DESC')
    ->select();

// Or join it alongside another table
$report = User::query()
    ->leftJoin($completedOrders, 'co', 'co.user_id = u.id')
    ->columns(['u.username', 'co.total_spent'])
    ->select();

Using the Query Builder Directly on Tables

For queries that don't belong to any model, start with Query::raw():

$results = Query::raw()
    ->table('orders o')
    ->join('users u', 'o.user_id = u.id')
    ->where('o.status', 'completed')
    ->where('o.total >', 100)
    ->groupBy('u.id')
    ->having('COUNT(*) >', 5)
    ->select([
        'u.username',
        'COUNT(*) as order_count',
        'SUM(o.total) as total_spent'
    ]);

CLI Tasks

Build command-line tools and scripts. Console auto-discovers tasks from PSR-4 namespaces, parses options, and renders color-highlighted help pages.

console.php — minimal entry point:

<?php
require_once __DIR__ . '/vendor/autoload.php';

use Azera\Cli\Console;

$console = new Console();
// App\Tasks is included automatically; add other namespaces if needed:
// $console->addNamespace('App\\Admin\\Tasks');
$console->process($argv[1] ?? null, $argv[2] ?? null, array_slice($argv, 3));

Create a task — extend Task, add *Action methods:

<?php
namespace App\Tasks;

use Azera\Cli\Task;

/**
 * Database maintenance utilities.
 *
 * Usage:
 *   php console.php database migrate [--direction=<up|down>]
 *
 * Examples:
 *   php console.php database migrate            # migrate up
 *   php console.php database migrate --direction=down
 */
class DatabaseTask extends Task
{
    public function migrateAction(string $target = 'latest'): void
    {
        $direction = $this->option('direction', 'up');
        $this->info("Migrating {$direction} to {$target}…");
        // migration logic here
        $this->success("Done.");
    }
}

Run tasks from the command line — options are separated from positional arguments automatically:

php console.php database migrate latest --direction=down
php console.php help               # overview with all tasks and actions
php console.php help database      # detail page for one task

Using Built-in Tasks

Azera includes a built-in model-sync task that synchronizes PHP models with the database schema:

# Preview differences between your models and the database
php console.php model-sync all

# Apply changes (writes to files)
php console.php model-sync all src/Models --apply --generate-accessors

# Scaffold a new model class
php console.php model-sync make Order

model-sync all and model-sync make auto-resolve the target directory from PSR-4 entries in composer.json. See CLI Tasks for all options.

Project Structure

Recommended directory layout for Azera applications:

your-project/
├── app/
│   ├── Controllers/     # MVC controllers
│   ├── Models/          # Database models
│   ├── Tasks/           # CLI tasks
│   ├── Middleware/      # Custom middleware
├── config/
│   └── database.php     # Configuration files
├── public/
│   ├── index.php        # Web entry point
│   ├── css/
│   └── js/
├── views/               # View templates
├── console.php          # CLI entry point
├── composer.json
└── .gitignore

Documentation

Start with Getting Started, or browse the complete guide index for MVC, data, HTTP, CLI, security, logging, events, caching, queues, AOP, and configuration. The API reference documents every public class.

Key Concepts

AppContext - Service Container

use Azera\AppContext;
use Azera\Db\Database;

$ctx = AppContext::instance();

// Register database connection(s)
$ctx->dbManager()->set('default',
    fn() => new Database('mysql:host=localhost;dbname=app', 'user', 'pass')
);

// Configure services
$ctx->view()->setViewPath(__DIR__ . '/views');   // ClarityEngine is the default

// Register application services as instances or lazy factories
$ctx->set(App\Services\StripeService::class, new App\Services\StripeService());
$ctx->set(App\Services\BillingService::class, fn() => new App\Services\BillingService());

// Access services anywhere
$stripe = $ctx->get(App\Services\StripeService::class);

Registered callables are zero-argument lazy factories: executed on first lookup, cached for the request lifecycle. Unregistered class names are auto-wired via reflection.

Middleware Pipeline

$dispatcher = new Dispatcher();
$dispatcher->addMiddleware(new AuthMiddleware());
$dispatcher->addMiddleware(new SessionMiddleware());
$response = $dispatcher->dispatch($route);

Read/Write Database Splitting

$mgr = AppContext::instance()->dbManager();
$mgr->set('write', new Database('mysql:host=master;dbname=app', 'user', 'pass'));
$mgr->set('read', new Database('mysql:host=replica;dbname=app', 'user', 'pass'));

// Models and queries automatically route reads to 'read' role, writes to
// 'write' role. Falls back to default if a specific role is missing

Development

Running Tests

Azera uses PHPUnit for testing:

# Run all tests
./vendor/bin/phpunit

# Run specific test file
./vendor/bin/phpunit tests/Db/QueryBuilderTest.php

# Run with coverage (requires Xdebug)
./vendor/bin/phpunit --coverage-html coverage/

Contributing

Contributions are welcome — feel free to submit pull requests or open issues. When contributing, fork the repository, create a feature branch, write tests for new functionality, and ensure the test suite passes before opening a pull request.

Examples

Complete working examples live in the examples/ directory:

Philosophy

  • Simplicity over magic - Explicit is better than implicit
  • Performance - Minimal overhead and memory footprint
  • Standards - PSR-compliant where applicable
  • Flexibility - Use what you need, ignore the rest
  • Security - Secure by default, not as an afterthought

Acknowledgments

Azera draws inspiration from:

  • Phalcon - Speed and C-based architecture concepts
  • CodeIgniter - Simplicity and developer-friendly APIs
  • Laravel - Elegant syntax and query builder design

About the Name

Azera is named after Andi Gutmans, Zeev Suraski, and Rasmus Lerdorf — the founders of PHP.

The little bird on the Azera logo is a Merlin falcon — a small, fast, and agile raptor, just like the framework itself: lightweight, focused, and built for speed. It is also a reference to Phalcon, which strongly influenced Azera’s design.

License

MIT License - see LICENSE file for details.

Built with ❤️ for developers who value security and performance