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
.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();
}
| Method | Description |
|---|---|
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
| Command | Description |
|---|---|
make:module Name | Scaffold controllers, model and a routes file |
make:migration name | Create an empty migration file |
migrate | Run pending migrations |
migrate:rollback | Roll back the last batch |
serve [host:port] | Start the dev server (default 127.0.0.1:8080) |
Configuration (.env)
| Key | Description |
|---|---|
APP_DEBUG | Show detailed errors when true |
APP_URL | Base URL of the app |
AUTO_ROUTING | Resolve /controller/method automatically |
DB_CONNECTION | sqlite (default) or mysql |
DB_DATABASE | SQLite file path |
DB_HOST / DB_NAME / DB_USER / DB_PASSWORD | MySQL credentials |
LOG_FILE | Log file name inside storage/ |
AUTH_TOKEN | Bearer token expected by the Auth middleware |