How Forge works under the hood.
Forge uses a modular architecture with dependency injection. It's built to be simple and flexible.
Forge's DI container resolves dependencies using PHP attributes. Keeps code testable and clean.
<?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;
}
}
<?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;
}
}
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.
Filter HTTP requests before they hit your application.
<?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);
}
}
<?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.
PHP-first templating. No new syntax to learn.
<!-- 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>
<!-- 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(); ?>
<!-- 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"); ?>
Lightweight ORM with multiple database support.
<?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);
}
}
<?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]);
Extend Forge with self-contained modules for services, routes, views, etc.
{
"$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"
}
<?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
}
}
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
}
Create custom commands and automate tasks.
<?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.