IRPHP Documentation

IRPHP is a lightweight PHP micro-framework: a small router, an Eloquent-backed database layer, a reflection-based DI container, and just enough structure to build real applications without the overhead of a full-stack framework.

Installation

git clone https://github.com/javadnasrollahi/irphp-framework.git
cd irphp-framework
composer install
cp .env.example .env
composer serve
The default .env uses SQLite (storage/database.sqlite), created automatically — no database server required to try the framework.

Project Structure

app/
  Controllers/       route handlers
  Core/               Router, Request, Response, Container, View, Validator, ExceptionHandler, Migrator
  Middleware/         Auth, Csrf, ...
  Models/             Eloquent models
  Services/           Database, Logger
  Modules/            optional self-contained modules (e.g. Telegram)
  Views/              PHP view templates
database/migrations/  schema migrations
routes/                *.php route files, auto-loaded
public/index.php       front controller
cli.php                 command-line tool

Routing

Routes are defined in any file under routes/ — they're all auto-loaded. The router supports GET, POST, PUT, PATCH, DELETE, route groups, and named routes.

$router->get('/', 'IndexController@index')->name('home');

$router->group(['prefix' => '/admin', 'middleware' => ['Auth']], function ($router) {
    $router->get('/dashboard', 'AdminController@dashboard')->name('admin.dashboard');
});

$router->put('/users/{id}', 'UserController@update');
$router->route('admin.dashboard'); // builds the URL

Set AUTO_ROUTING=true in .env to resolve /controller/method URLs automatically, without declaring routes.

Request & Response

Type-hint App\Core\Request in any controller method to receive the current request instead of touching superglobals directly.

use App\Core\Request;
use App\Core\Response;

public function update(Request $request, $id)
{
    $data = $request->only(['name', 'email']);

    return Response::make()
        ->status(200)
        ->json(['id' => $id, 'data' => $data])
        ->send();
}
MethodDescription
all() / input($key)Query + body (or JSON) params
only([...]) / except([...])Filtered subsets of input
header($key) / bearerToken()Read request headers
method() / path() / ip()Request metadata

Response is a fluent builder: status(), json(), view(), text(), download(), redirect(), header(), send().

Controllers

namespace App\Controllers;

use App\Core\Response;

class IndexController
{
    public function index()
    {
        return Response::make()->view('index', ['name' => 'Amir'])->send();
    }
}

Controllers are resolved through the DI container, so constructor dependencies are auto-wired.

Views

<?php \App\Core\View::extend('master'); ?>
<h1>Hello, <?= htmlspecialchars($name) ?> 👋</h1>

Views live in app/Views/, layouts in app/Views/layouts/. View::extend('master') wraps the view output in the given layout, available as $content.

DI Container

App\Core\Container auto-wires constructor dependencies via reflection. You rarely need to touch it directly — the router uses it to build controllers and middleware — but you can bind interfaces or singletons explicitly:

$container = App\Core\Container::getInstance();

$container->singleton(SomeService::class, fn() => new SomeService(config: '...'));

Middleware

A middleware is any class in App\Middleware with a handle() method. Attach it by name to a route or group:

$router->post('/webhook', 'WebhookController@handle', ['Auth']);

Built in: Auth (bearer token from AUTH_TOKEN) and Csrf (session-token check for non-GET requests).

Database & Models

Database access is powered by Eloquent (illuminate/database). Models extend App\Models\BaseModel, which defaults every field to guarded except id — declare $fillable explicitly in your model for mass assignment.

class Post extends BaseModel
{
    protected $table = 'posts';
    protected $fillable = ['title', 'body'];
}

Migrations

php cli.php make:migration create_posts_table
php cli.php migrate
php cli.php migrate:rollback
class CreatePostsTable
{
    public function up(): void
    {
        Capsule::schema()->create('posts', function ($table) {
            $table->increments('id');
            $table->string('title');
            $table->text('body');
        });
    }

    public function down(): void
    {
        Capsule::schema()->dropIfExists('posts');
    }
}

Validator

use App\Core\Validator;

$v = Validator::make($request->all(), [
    'email' => 'required|email',
    'name'  => 'required|min:3|max:50',
]);

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

Available rules: required, email, numeric, min:n, max:n, in:a,b,c.

CSRF Protection

Attach the Csrf middleware to any state-changing route, and include the token in your form:

<form method="POST">
    <?= csrf_field() ?>
    ...
</form>

Errors & Logging

App\Core\ExceptionHandler is registered in bootstrap.php and catches every uncaught exception and fatal error. With APP_DEBUG=true it renders a full trace (HTML or JSON depending on the request); in production it returns a generic 500.

use App\Services\Logger;

Logger::info('user.created', ['id' => $user->id]);
Logger::error('payment.failed', ['reason' => $e->getMessage()]);

Logs are written to storage/<LOG_FILE>.

CLI Commands

CommandDescription
make:module NameScaffold controllers, model and a routes file
make:migration nameCreate an empty migration file
migrateRun pending migrations
migrate:rollbackRoll back the last batch
serve [host:port]Start the dev server (default 127.0.0.1:8080)

Configuration (.env)

KeyDescription
APP_DEBUGShow detailed errors when true
APP_URLBase URL of the app
AUTO_ROUTINGResolve /controller/method automatically
DB_CONNECTIONsqlite (default) or mysql
DB_DATABASESQLite file path
DB_HOST / DB_NAME / DB_USER / DB_PASSWORDMySQL credentials
LOG_FILELog file name inside storage/
AUTH_TOKENBearer token expected by the Auth middleware