Core Concepts

How Forge works under the hood.

Architecture

Forge uses a modular architecture with dependency injection. It's built to be simple and flexible.

Core Components

Engine Core
Database Layer
Router
View Engine
Module Loader
DI Container
CLI Kernel
Bootstrap

Dependency Injection

Forge's DI container resolves dependencies using PHP attributes. Keeps code testable and clean.

Service Registration

<?php

use Forge\Core\DI\Attributes\Service;

#[Service]
class UserService
{
    public function __construct(
        private UserRepository $repository,
        private EmailService $emailService
    ) {}
    
    public function createUser(array $data): User
    {
        $user = $this->repository->create($data);
        $this->emailService->sendWelcomeEmail($user);
        return $user;
    }
}

#[Service(singleton: true)] // Only one instance throughout the application
class CacheService
{
    private array $cache = [];
    
    public function get(string $key): mixed
    {
        return $this->cache[$key] ?? null;
    }
}

Interface Binding

<?php

use Forge\Core\DI\Attributes\Bind;

interface PaymentGatewayInterface
{
    public function charge(float $amount): bool;
}

#[Service]
#[Bind(PaymentGatewayInterface::class)]
class StripePaymentGateway implements PaymentGatewayInterface
{
    public function charge(float $amount): bool
    {
        // Stripe implementation
        return true;
    }
}

Routing System

Attribute-based routing that auto-discovers routes from controllers. No separate route files needed.

<?php

use Forge\Core\Routing\Route;
use Forge\Core\Http\Request;
use Forge\Core\Http\Response;

class ApiController
{
    #[Route("/api/users")]
    public function listUsers(): Response
    {
        return $this->json(User::all());
    }
    
    #[Route("/api/users/{id}", method: "GET")]
    public function getUser(Request $request, int $id): Response
    {
        $user = User::find($id);
        return $this->json($user);
    }
    
    #[Route("/api/users", method: "POST")]
    public function createUser(Request $request): Response
    {
        $data = $request->json();
        $user = User::create($data);
        return $this->json($user, 201);
    }
    
    #[Route("/api/users/{id}", method: "PUT")]
    public function updateUser(Request $request, int $id): Response
    {
        $user = User::find($id);
        $user->update($request->json());
        return $this->json($user);
    }
}

Use curly braces for route parameters. Names must match method params.

Middleware

Filter HTTP requests before they hit your application.

Creating Middleware

<?php

use Forge\Core\Http\Middleware;
use Forge\Core\Http\Request;
use Forge\Core\Http\Response;

class AuthMiddleware extends Middleware
{
    public function handle(Request $request, callable $next): Response
    {
        if (!$request->hasHeader('Authorization')) {
            return new Response('Unauthorized', 401);
        }
        
        // Continue to next middleware or controller
        return $next($request);
    }
}

Applying Middleware

<?php

use Forge\Core\Http\Attributes\Middleware;

// Apply middleware to entire controller
#[Middleware("auth")]
class DashboardController
{
    #[Route("/dashboard")]
    public function index(): Response
    {
        return $this->view('dashboard/index');
    }
    
    #[Route("/dashboard/settings")]
    public function settings(): Response
    {
        return $this->view('dashboard/settings');
    }
}

// Apply middleware to specific methods
class UserController
{
    #[Middleware("App\Modules\ForgeAuth\Middlewares\AuthMiddleware")]
    #[Route("/profile")]
    public function profile(): Response
    {
        return $this->view('user/profile');
    }
    
    // Multiple middleware using repeatable attribute
    #[Middleware("web")]
    #[Middleware("auth")]
    #[Route("/admin")]
    public function admin(): Response
    {
        return $this->view('admin/dashboard');
    }
}

Use #[Middleware] for controllers/methods, or middlewares in #[Route] for routes.

View Engine

PHP-first templating. No new syntax to learn.

Template Inheritance

<!-- layouts/app.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><?= $title ?? 'My App' ?></title>
    <?php use Forge\Core\View\View; echo View::section('head'); ?>
</head>
<body>
    <header>
        <?php use Forge\Core\View\View; echo View::section('header'); ?>
    </header>
    
    <main>
        <?php use Forge\Core\View\View; echo View::section('content'); ?>
    </main>
    
    <footer>
        <?php use Forge\Core\View\View; echo View::section('footer'); ?>
    </footer>
</body>
</html>

Child Templates

<!-- pages/home.php -->
<?php use Forge\Core\View\View; View::layout(name: "layouts/app", loadFromModule: false); ?>

<?php View::startSection('head'); ?>
    <link rel="stylesheet" href="/css/home.css">
<?php View::endSection(); ?>

<?php View::startSection('content'); ?>
    <div class="hero">
        <h1>Welcome to <?= $appName ?></h1>
        <p><?= $description ?></p>
    </div>
<?php View::endSection(); ?>

Components & Partials

<!-- Using components -->
<?php use Forge\Core\Component\Component; echo Component::render(name: "components/alert", props: [
    'type' => 'success',
    'message' => 'User created successfully!'
]); ?>

<!-- Including partials -->
<?php use Forge\Core\View\View; echo View::include(name: "partials/navigation"); ?>

Database & ORM

Lightweight ORM with multiple database support.

Model Definition

<?php

use Forge\Core\Database\Model;
use Forge\Core\Database\Attributes\Table;
use Forge\Core\Database\Attributes\Column;

#[Table('users')]
class User extends Model
{
    #[Column(type: 'string', length: 255)]
    public string $name;
    
    #[Column(type: 'string', length: 255, unique: true)]
    public string $email;
    
    #[Column(type: 'string', length: 255)]
    protected string $password;
    
    protected array $fillable = ['name', 'email', 'password'];
    protected array $hidden = ['password'];
    
    // Relationships
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

Query Builder

<?php

// Basic queries
$users = User::all();
$user = User::find(1);
$activeUsers = User::where('status', 'active')->get();

// Advanced queries
$users = User::where('created_at', '>', '2024-01-01')
    ->orderBy('name')
    ->limit(10)
    ->get();

// Relationships
$user = User::with('posts')->find(1);
$posts = $user->posts;

// Aggregates
$count = User::count();
$avgAge = User::avg('age');

// Raw queries when needed
$users = User::raw('SELECT * FROM users WHERE custom_condition = ?', [true]);

Module System

Extend Forge with self-contained modules for services, routes, views, etc.

Module Structure

{
  "$schema": "./../../engine/Core/Schema/module-schema.json",
  "name": "forge-my-module",
  "version": "1.0.0",
  "description": "A custom module for my application",
  "type": "generic",
  "order": 100,
  "author": "Your Name",
  "license": "MIT"
}

Module Class

<?php

namespace App\Modules\MyModule;

use Forge\Core\DI\Container;
use Forge\Core\Module\Attributes\Compatibility;
use Forge\Core\Module\Attributes\Module;
use Forge\Core\Module\Attributes\Repository;
use Forge\Core\DI\Attributes\Service;
use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\LifecycleHookName;
use App\Modules\MyModule\Contracts\MyModuleInterface;
use App\Modules\MyModule\Services\MyModuleService;

#[Module(
    name: 'MyModule',
    description: 'A custom module for my application',
    order: 100
)]
#[Service]
#[Compatibility(framework: '>=0.1.0', php: '>=8.3')]
#[Repository(type: 'git', url: 'https://github.com/your-repo/modules')]
final class MyModule
{
    public function register(Container $container): void
    {
        // Register services
        $container->bind(MyModuleInterface::class, MyModuleService::class);
    }

    #[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
    public function onAfterModuleRegister(): void
    {
        // Boot logic after module registration
    }
}

Configuration Management

Environment-based configuration that's secure and flexible.

<?php

use Forge\Core\Config\Config;
use Forge\Core\Config\Environment;

// Using helper functions (recommended)
$dbHost = env('DB_HOST', 'localhost');
$debug = env('APP_DEBUG', false);
$appName = config('app.name', 'Forge App');

// Using Config class directly
$config = Config::get('database.connections.mysql');
Config::set('cache.driver', 'redis');

// Using Environment class directly  
$env = Environment::getInstance();
$port = $env->get('APP_PORT', 8000);
$isDev = $env->isDevelopment();
$debugEnabled = $env->isDebugEnabled();

// Checking if configuration exists
if (config('services.stripe.key')) {
    // Stripe is configured
}

CLI Kernel

Create custom commands and automate tasks.

Creating Custom Commands

<?php

declare(strict_types=1);

namespace App\Modules\MyModule\Commands;

use Forge\CLI\Command;
use Forge\Core\Module\Attributes\CLICommand;

#[CLICommand(name: 'app:send-emails', description: 'Send pending emails to users')]
class SendEmailsCommand extends Command
{
    public function execute(array $args): int
    {
        $user = $this->argument('name', $args) ?? 'all users';
        $useQueue = $this->option('queue', $args) !== null;
        
        $this->info('Starting email sending process...');
        
        if ($user !== 'all users') {
            $this->line("Sending emails to user: {$user}");
        } else {
            $this->line('Sending emails to all users');
        }
        
        if ($useQueue) {
            $this->line('Using queue for processing');
        }
        
        // Your email sending logic here
        
        $this->info('Emails sent successfully!');
        
        return 0;
    }
}

Commands auto-discover. Just create and use.