Skip to content

routsy/framework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Routsy Framework

Advanced PHP framework with zero external dependencies.

Inspired by the Lapa Framework, Routsy elevates the concept of a minimalist framework with native integration of Artificial Intelligence, robust security, multi-driver cache, queue system, migrations, template engine, intelligent error detection, multi-channel log system, extensible plugins, OAuth2 authentication (Google, Microsoft, GitHub), S3-compatible storage (AWS, MinIO, R2), and an admin dashboard — all without relying on external dependencies.

GitHub Organization: https://github.com/routsy


Table of Contents


Why Routsy?

Feature Lapa Routsy
Router with parameters Yes Yes + named routes + REST resource
Subdomains Yes (vhost) Yes (multi-host with pattern)
Database Medoo (wrapper) Native fluent Query Builder + Schema + Migrations
Security Basic CORS CSRF, JWT, AES-256-GCM, Rate Limiting, Sanitizer
Artificial Intelligence No OpenAI / compatible, Chat, Vision, Embeddings
Cache Simple file Multi-driver (File, Redis, Memory) with tags
Validation Basic 30+ rules, customizable messages
Templates Plain PHP with extract Blade-like engine (layouts, sections, partials)
Email PHPMailer (ext. dep.) Native SMTP (zero dependencies)
Queues No Yes (File + Database drivers)
CLI / Console No Yes (serve, migrate, scaffold)
External dependencies 3 (Medoo, PHPMailer, JSON) Zero — native PHP extensions only
Error Detection Simple message Intelligent debug mode with exact line, fix suggestions, syntax highlighting, stack trace, environment and request info
Log System Simple file Multi-channel (file, database, syslog) with rotation, PSR-like levels, filters, and query
Plugins No Complete system — discovery, lifecycle, hooks/events, marketplace-ready
Authentication No Full auth — login, register, roles, password reset, remember-me, throttle
OAuth2 / Social Login No Google, Microsoft, GitHub — full flow
Cloud Storage No S3-compatible — AWS S3, MinIO, Cloudflare R2, DigitalOcean, Backblaze
Admin Dashboard No Admin panel — login, user CRUD, logs, metrics

Requirements

  • PHP 8.1 or higher
  • Extensions: pdo, json, mbstring, openssl, curl

Installation

composer create-project routsy/framework my-project
cd my-project

# Set up environment
cp .env.example .env

# Generate encryption key
php bin/routsy key:generate

# Start development server
php bin/routsy serve

Quick Start

Minimal setup

<?php
// public/index.php
require __DIR__ . '/../vendor/autoload.php';

$app = new Routsy\Routsy([
    'debug'     => true,
    'base_path' => dirname(__DIR__),
]);

$app->router->get('/', function ($request, $response) {
    return $response->json(['hello' => 'world']);
});

$app->run();

With configuration file

$config = require __DIR__ . '/../config/app.php';
$config['base_path'] = dirname(__DIR__);
$app = new Routsy\Routsy($config);
$app->run();

Directory Structure

my-project/
├── bin/
│   └── routsy                # CLI (composer, migrations, scaffold)
├── config/
│   └── app.php               # Main configuration
├── public/
│   ├── index.php             # HTTP entry point
│   └── .htaccess             # URL rewriting
├── routes/
│   ├── web.php               # Web routes (pages, views)
│   └── api.php               # API routes (JSON)
├── src/                      # Framework core
│   ├── Routsy.php            # Main application class
│   ├── Router/               # Routing system
│   ├── Database/             # Query builder, schema, migrations
│   ├── Security/             # CSRF, JWT, encryption, sanitizer, rate limiter
│   ├── Ai/                   # OpenAI client, prompts, embeddings
│   ├── Cache/                # File, Redis, Memory drivers
│   ├── Validation/           # Validator with 30+ rules
│   ├── Session/              # Session + flash management
│   ├── View/                 # Template engine
│   ├── Mail/                 # Native SMTP
│   ├── Queue/                # Queue system
│   ├── Console/              # CLI commands
│   ├── Http/                 # Request, Response, Middleware
│   ├── ErrorHandler/         # Intelligent error detector
│   ├── Log/                  # Multi-channel logger
│   ├── Auth/                 # Authentication system
│   ├── Storage/              # S3-compatible storage
│   ├── FileManager/          # File & media management
│   ├── Plugin/               # Plugin system
│   ├── Integrations/         # OAuth2 providers
│   └── Helpers/              # Global helper functions
├── storage/
│   ├── cache/
│   ├── logs/
│   ├── uploads/
│   └── temp/
├── views/
│   ├── layouts/
│   ├── partials/
│   └── home.php
├── admin/                    # Admin dashboard routes and views
├── plugins/                  # Installed plugins directory
├── .env.example
├── .htaccess
└── composer.json

Core Features

Router

Basic Routes

use Routsy\Http\Request;
use Routsy\Http\Response;

// GET
$router->get('/users', function (Request $req, Response $res) {
    return $res->json(['users' => []]);
});

// POST
$router->post('/users', function (Request $req, Response $res) {
    $data = $req->input();
    return $res->success($data, 'Created', 201);
});

// PUT, PATCH, DELETE, OPTIONS
$router->put('/users/{id}', fn(Request $req, Response $res) =>
    $res->success(['updated' => $req->param('id')])
);

// Any method
$router->any('/webhook', fn() => 'ok');

// Multiple methods
$router->match(['GET', 'POST'], '/form', fn() => 'ok');

Route Parameters

// Lapa style: :param
$router->get('/users/:id', function (Request $req, Response $res) {
    $id = $req->param('id');
    return $res->success(['user' => $id]);
});

// Laravel style: {param}
$router->get('/posts/{slug}', function (Request $req, Response $res) {
    return $res->success(['slug' => $req->param('slug')]);
});

// With constraints (where)
$router->get('/users/{id}', fn() => ...)
    ->where(['id' => '\d+']); // numbers only

Route Groups

$router->group('/admin', function ($router) {
    $router->get('/dashboard', fn() => 'Admin Dashboard');
    $router->get('/users', fn() => 'Manage Users');

    // Nested groups
    $router->group('/settings', function ($router) {
        $router->get('/profile', fn() => 'Profile Settings');
        $router->get('/security', fn() => 'Security Settings');
    });
});

Subdomains (Multi-Host)

// API subdomain
$router->host('api.mysite.com', function ($router) {
    $router->get('/v1/status', fn() => ['version' => '1.0']);
});

// Admin subdomain
$router->host('admin.mysite.com', function ($router) {
    $router->get('/', fn() => 'Admin Area');
});

REST Resource Routes

// Auto-generates: index, create, store, show, edit, update, destroy
$router->resource('posts', App\Controllers\PostController::class);

// API resource (no create/edit)
$router->apiResource('posts', App\Controllers\PostApiController::class);

Named Routes

$router->get('/profile', fn() => ...)->name('profile');
$router->get('/users/{id}', fn() => ...)->name('users.show');

// Generate URL
$url = $router->url('users.show', ['id' => 5]); // /users/5

// Global helper
$url = route('profile');

Middleware per Route / Group

$auth = function (Request $req, Closure $next) {
    if (!$req->bearerToken()) {
        return (new Response())->error('Unauthorized', 401);
    }
    return $next($req);
};

$router->group('/admin', function ($router) {
    $router->get('/dashboard', fn() => 'OK');
})->middleware($auth);

Database

Configuration

// config/app.php
'database' => [
    'driver'   => 'mysql',   // mysql, pgsql, sqlite
    'host'     => 'localhost',
    'database' => 'my_database',
    'username' => 'root',
    'password' => '',
    'charset'  => 'utf8mb4',
],

Fluent Query Builder

$db = $app->db();

// SELECT
$users = $db->table('users')
    ->select(['id', 'name', 'email'])
    ->where('active', 1)
    ->where('created_at', '>', '2024-01-01')
    ->orderBy('name')
    ->limit(10)
    ->get();

// First result
$user = $db->table('users')->where('id', 1)->first();

// Count
$total = $db->table('users')->where('active', 1)->count();

// EXISTS
$exists = $db->table('users')->where('email', 'test@test.com')->exists();

// Pagination
$result = $db->table('users')->paginate(15, 1);
// Returns: ['data' => [...], 'total' => 100, 'page' => 1, 'perPage' => 15, 'lastPage' => 7]

INSERT / UPDATE / DELETE

// Insert
$id = $db->table('users')->insert([
    'name'  => 'John Doe',
    'email' => 'john@email.com',
]);

// Insert many
$db->table('users')->insertMany([
    ['name' => 'A', 'email' => 'a@a.com'],
    ['name' => 'B', 'email' => 'b@b.com'],
]);

// Update
$db->table('users')->where('id', 1)->update(['name' => 'New Name']);

// Delete
$db->table('users')->where('id', 1)->delete();

Joins

$orders = $db->table('orders', 'o')
    ->select(['o.id', 'o.total', 'u.name'])
    ->join('users', 'o.user_id', '=', 'u.id', 'INNER')
    ->leftJoin('payments', 'o.id', '=', 'p.order_id')
    ->where('o.status', 'paid')
    ->get();

Aggregates

$total = $db->table('orders')->sum('amount');
$avg   = $db->table('orders')->avg('amount');
$min   = $db->table('products')->min('price');
$max   = $db->table('products')->max('price');

Transactions

$app->connection()->transaction(function ($conn) use ($db) {
    $db->table('accounts')->where('id', 1)->update(['balance -=' => 100]);
    $db->table('accounts')->where('id', 2)->update(['balance +=' => 100]);
});

Raw SQL

$results = $db->raw("SELECT * FROM users WHERE created_at > ?", ['2024-01-01']);
$db->rawExecute("UPDATE users SET active = 0 WHERE last_login < ?", ['2023-01-01']);

Schema Builder & Migrations

// Create table programmatically
$app->schema()->createTable('products', function ($table) {
    $table->id();
    $table->string('name', 200);
    $table->text('description')->nullable();
    $table->decimal('price', 10, 2);
    $table->integer('stock')->default(0);
    $table->boolean('active')->default(true);
    $table->foreign('category_id', 'id', 'categories');
    $table->timestamps();
    $table->softDeletes();
});

// Migrations via CLI
// php bin/routsy make:migration CreateProductsTable
// php bin/routsy migrate
// php bin/routsy migrate:rollback
// php bin/routsy migrate:status

Security

CSRF Protection

// Generate token (in forms)
echo csrf_field();   // <input type="hidden" name="_csrf_token" value="...">
echo csrf_token();   // Token value only

// Validate token (in controller)
$app->csrf->validate($request->post('_csrf_token'));

AES-256-GCM Encryption

$encrypted = encrypt('secret data');
$original  = decrypt($encrypted);

// Password hashing (Argon2id)
$hash = bcrypt('my-password');

JSON Web Tokens (JWT)

// Generate token
$token = $app->jwt()->encode([
    'user_id' => 42,
    'role'    => 'admin',
], 3600); // 1 hour

// Validate token
try {
    $payload = $app->jwt()->decode($token);
    $userId  = $payload['user_id'];
} catch (\RuntimeException $e) {
    // Invalid or expired token
}

// Refresh token
$newToken = $app->jwt()->refresh($token);

Rate Limiting

$ip = $request->ip();
$limiter = $app->rateLimiter;

if ($limiter->tooManyAttempts('login:' . $ip, 5, 60)) {
    return $response->error('Too many attempts. Try again in ' .
        $limiter->availableIn('login:' . $ip, 5, 60) . 's', 429);
}

// Clear attempts after success
$limiter->clear('login:' . $ip);

Input Sanitization

$clean = $app->sanitizer->string($input);     // Remove HTML, escape
$email = $app->sanitizer->email($input);       // Sanitize email
$num   = $app->sanitizer->int($input);         // Safe integer
$url   = $app->sanitizer->url($input);         // Safe URL
$file  = $app->sanitizer->filename($input);    // Safe filename
$arr   = $app->sanitizer->array($data);        // Recursively sanitize array

AI — Artificial Intelligence

Compatible client for OpenAI, Azure OpenAI, Anthropic, Ollama, and any compatible API.

Configuration

AI_API_KEY=sk-your-key-here
AI_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4o

Chat / Questions

$ai = $app->ai();

// Simple question
$answer = $ai->ask('What is the capital of France?');

// With system prompt
$answer = $ai->ask(
    'What is PHP?',
    'You are a programming expert. Answer concisely.'
);

// Conversation with history
$messages = [
    ['role' => 'system', 'content' => 'You are a helpful assistant.'],
    ['role' => 'user', 'content' => 'Who are you?'],
];
$result = $ai->chat($messages);
echo $result['choices'][0]['message']['content'];

// Streaming (real-time)
$fullText = $ai->chatStream($messages, function ($chunk) {
    echo $chunk;
    ob_flush(); flush();
});

Vision (Image Analysis)

$description = $ai->vision(
    'https://example.com/photo.jpg',
    'Describe what you see in this image.'
);

Embeddings & Semantic Search

// Create semantic index
$embeddings = new Routsy\Ai\Embeddings($ai);
$embeddings->index('doc1', 'Routsy is a modern PHP framework.');
$embeddings->index('doc2', 'Laravel is a popular PHP framework.');
$embeddings->index('doc3', 'React is a JavaScript library for UIs.');

// Search
$results = $embeddings->search('frameworks for web development', 2);
// Results sorted by cosine similarity

Prompt Templates

// Register template
Routsy\Ai\Prompt::register('translator',
    'Translate the following text from {{from}} to {{to}}: {{text}}'
);

// Use template
$result = Routsy\Ai\Prompt::use('translator', [
    'from' => 'English',
    'to'   => 'French',
    'text' => 'Hello, how are you?',
]);

Cache

// File driver (default)
$app->cache->set('key', 'value', 3600);   // Store
$value  = $app->cache->get('key');         // Retrieve
$exists = $app->cache->has('key');         // Check
$app->cache->delete('key');                // Remove
$app->cache->clear();                      // Clear all

// Increment / Decrement
$app->cache->increment('visits');
$app->cache->decrement('stock');

// Remember (cache + callback)
$users = $app->cache->remember('users.list', 300, function () use ($db) {
    return $db->table('users')->get();
});

// Global helper
cache('key', 'value', 600);
$value = cache('key');

Drivers

// File (default)
$cache = $app->cache->driver('file');

// Redis (requires ext-redis)
$cache = $app->cache->driver('redis');

// Memory (request lifetime only)
$cache = $app->cache->driver('memory');

Validation

Usage in Controller

use Routsy\Validation\Validator;
use Routsy\Validation\ValidationException;

$data = $request->input();

try {
    $validated = Validator::validate($data, [
        'name'     => 'required|min:3|max:100',
        'email'    => 'required|email|unique:users,email',
        'age'      => 'required|integer|between:1,120',
        'password' => 'required|min:8|confirmed',
        'role'     => 'required|in:admin,user,editor',
        'bio'      => 'nullable|string|max:500',
        'website'  => 'nullable|url',
        'avatar'   => 'nullable|file|image|mimes:jpeg,png|size:2048',
    ]);

    // $validated contains only the validated fields
    $db->table('users')->insert($validated);

} catch (ValidationException $e) {
    return $response->error('Validation failed', 422, $e->errors());
}

Available Rules

Rule Description
required Field is required
nullable Field may be null (skips remaining rules)
email Valid email address
min:N Minimum value (string: length, number: value)
max:N Maximum value
between:MIN,MAX Value between MIN and MAX
numeric Numeric value
integer Integer value
string Must be a string
array Must be an array
in:val1,val2 Value must be in the list
not_in:val1,val2 Value must not be in the list
regex:/pattern/ Regex validation
url Valid URL
date Valid date
date_format:Y-m-d Date in specific format
confirmed Confirmation (field_confirmation must match)
same:other_field Must equal another field
different:other_field Must differ from another field
starts_with:abc Starts with...
ends_with:xyz Ends with...
alpha Letters only
alphanumeric Letters and numbers only
boolean true/false/0/1
json Valid JSON
file Uploaded file
image Valid image
mimes:jpeg,png Allowed MIME types
size:2048 Maximum size in KB
unique:table,column Value must be unique in DB
exists:table,column Value must exist in DB

Custom Messages

$validator = new Validator($data, $rules);
$validator->setMessages([
    'email.required' => 'The email is required!',
    'email.email'    => 'Invalid email format.',
    'required'       => 'The :field field is required.',
]);

if (!$validator->validate()) {
    $errors = $validator->errors();
}

Views — Template Engine

Syntax

{{-- Layout with extension --}}
@extends('default')

@section('content')
    <h1>{{ $title }}</h1>

    @if($user)
        <p>Welcome, {{ $user['name'] }}!</p>
    @else
        <p>Please log in.</p>
    @endif

    @foreach($posts as $post)
        <div class="post">
            <h2>{{ $post['title'] }}</h2>
            <p>{!! $post['body'] !!}</p>
        </div>
    @endforeach

    @include('partials.sidebar', ['categories' => $categories])

    <form method="POST">
        @csrf
        @method('PUT')
        <input name="name" value="{{ old('name') }}">
    </form>
@endsection

Usage in Controller

// Render view
return $app->view->render('home', [
    'title' => 'Home Page',
    'user'  => ['name' => 'John'],
]);

// With specific layout
return $app->view->render('blog/post', $data, 'admin');

// Global helper
return view('welcome', ['name' => 'World']);

// Partial
return $app->view->partial('header', ['title' => 'My Site']);

Available Directives

Directive Description
@extends('layout') Defines parent layout
@section('name') ... @endsection Defines a section
@yield('name') Renders a section
@include('view') Includes a partial
@if / @elseif / @else / @endif Conditionals
@foreach / @endforeach Foreach loop
@for / @endfor For loop
@while / @endwhile While loop
@isset / @endisset Checks if variable exists
@empty / @endempty Checks if variable is empty
@auth / @endauth User is authenticated
@guest / @endguest User is not authenticated
@csrf CSRF field
@method('PUT') Method spoofing
{{ $var }} Safe echo (htmlspecialchars)
{!! $var !!} Raw echo

Mail

$app->mailer()->send(
    'recipient@email.com',
    'Email Subject',
    '<h1>Hello!</h1><p>Email body in <strong>HTML</strong>.</p>',
    [
        'cc'       => 'cc@email.com',
        'bcc'      => 'bcc@email.com',
        'reply_to' => 'reply@email.com',
        'alt_body' => 'Plain text version for non-HTML clients.',
    ]
);

// With attachments
$app->mailer()
    ->attach('/path/file.pdf', 'Report.pdf')
    ->send('recipient@email.com', 'Report', '<p>Attached.</p>');

Queue

Create a Job

class SendWelcomeEmail
{
    public function handle(array $payload): void
    {
        $email = $payload['email'];
        // Send welcome email...
    }
}

Enqueue Jobs

// File driver
$app->queue()->push(SendWelcomeEmail::class, [
    'email' => 'user@example.com',
    'name'  => 'John',
]);

// With 5-minute delay
$app->queue()->push(SendWelcomeEmail::class, $data, 300);

Process Queue

# Process 1 job
php bin/routsy queue:work

# Process 10 jobs
php bin/routsy queue:work --max=10

# Pending jobs
php bin/routsy queue:pending

Console

# Development server
php bin/routsy serve
php bin/routsy serve 0.0.0.0 8080

# Generate application key
php bin/routsy key:generate

# Clear cache
php bin/routsy cache:clear

# Scaffolding
php bin/routsy make:migration CreateUsersTable
php bin/routsy make:controller UserController
php bin/routsy make:middleware AuthMiddleware

# Migrations
php bin/routsy migrate
php bin/routsy migrate:rollback
php bin/routsy migrate:status

# Queues
php bin/routsy queue:work
php bin/routsy queue:pending

# List routes
php bin/routsy routes:list

Custom Commands

// In bin/routsy or bootstrap
$console = new Routsy\Console\Console($argv, $basePath);
$console->register('my:command', function ($args) {
    echo "Command executed with arguments: " . implode(', ', $args) . "\n";
    return 0;
}, 'Description of my command');

Helpers — Global Functions

Helper Description
app() Application instance
config('database.host') Config value (dot notation)
env('APP_KEY') Environment variable
view('home', $data) Render view
route('users.show', ['id' => 5]) Generate URL by route name
redirect('/login') Redirect
back() Go back to previous page
session('user_id') Get session value
flash('success', 'Message!') Flash message
csrf_field() CSRF hidden field
csrf_token() CSRF token
old('email') Old input (after validation)
error('email') First validation error
errors() All validation errors
cache('key') Get/set cache
encrypt($data) Encrypt data
decrypt($payload) Decrypt data
bcrypt('password') Password hash
slug('Hello World!') Generate slug
str_random(32) Random string
str_limit('long text', 10) Truncate string
now() Current DateTime
ago('2024-01-01') "X time ago"
dd($var) Dump and Die
dump($var) Dump (non-fatal)
abort(404) Abort with HTTP code
base_path('config') Absolute path
storage_path('uploads') Path to storage
public_path('css/app.css') Path to public
response(['ok' => true]) Create Response

Error Handler

Routsy includes an intelligent error detection system that, in debug mode, shows exactly:

  • The exact line where the error occurred, highlighted with an animated ← ERROR HERE arrow
  • Code context (10 lines before and after) with syntax highlighting
  • Error classification (Syntax Error, Type Error, Database Error...)
  • Automatic fix suggestions based on the error type
  • Formatted stack trace with argument previews for each call
  • Environment information (PHP version, OS, memory, loaded extensions)
  • Request data (URL, method, headers, IP, user agent)
  • Timeline (execution time up to the error)
  • Dark/light mode auto-detection

Example error with suggestion

┌ MAIN ERROR src/Controllers/UserController.php (lines 32-52 of 120)
  30 │     public function show(Request $req, Response $res): mixed
  31 │     {
  32 │►        $user = $this->db->table('users')->where('id', $id)->first();    ← ERROR HERE
  33 │         return $res->success($user);
  34 │     }

💡 Fix Suggestion: The variable was not defined before being used. Check that you initialized the variable or that the name is correct. In this case, $id should be $req->param('id').

In Production

With APP_DEBUG=false, the user sees a friendly, generic error page. Full details are always logged to storage/logs/error.log.


Log System

Multi-channel logger compatible with PSR-3. Supports levels (emergency, alert, critical, error, warning, notice, info, debug), multiple channels, and automatic rotation.

// Available channels: file, database, syslog
$app->logger->info('User {user} logged in', ['user' => 'john@email.com']);
$app->logger->error('DB connection failed: {error}', ['error' => $e->getMessage()]);
$app->logger->warning('API limit nearly reached: {remaining}', ['remaining' => 5]);

// Specific channel
$app->logger->error('Critical error!', [], ['error']);  // writes to error channel only

// Global context
$app->logger->withContext(['app_version' => '1.0', 'env' => 'production']);

// Query logs (for dashboards)
$recentErrors = $app->logger->query('error', 50, 0, 'database');

// Prune old logs
$app->logger->prune(30); // remove logs older than 30 days

Channels:

Channel Description
file File with automatic size-based rotation (default)
database logs table in the database
syslog Operating system syslog
null Discards logs (for testing)

Plugin System

Complete plugin system with automatic discovery, lifecycle management, and hooks. Plugins can be installed directly from GitHub repositories through the admin dashboard.

$plugins = $app->plugins();

// Discover plugins
$all = $plugins->discover();

// Install / Enable / Disable
$plugins->install('MyPlugin');
$plugins->enable('MyPlugin');
$plugins->disable('MyPlugin');
$plugins->uninstall('MyPlugin');

// Hooks — fire events
$plugins->fire('user.created', $user);

// Hooks — listen to events
$plugins->on('user.created', function ($user) {
    // Send welcome email...
});

// Filters — transformation chain
$plugins->on('content.render', function ($content) {
    return str_replace('{year}', date('Y'), $content);
});
$parsed = $plugins->filter('content.render', $rawContent);

// Check if plugin is active
if ($plugins->isActive('MyPlugin')) { ... }

Plugin structure:

plugins/MyPlugin/
├── manifest.json      # {"name":"MyPlugin","version":"1.0","requires":[]}
├── Plugin.php         # Class with boot(), install(), uninstall()
├── routes.php         # Plugin routes
├── views/             # Plugin views
└── migrations/        # Plugin migrations

manifest.json:

{
    "name": "MyPlugin",
    "version": "1.0.0",
    "description": "Plugin description",
    "author": "Author Name",
    "requires": ["AnotherPlugin"],
    "class": "Plugin\\MyPlugin\\Plugin"
}

Installation via Admin Dashboard: Plugins can be installed directly through the admin dashboard at /admin/plugins by providing the GitHub repository URL (e.g., https://github.com/username/routsy-plugin). The system automatically clones the repository, resolves dependencies, registers the plugin, and activates it.


Auth System

Complete authentication system with roles, throttle, and remember-me.

$auth = $app->auth();

// Login
$user = $auth->attempt('email@example.com', 'password', remember: true);
if (!$user) { /* invalid credentials */ }

// Check authentication
if ($auth->check()) {
    $user = $auth->user();
    $id   = $auth->id();
}

// Logout
$auth->logout();

// Register
$userId = $auth->register([
    'name'     => 'John Doe',
    'email'    => 'john@email.com',
    'password' => 'secret123',
    'role'     => 'user',
]);

// Roles
if ($auth->hasRole('admin')) { ... }
if ($auth->hasRole(['admin', 'editor'])) { ... }
if ($auth->can('delete')) { ... }

// Password Reset
$token = $auth->createResetToken('john@email.com');
// Send email with link: /reset?token=$token&email=...
$success = $auth->resetPassword('john@email.com', $token, 'new-password');

// User CRUD (admin)
$users = $auth->listUsers(page: 1, perPage: 15, filters: ['role' => 'admin']);
$user  = $auth->findUser(1);
$auth->createUser([...]);
$auth->updateUser(1, ['role' => 'admin']);
$auth->deleteUser(1);

Auth middleware:

use Routsy\Auth\AuthMiddleware;

// Require login
$router->get('/profile', fn() => ...)->middleware(
    AuthMiddleware::auth($app)
);

// Require specific role
$router->get('/admin', fn() => ...)->middleware(
    AuthMiddleware::role($app, 'admin')
);

// Redirect if already authenticated
$router->get('/login', fn() => ...)->middleware(
    AuthMiddleware::guest($app)
);

OAuth2 — Social Integrations

Login with Google, Microsoft, and GitHub. Generic OAuth 2.0 client with full flow support.

use Routsy\Integrations\OAuth2;

// Configuration
$oauthConfig = [
    'client_id'     => 'YOUR_CLIENT_ID',
    'client_secret' => 'YOUR_CLIENT_SECRET',
    'redirect_uri'  => 'https://mysite.com/auth/google/callback',
];

$oauth = new OAuth2($app, 'google', $oauthConfig);

// Route: initiate login
$router->get('/auth/google', function () use ($oauth) {
    $oauth->redirect();
});

// Route: callback (process return)
$router->get('/auth/google/callback', function () use ($oauth, $app) {
    try {
        $user = $oauth->callback();
        // $user = ['provider' => 'google', 'email' => '...', 'name' => '...', 'avatar' => '...']

        // Create/authenticate local user
        $localUser = $app->db()->table('users')->where('email', $user['email'])->first();
        if (!$localUser) {
            $id = $app->auth()->register([
                'name'           => $user['name'],
                'email'          => $user['email'],
                'password'       => bin2hex(random_bytes(16)),
                'oauth_provider' => 'google',
                'oauth_id'       => $user['provider_id'],
            ]);
            $localUser = $app->auth()->findUser($id);
        }

        $app->auth()->login($localUser);
        return $app->response->redirect('/dashboard');

    } catch (\RuntimeException $e) {
        return $app->response->error('Authentication failed: ' . $e->getMessage());
    }
});

// Microsoft
$oauth = new OAuth2($app, 'microsoft', ['client_id' => '...', 'client_secret' => '...', 'redirect_uri' => '...', 'tenant' => 'common']);

// GitHub
$oauth = new OAuth2($app, 'github', ['client_id' => '...', 'client_secret' => '...', 'redirect_uri' => '...']);

Supported providers: Google, Microsoft (Azure AD), GitHub


S3 Storage — Cloud Storage

S3-compatible driver for AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, and Wasabi.

use Routsy\Storage\S3Driver;

// Configuration
$s3 = new S3Driver([
    'key'      => 'YOUR_ACCESS_KEY',
    'secret'   => 'YOUR_SECRET_KEY',
    'region'   => 'us-east-1',
    'bucket'   => 'my-bucket',
    'endpoint' => 'https://s3.amazonaws.com',  // AWS
    // 'endpoint' => 'https://minio.mysite.com', // MinIO
    // 'endpoint' => 'https://ACCOUNT.r2.cloudflarestorage.com', // Cloudflare R2
    // 'path_style' => true, // For MinIO and R2
]);

// Basic operations
$s3->put('folder/photo.jpg', $imageContent, 'image/jpeg');
$s3->putFile('docs/report.pdf', '/local/path/file.pdf', 'application/pdf');
$content = $s3->get('folder/photo.jpg');
$exists  = $s3->exists('folder/photo.jpg');
$size    = $s3->size('folder/photo.jpg');
$mime    = $s3->mimeType('folder/photo.jpg');
$s3->delete('folder/photo.jpg');

// URLs
$url       = $s3->url('folder/photo.jpg');            // Public URL
$signedUrl = $s3->signedUrl('folder/photo.jpg', 3600); // Pre-signed URL (1h)

// Listing
$files = $s3->list('folder/', 100);
// [['key' => 'folder/a.jpg', 'size' => 12345, 'last_modified' => '...', 'etag' => '...'], ...]

// Copy / Move
$s3->copy('source.txt', 'destination.txt');
$s3->move('source.txt', 'destination.txt');

Authentication: AWS Signature V4 calculated natively, with zero external dependencies.


File Manager

Complete file upload, storage, and processing system. Ideal for blogs, CMS, and galleries.

$fm = $app->fileManager();

// Upload with validation
$file = $fm->upload('photo', [
    'max_size'  => '5MB',
    'types'     => ['image/jpeg', 'image/png', 'image/webp'],
    'min_width' => 800,
], 'blog/posts');

// Upload raw content
$doc = $fm->put('docs/report.pdf', $pdfContent, 'application/pdf');

// URLs
echo $file->url();          // /storage/uploads/blog/posts/abc123.jpg
echo $file->signedUrl(300); // Temporary URL (5 min)

// Read / Download
$content = $fm->read($file);
$fm->download($file, 'profile-photo.jpg');

// Thumbnails (GD, zero dependencies)
$thumb = $fm->thumbnail($file, 400, 300, 'cover');  // center crop
$thumb = $fm->thumbnail($file, 800, 0, 'contain');   // proportional
$fm->images()->crop($file, 0, 0, 200, 200);          // manual crop
$fm->images()->convert($file, 'webp');               // convert format
$fm->images()->watermarkText($file, '© My Site');    // watermark
$fm->images()->strip($file);                         // remove EXIF

// Serve image with on-the-fly resize
// GET /files/uploads/blog/posts/photo.jpg?w=400&h=300&fit=cover

// Media Library (blog/CMS)
$library = $fm->library();
$media   = $library->list('blog/post-1', page: 1, perPage: 20);
$images  = $library->list('blog/post-1', type: 'image');
$library->attachToPost($file->id, $postId, ['alt_text' => 'Article photo']);
$library->setFeatured($postId, $file->id);
$featured = $library->getFeatured($postId);
$gallery  = $library->getPostFiles($postId);

// Folders and statistics
$folders = $library->folders();
$stats   = $library->stats(); // total, images, size, folders

Supported drivers:

Driver Config Usage
local Local disk (default) $fm->driver('local')
s3 AWS S3, MinIO, R2... $fm->addDriver('s3', new S3Driver([...]))

Image capabilities (native GD, no Imagick):

  • Resize with fit modes: cover, contain, fill, stretch
  • Manual crop
  • Format conversion (JPEG ↔ PNG ↔ WebP ↔ GIF)
  • Text watermark
  • EXIF metadata stripping
  • Cached thumbnails (processed once)

Admin Dashboard

Built-in admin panel accessible at /admin.

// Routes are auto-loaded from admin/routes.php
// Access: http://localhost:8000/admin

// Create first admin:
$app->auth()->register([
    'name'     => 'Admin',
    'email'    => 'admin@example.com',
    'password' => 'admin123',
    'role'     => 'admin',
]);

Dashboard features:

  • Administrative login with throttle
  • Dashboard with metrics (users, logs, storage, PHP)
  • User CRUD with search, role filter, and pagination
  • Log viewer with level filters and text search
  • Plugin manager — install plugins directly from GitHub repositories
  • Automatic dark/light mode
  • Protected by auth + role middleware

Configuration

.env (Environment Variables)

APP_NAME=Routsy
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_KEY=base64:...

DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=routsy
DB_USERNAME=root
DB_PASSWORD=

CACHE_DRIVER=file
QUEUE_DRIVER=file
LOG_DRIVER=file
LOG_LEVEL=debug

MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_USERNAME=user@example.com
MAIL_PASSWORD=secret
MAIL_FROM_NAME=Routsy
MAIL_FROM_ADDRESS=noreply@example.com

AI_API_KEY=sk-...
AI_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4o

CORS_ENABLED=false
CORS_ORIGINS=https://mysite.com
CORS_METHODS=GET,POST,PUT,DELETE
CORS_HEADERS=Content-Type,Authorization
CORS_CREDENTIALS=true

Security Best Practices

  1. APP_KEY — Generate with php bin/routsy key:generate and never commit the .env file
  2. CSRF — Use @csrf in all POST/PUT/DELETE forms
  3. JWT — Use short-lived tokens; refresh as needed
  4. Rate Limiting — Apply to sensitive endpoints (login, API)
  5. Sanitization — Always sanitize user input
  6. CORS — Configure specific origins in production
  7. HTTPS — Enable FORCE_HTTPS=true in production
  8. Security Headers — X-Frame-Options, X-Content-Type-Options, X-XSS-Protection (enabled by default)

Deploy in Production

# 1. Install dependencies without dev
composer install --no-dev --optimize-autoloader

# 2. Configure environment
cp .env.example .env
php bin/routsy key:generate

# 3. Tune .env for production
APP_DEBUG=false
FORCE_HTTPS=true
CORS_ENABLED=true

# 4. Set permissions
chmod -R 755 storage
chmod -R 755 public

License

MIT License. See the LICENSE file for details.


Routsy Framework — Built with passion. Visit us at https://github.com/routsy.

About

Advanced PHP framework with zero dependencies — AI, Auth, WebSocket, PWA, Cache, Queue, Admin

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors