Getting Started with Forge

This guide will help you get Forge up and running. It's pretty straightforward.

Requirements

  • PHP 8.2 or higher
  • Web server (Apache, Nginx, or PHP CLI built-in server)
  • SQLite, MySQL, or PostgreSQL (optional, install via database capability if needed)

Note: Forge doesn't require Composer. The kernel is dependency-free. Database and ORM are capabilities you install when needed, not built-in requirements.

Installation

A few ways to get Forge running.

# Quick install with one command
bash <(curl -Ls https://raw.githubusercontent.com/forge-engine/installer/main/installer.sh)

That's it! This command will clone the starter project, set up the folder structure, and give you a working Forge app you can run immediately.

Manual Installation

If you prefer to set things up manually:

git clone https://github.com/forge-engine/forge-starter
cd forge-starter
cp env-example .env
php install.php
php forge.php key:generate
php forge.php package:install-project

Configuration

Forge uses environment variables for configuration. Edit your .env file:

# Application Settings
APP_NAME="My Forge App"
APP_ENV=development
APP_DEBUG=true
APP_KEY=your-generated-key
FORGE_DEVELOPER_MODE=false

# Database Configuration (only if you've installed a database capability)
DB_DRIVER=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=forge_app
DB_USER=root
DB_PASS=password
SQLITE_PATH=/storage/database
SQLITE_DB=/database.sqlite

# Cache Configuration
CACHE_DRIVER=file

# Session Configuration
SESSION_DRIVER=file
SESSION_LIFETIME=1440

Start the development server to test your installation:

php forge.php serve

Visit http://localhost:8000 to see your application running!

Creating Your First Controller

Most Forge commands use interactive wizards by default. Let's create a simple controller to handle HTTP requests:

Using the Wizard

Run the command without arguments to start the interactive wizard:

# Run wizard (interactive)
php forge.php generate:controller

# The wizard will ask:
# Type (app or module): app
# Name (Controller class name without suffix): Welcome
# Path (Optional subfolder inside Controllers): [press Enter for none]

Skipping the Wizard

You can skip the wizard by providing options directly:

# Skip wizard for app scope
php forge.php generate:controller --type=app --name=Welcome

# Skip wizard for module scope
php forge.php generate:controller --type=module --module=Blog --name=Post

# With optional subfolder path
php forge.php generate:controller --type=app --name=User --path=Admin

This creates a controller in app/Controllers/WelcomeController.php:

<?php

declare(strict_types=1);

namespace App\Controllers;

use Forge\Core\DI\Attributes\Service;
use Forge\Core\Http\Attributes\Middleware;
use Forge\Core\Http\Response;
use Forge\Core\Routing\Route;
use Forge\Core\Http\Request;
use Forge\Traits\ControllerHelper;

#[Service]
#[Middleware('web')]
final class WelcomeController
{
    use ControllerHelper;

    #[Route("/welcome")]
    public function index(Request $request): Response
    {
        $data = [
            "title" => "Welcome to Forge",
            "message" => "Hello from your first controller!"
        ];

        return $this->view(view: "pages/welcome/index", data: $data);
    }
}

Services & Dependency Injection

Forge uses attributes for dependency injection. Services are automatically discovered from any folder in your application or modules when they have the #[Service] or #[Discoverable] attribute.

Service Discovery

The framework automatically scans all directories recursively, so you can organize your code however you prefer. No specific folder structure is required.

Attributes:

  • #[Service] - Register a class as a service in the dependency injection container
  • #[Discoverable] - Semantically marks a class as discoverable (same behavior as #[Service])

Discovery Scope: Services are discovered from all directories under:

  • app/ - Any folder structure
  • modules/*/src/ - Any folder structure within module source directories

Note: While you can use any folder structure, traditional structures like app/Services/ or app/Repositories/ still work perfectly. The framework discovers services based on attributes, not folder location.

<?php
namespace App\Services;

use Forge\Core\DI\Attributes\Service;

#[Service]
class UserService
{
    public function findUser(int $id)
    {
        // Your logic here
        // Note: This example assumes you have a User model from an ORM capability
        // return User::query()->id($id)->first();
    }
}

Using Services

Use services in controllers by adding them to the constructor:

<?php
namespace App\Controllers;

use App\Services\UserService;
use Forge\Core\DI\Attributes\Service;
use Forge\Core\Http\Response;
use Forge\Core\Routing\Route;

#[Service]
class UserController
{
    public function __construct(
        private readonly UserService $userService
    ) {}

    #[Route('/users/{id}')]
    public function show(string $id): Response
    {
        $user = $this->userService->findUser((int)$id);
        return $this->view('users/show', ['user' => $user]);
    }
}

Service Discovery Cache

Service discovery happens once at bootstrap and uses a class map cache for performance. The cache is automatically updated when files change. If you add a new service with #[Service] and it's not being discovered, clear the cache:

php forge.php cache:flush

Note: Services are automatically resolved by the container. Just add them to your constructor and they'll be injected. The discovery process has no performance trade-off since it uses class mapping and only updates entries when files change.

Routing

Forge uses attribute-based routing. Define routes directly on your controller methods:

Basic Routes

<?php
#[Route("/")]
public function home(): Response
{
    return $this->view("pages/home");
}

#[Route("/api/posts", method: "POST")]
public function createPost(Request $request): Response
{
    // Handle POST request
    return $this->json(["status" => "created"]);
}

// Using Middleware attribute on controller/method (recommended)
use Forge\Core\Http\Attributes\Middleware;

#[Middleware("web")]
class WebController
{
    #[Middleware("auth")]
    #[Route("/profile")]
    public function profile(): Response
    {
        return $this->view("profile/index");
    }
}

Route Parameters

Routes support parameters that are automatically extracted and type-cast based on your method signature:

<?php
// Basic parameter - automatically cast to int
#[Route("/users/{id}")]
public function showUser(Request $request, int $id): Response
{
    // $id is automatically cast to int
    return $this->json(["user_id" => $id]);
}

// Multiple parameters
#[Route("/posts/{year}/{month}/{slug}")]
public function showPost(int $year, int $month, string $slug): Response
{
    return $this->json([
        "year" => $year,
        "month" => $month,
        "slug" => $slug
    ]);
}

// Complex parameter with constraint - matches anything including slashes
#[Route("/files/{path:.+}")]
public function serveFile(string $path): Response
{
    // {path:.+} matches any path including slashes (e.g., /files/docs/user-guide.pdf)
    return $this->file($path);
}

// Mix Request object with route parameters
#[Route("/users/{id}/posts/{postId}")]
public function showUserPost(Request $request, int $id, int $postId): Response
{
    // Both $id and $postId are extracted from the route
    // $request is injected automatically
    return $this->json([
        "user_id" => $id,
        "post_id" => $postId,
        "query" => $request->query("filter")
    ]);
}

Parameter Constraints: Use {param:.+} to match any path including slashes. Route parameters are automatically type-cast (int, float, bool) based on your method signature types.

Note: The method parameter accepts a single HTTP method as a string (e.g., "GET", "POST", "PUT", "DELETE"), not an array.

Routes are automatically discovered from your controllers. No need for separate route files! Use #[Middleware] attribute for cleaner middleware application on controllers/methods.

Views & Templates

Forge uses PHP-first templating. Views can be created in your app or within modules (capabilities).

View Locations

  • App scope: app/resources/views/
  • Module scope: modules/ModuleName/src/Resources/views/

Layout Locations

Layouts are expected to be in:

  • App scope: app/resources/views/layouts/
  • Module scope: modules/ModuleName/src/Resources/views/layouts/

Using Views

<!-- app/resources/views/pages/welcome/index.php -->
<?php
use Forge\Core\View\View;

// Load layout from app
View::layout(name: "layouts/app", loadFromModule: false);
?>

<div class="container mx-auto px-4 py-8">
    <h1 class="text-4xl font-bold text-gray-900 mb-4">
        <?= $title ?>
    </h1>
    <p class="text-xl text-gray-600">
        <?= $message ?>
    </p>
</div>

To load a layout from a module (like ForgeUi):

<?php
use Forge\Core\View\View;

// Load layout from module
View::layout(name: "layouts/app", loadFromModule: true, moduleName: "ForgeUi");

Creating Layouts

Create a layout file in your app:

<!-- app/resources/views/layouts/app.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Forge App</title>
</head>
<body>
    <?php
    use Forge\Core\View\View;
    echo View::section('content');
    ?>
</body>
</html>

Note: See the ForgeUi module for reference on module folder structure and component organization.

Database & ORM (Capabilities)

Database and ORM are not built into the kernel. They're capabilities you install when you need them.

Install a database capability first:

# Install a database capability (with wizard)
php forge.php package:install-module

# Or skip wizard and specify directly
php forge.php package:install-module --module=ForgeDatabaseSQL

# Or install an ORM capability
php forge.php package:install-module --module=ForgeSqlOrm

Once installed, you can create migrations and models:

# Create a migration
php forge.php generate:migration CreateUsersTable

# Run migrations
php forge.php db:migrate

Note: Database and ORM functionality requires installing the appropriate capability module first. The kernel doesn't include these by default.

Developer Mode

Developer mode unlocks advanced CLI commands for working with registries and publishing kernel/capability versions. You only need this if you're building your own kernel or publishing capabilities.

Enabling Developer Mode

Set FORGE_DEVELOPER_MODE=true in your .env file:

FORGE_DEVELOPER_MODE=true

Or set it in config/app.php:

<?php
return [
    'developer_mode' => true,
    // ... other config
];

Commands Unlocked

When developer mode is enabled, these commands become available:

# Framework/Kernel registry commands
php forge.php dev:framework:list          # List available kernel versions
php forge.php dev:framework:publish        # Publish kernel registry changes
php forge.php dev:framework:version        # Create new kernel version

# Module/Capability registry commands
php forge.php dev:module:list              # List available capability versions
php forge.php dev:module:publish           # Publish capability registry changes
php forge.php dev:module:version           # Create new capability version

# Registry management
php forge.php dev:registry:config          # Configure registry settings
php forge.php dev:registry:init             # Initialize new registry

When to enable: Only enable developer mode if you're building your own kernel fork or publishing capabilities to registries. For regular application development, you don't need it.

CLI Commands

Most commands use interactive wizards by default. You can skip wizards by providing options with --option=value flags.

Interactive Command Browser

Forge includes a retro-styled interactive command browser that makes it easy to discover and execute commands. Simply run php forge.php without any arguments to access it.

Features: The interactive browser includes a splash screen (skippable with --no-splash), multi-column command listings that adapt to your terminal width, category-based browsing, and the ability to execute commands or view help directly from the interface. Use arrow keys to navigate and Esc to exit.

# Launch interactive command browser
php forge.php

# Skip splash screen
php forge.php --no-splash

# Show traditional command list
php forge.php --list

# Launch interactive browser directly
php forge.php --interactive

The interactive browser allows you to:

  • Browse commands by category (General, Database, Modules, etc.)
  • View all commands in a searchable multi-column layout
  • Execute commands directly from the browser
  • View detailed help for any command
  • Navigate with arrow keys (↑↓←→) and exit with Esc

Generate Commands

# Generate files (wizards ask for type: app/module, name, etc.)
php forge.php generate:controller
php forge.php generate:middleware
php forge.php generate:migration
php forge.php generate:model
php forge.php generate:service
php forge.php generate:component

# Skip wizards with options
php forge.php generate:controller --type=app --name=User
php forge.php generate:controller --type=module --module=Blog --name=Post

# Generate test (wizard can ask for type: app/module/engine)
php forge.php generate:test
php forge.php generate:test --type=app --group=example
php forge.php generate:test --type=module --module=Blog --group=example
# Note: engine scope is for testing the kernel itself

Database Operations

php forge.php db:migrate
php forge.php db:migrate:rollback

Development Server

php forge.php serve

Cache Management

php forge.php cache:clear
php forge.php cache:flush  # Flush service discovery cache

Application Maintenance

php forge.php down
php forge.php up

Package Management

Package management commands use wizards by default. Use --module= to skip the wizard:

# With wizard (interactive)
php forge.php package:install-module
php forge.php package:remove-module

# Skip wizard
php forge.php package:install-module --module=ForgeAuth
php forge.php package:install-module --module=ForgeAuth@1.2.0
php forge.php package:remove-module --module=ForgeAuth
php forge.php package:list-modules

Other Commands

# Generate application key
php forge.php key:generate

# See all available commands (traditional list)
php forge.php help

# Launch interactive command browser
php forge.php

Next Steps

What you might want to look at next:

  • Core Concepts — How dependency injection, middleware, and the module system work.
  • Capabilities — Available capabilities and how to make your own.
  • API Reference — Complete API documentation for all kernel components.