A lightweight, transparent PHP framework built on the principles of simplicity and clarity.
~15 MB Β· Lightweight Query Builder Β· No hidden dependencies Β· No magic Β· PHP 8.3 Attributes Β· Porto Architecture
Rudra follows the KISS principle β every tool is visible, every dependency is explicit, and every layer does exactly one thing. No "magic" happens behind the scenes: if you see it in the code, that's all there is.
- Transparency β no hidden behavior, no implicit side effects
- Simplicity β minimal surface area, easy to learn and extend
- Explicit over implicit β dependencies are declared, not guessed
- Modern PHP β built from the ground up on PHP 8.3+ features (attributes, typed constants, fibers)
Rudra is built for developers who:
- Prefer explicit code over framework magic β you want to see exactly what SQL is executed, what routes are registered, and what dependencies are injected
- Value cognitive clarity β you need a framework that stays out of your way and doesn't require memorizing hidden conventions
- Work on small-to-medium projects β you want structure and best practices without the overhead of enterprise-grade frameworks
- Want full control β you believe that understanding your tools is more important than having hundreds of features out of the box
If you're looking for a batteries-included framework with built-in ORM, pre-configured queue workers, and ready-made broadcast drivers β Rudra is not for you.
If you want a lightweight, transparent foundation with simple primitives that you can extend exactly as needed β you're in the right place.
- PHP 8.3 or higher
- PDO extension
- Composer (for installation)
composer create-project --prefer-dist rudra/framework newappcomposer create-project --prefer-dist --stability=dev rudra/framework newappgit clone git@github.com:Jagepard/Rudra-Framework.git
cd Rudra-Framework
git checkout v26.7.27
ddev start
ddev launchgit clone git@github.com:Jagepard/Rudra-Framework.git
cd Rudra-Framework
ddev start
ddev launchSecret keys are automatically generated during Composer installation for all environments (local, ddev, production).
If you need to regenerate them (e.g., after cloning the repository), simply run:
php rudra secretRudra uses environment-specific configuration files. The framework automatically loads the appropriate file based on your environment:
| Environment | Config File |
|---|---|
| DDEV | config/setting.ddev.yml |
| Local development | config/setting.local.yml |
| Production | config/setting.production.yml |
These files contain database credentials, container paths, debug settings, and other environment-specific values.
Rudra uses a simple, explicit YAML configuration for database connections. There is no hidden magic or auto-guessing: you define exactly which driver and credentials to use for each environment.
Local Development (config/setting.local.yml):
database:
dsn: mysql:dbname=db;host=127.0.0.1
# dsn: pgsql:host=127.0.0.1;port=5432;dbname=db;
# dsn: sqlite:/path-to/app/Ship/Data/rudra.sqlite
username: jagepard
password: passwordDDEV Environment (config/setting.ddev.yml):
database:
dsn: mysql:dbname=db;host=db;port=3306
# dsn: pgsql:host=db;port=5432;dbname=db;
# dsn: sqlite:/path-to/app/Ship/Data/rudra.sqlite
username: root
password: root
# For pgsql in DDEV:
# username: db
# password: dbProduction (config/setting.production.yml):
database:
dsn: mysql:dbname=db;host=127.0.0.1 # Replace with your production host
username: your_prod_user
password: your_prod_passwordπ‘ Tip: The framework's Query Builder (Rudra-Model) automatically reads these settings based on the active environment. Simply uncomment the dsn line for your preferred database engine (MySQL, PostgreSQL, or SQLite).
For web requests, Rudra automatically detects the environment in index.php:
$env = match (true) {
getenv('IS_DDEV_PROJECT') === 'true' => 'ddev',
php_sapi_name() === 'cli-server' => 'local',
default => 'production',
};For CLI commands (like php rudra migrate), the framework cannot auto-detect the environment as it does in index.php. Instead, it relies on the app_env.php file to determine which configuration file to load (e.g., 'local' β config/setting.local.yml).
Use the built-in interactive command to set or change your environment. It will automatically create or update the app_env.php file for you, preserving all comments and structure:
php rudra envFollow the interactive prompt to select 1 (local), 2 (ddev), or 3 (production).
If you prefer, you can manually create or edit the app_env.php file in your project root:
<?php
// Do NOT commit this file to Git (.gitignore)
return 'local'; // β Set to: 'local', 'ddev', or 'production'βΉοΈ Note: If
app_env.phpis missing (e.g., if you cloned the repository directly instead of usingcomposer create-project), the framework defaults to'local'for CLI commands. This is safe for local development but should not be relied upon in production β always create the file manually or set theAPP_ENVserver variable explicitly.
π‘ Tip: When adding new containers or settings during development, update
config/setting.local.yml. For production deployment, copy the relevant sections toconfig/setting.production.yml.
php rudra serveLet's create a simple /hello/:name page that greets the user. This example shows the full flow: container β controller β route β browser.
php rudra make:containerEnter container name: web
This creates the App\Containers\Web\ namespace and registers it in config/setting.local.yml.
π‘ Don't forget to also register it in config/setting.ddev.yml and config/setting.production.yml if needed.
php rudra make:controllerEnter controller name: hello
Enter container: web
This generates App/Containers/Web/Controller/HelloController.php.
Open the controller and add a method with the #[Routing] attribute:
<?php
namespace App\Containers\Web\Controller;
use App\Containers\Web\WebController;
use Rudra\Router\Attribute\Routing;
use Rudra\Container\Facades\Request;
class HelloController extends WebController
{
#[Routing(url: 'hello/:name', method: 'GET')]
public function greet(string $name): void
{
echo "<h1>Hello, {$name}!</h1>";
echo "<p>Your lucky number is: " . random_int(1, 100) . "</p>";
}
}That's it. The route is registered automatically via the attribute β no separate routes.php needed.
http://127.0.0.1:8000/hello/world
You should see: Hello, world! Your lucky number is: 42
If you prefer to keep routes separate from controllers, create (or edit) App/Containers/Web/routes.php:
<?php
use Rudra\Router\RouterFacade as Router;
use App\Containers\Web\Controller\HelloController;
Router::get('hello/:name', [HelloController::class, 'greet']);
β οΈ When usingroutes.phpfor manual routing, make sure the file returns an empty array to disable automatic attribute-based route registration:
return [];For quick prototyping, you can even skip the controller entirely:
Router::get('hello/:name', function (string $name) {
echo "Hello, {$name}!";
});Then open http://127.0.0.1:8000 in your browser.
Rudra is built on a modular component architecture. Each component is a standalone Composer package β use only what you need, or install everything via rudra/framework.
| Component | Description | Package |
|---|---|---|
| π¦ Container | DI container with 5 binding patterns (string, object, factory string, factory object, closure) | rudra/container |
| π§ Router | HTTP router with PHP 8 attributes, middleware pipeline, REST resources, method spoofing | rudra/router |
| π― Controller | MVC controller abstraction with lifecycle hooks (init, before, after) |
rudra/controller |
| πΎ Model | Lightweight Query Builder + Repository pattern (not a heavy ORM) | rudra/model |
| π Auth | Authentication, session management, RBAC, "Remember Me" | rudra/auth |
| π OAuthClient | OAuth 2.0 client (Yandex, VK, custom providers) | rudra/oauth-client |
| π§ EventDispatcher | Events system with Listeners (pub/sub) and Observers (stateless notifications) | rudra/event-dispatcher |
| β Validation | Fluent validation with CSRF, sanitization, custom rules, field aliases | rudra/validation |
| πΌοΈ View | Template engine (PHP + Twig support) with file-based caching | rudra/view |
| π‘οΈ Exception | HTTP error handling with custom error pages and DebugBar integration | rudra/exception |
| π Redirect | HTTP redirection with status codes (1xx-5xx) | rudra/redirect |
| π Pagination | SQL OFFSET/LIMIT calculator with page links generator | rudra/pagination |
| π Annotation | Metadata reader for PHP 8 attributes + legacy docblock annotations | rudra/annotation |
| π₯οΈ Cli | CLI component with command routing | rudra/cli |
| π Docs | Auto-generates Markdown API documentation from source code | rudra/docs |
use Rudra\Auth\AuthFacade as Auth;
// Registration
$user = [
"email" => "user@email.com",
"password" => Auth::bcrypt("password")
];
// Authentication (plain password vs hash from DB)
Auth::authentication($user, "password", ["admin/dashboard", "login"]);
// Authorization check (redirects to 'login' if not authenticated)
if (!Auth::authorization(null, "login")) {
exit;
}
// Role-Based Access Control
if (!Auth::roleBasedAccess($currentRole, "editor", "error/403")) {
exit;
}use Rudra\EventDispatcher\EventDispatcherFacade as Dispatcher;
// Listeners β pub/sub with payload
Dispatcher::addListener('user.registered', [UserListener::class, 'onRegister']);
Dispatcher::dispatch('user.registered', ['id' => 1, 'email' => '...']);
// Observers β stateless notifications (no payload)
Dispatcher::attachObserver('before', [AuditObserver::class, 'onEvent']);
Dispatcher::notify('before');use Rudra\Container\Facades\Request;
use Rudra\Container\Facades\Session;
use Rudra\Validation\ValidationFacade as V;
$fields = Request::post()->all();
$processed = [
'name' => V::sanitize($fields['name'])->required()->min(3)->max(50)->run(),
'email' => V::email($fields['email'])->run(),
'age' => V::sanitize($fields['age'])->integer()->between(18, 100)->run(),
'description' => V::set($fields['description'])->run(), // without validation
'csrf' => V::sanitize($fields['csrf'])->csrf(Session::get('csrf_token'))->run(),
];
if (V::approve($processed)) {
$data = V::getValidated($processed, ["csrf", "_method"]);
} else {
$errors = V::getErrors($processed);
}Registers standard CRUD routes with explicit plural and singular URL patterns. No magic pluralization β you define exactly what the URLs look like.
$router->resource('api/users', 'api/user', UserController::class);This creates the following routes:
| Method | URL | Controller Method | Description |
|---|---|---|---|
| GET | api/users |
index | List all users |
| GET | api/user/:id |
read | Get single user |
| POST | api/users |
create | Create new user |
| PUT | api/user/:id |
update | Full update of user |
| PATCH | api/user/:id |
update | Partial update of user |
| DELETE | api/user/:id |
delete | Delete user |
The default action names are
['index', 'read', 'create', 'update', 'delete'].
You can override the default action names by passing a custom array of 5 methods:
$router->resource('api/posts', 'api/post', PostController::class, [
'actionIndex', // GET api/posts β list all posts
'actionView', // GET api/post/:id β get single post
'actionAdd', // POST api/posts β create new post
'actionUpdate', // PUT/PATCH api/post/:id β update post
'actionDrop' // DELETE api/post/:id β delete post
]);The array order is fixed:
[index, read, create, update, delete].
use Rudra\View\ViewFacade as View;
// Cache-first strategy with fallback
echo View::cache(['mainpage', "+1 day"]) ?? View::render(["layout", "mainpage"], [
'content' => View::cache(["page_{$slug}", "+1 day"])
?? View::view(["page", "page_{$slug}"], ['foo' => 'bar']),
]);
// Or with helpers
data(['content' => cache(['mainpage']) ?? view(['index', 'mainpage'])]);
render('layout', data());Rudra includes a lightweight, transparent data access layer β not a heavy ORM, but a simple Query Builder with Repository pattern.
A predictable delegation chain with automatic fallback:
- Entity β entry point, defines the table name
- Model β business logic layer (optional)
- Repository β data access layer (optional, falls back to base CRUD)
If you don't create a Model or Repository, the base Repository handles standard CRUD automatically.
namespace App\Containers\User\Entity;
use Rudra\Model\Entity;
class User extends Entity
{
public static ?string $table = 'users';
}
// Usage β no Model or Repository needed:
// These methods are provided by the base Repository automatically
$users = User::getAll();
$user = User::find(1);
User::create(['name' => 'John', 'email' => 'john@example.com']);namespace App\Containers\User\Repository;
use Rudra\Model\Repository;
class UserRepository extends Repository
{
public function findActiveUsers(): array
{
return $this->qBuilder("SELECT * FROM {$this->table} WHERE active = 1");
}
}
// Automatically routed:
$activeUsers = User::findActiveUsers();use Rudra\Model\QBFacade as QB;
$query = QB::select('id, name, email')
->from('users')
->where('status = :status')
->and('role = :role')
->orderBy('created_at DESC')
->limit(10)
->get(); // Returns SQL query string
// Execute via Repository:
$results = User::qBuilder($query, ['status' => 'active', 'role' => 'admin']);use Rudra\Model\Schema;
Schema::create('users', function ($table) {
$table->integer('id', autoincrement: true)
->string('name')
->string('email')
->text('bio', 'NULL')
->createdAt()
->updatedAt()
->primaryKey('id'); // Explicitly declare primary key
})->execute();Simple, reliable JSON file caching β no Redis/Memcached required:
// Cache the result of User::getAll() method
$users = User::cache(['getAll']);
// Cache Post::findBy('category', 'news') with 1 hour TTL
$posts = Post::cache(['findBy', ['category', 'news']], '+1 hour');
// Auto-clears on create/update/delete- No magic β no hidden queries, no lazy loading surprises
- No hidden dependencies β direct PDO access when needed
- Predictable β you see exactly what SQL is executed
- Fast β no reflection overhead, no entity hydration complexity
- Simple β if you know SQL, you know Rudra-Model
Rudra ships with a rich set of CLI utilities following the Porto architecture. Run php rudra help to see the full list.
| Command | Description |
|---|---|
php rudra serve |
Start the built-in dev server (alias: run) |
php rudra help |
List all available commands |
| Command | Description |
|---|---|
php rudra make:container |
π¦ Scaffold a new Porto container |
php rudra make:controller |
π§ Generate a controller class |
php rudra make:contract |
π Generate an interface (contract) |
php rudra make:factory |
π Generate a factory class |
php rudra make:listener |
π Generate an event listener |
php rudra make:middleware |
π‘οΈ Generate a middleware class |
php rudra make:migration |
ποΈ Generate a database migration |
php rudra make:model |
π§± Generate Entity + Repository pair |
php rudra make:observer |
ποΈ Generate an event observer |
php rudra make:seed |
π± Generate a database seeder |
php rudra make:docs |
π Generate documentation from source |
| Command | Description |
|---|---|
php rudra migrate |
π€οΈ Run pending migrations (with duplicate protection) |
php rudra seed |
πΎ Execute seeders (with duplicate protection) |
| Command | Description |
|---|---|
php rudra debug:router |
πΊοΈ List all registered routes |
php rudra debug:router:container |
π List routes for a specific container |
php rudra debug:listeners |
π§ List registered event listeners |
php rudra debug:observers |
π List registered event observers |
| Command | Description |
|---|---|
php rudra bcrypt |
π Hash a password with bcrypt |
php rudra secret |
π Generate a cryptographic secret (APP_KEY) |
| Command | Description |
|---|---|
php rudra cache:clear |
π§Ή Clear application cache |
php rudra convert:array-to-yml |
π Convert a PHP array config to YAML (alias: to:yml) |
This project is licensed under the Mozilla Public License 2.0 (MPL-2.0) β a free, open-source license that:
- β Requires preservation of copyright and license notices
- β Allows commercial and non-commercial use
- β Requires that modifications to original files remain open under MPL-2.0
- β Permits combining with proprietary code in larger works
π Full license text: LICENSE
π Official MPL-2.0 page: https://mozilla.org/MPL/2.0/