Core Concepts

The handful of ideas you need to build on the Forge Kernel. The kernel is small by design — the concepts below are the core; everything else is a capability you plug in.

Not on this page: routing, middleware, views, components, and database/ORM are capabilities, not kernel internals. They're summarized under Beyond the Kernel and covered fully on their own pages.

Architecture

The kernel is a foundation with utilities — you get the structure and plumbing, then decide what to build on top. It boots, loads your app and any capabilities you installed, and hands control to whatever answers the request (usually a routing capability like ForgeRouter).

What ships in the kernel

  • Bootstrap — boots the kernel on every request
  • Dependency injection container — resolves the services your app uses
  • PSR-4 autoloader with a persistent class map
  • Configuration and environment handling
  • Cache system with proxy generation
  • Capability (module) loader and lifecycle hooks
  • CLI kernel and command system
  • Contracts for the boundaries capabilities implement

The kernel component list above is the whole story — there is no router, view engine, or middleware baked in. Those are capabilities you install (ForgeRouter, ForgeView, and so on).

Request flow

Your front controller calls Kernel::init(). The bootstrap runs its setup steps (load the environment, load modules, wire up the container, handle errors, boot sessions), then the installed capabilities register themselves. See Lifecycle for the full sequence.

Dependency Injection

The kernel's Container resolves the dependencies your code needs. It is a singleton, so you reach it from anywhere with Container::getInstance().

The Injectable attribute

Classes you want the container to manage are marked with a single attribute, #[Injectable]. You can give it an optional id and choose whether it is a singleton.

<?php

use Forge\Core\DI\Attributes\Injectable;

#[Injectable] // most services default to a shared (singleton) instance
class UserService
{
    public function __construct(
        private UserRepository $repository,
    ) {}

    public function createUser(array $data): User
    {
        return $this->repository->create($data);
    }
}

// Non-singleton service
#[Injectable(singleton: false)]
class RequestId
{
    public string $id = bin2hex(random_bytes(8));
}

Registering and resolving

Register a service with register() and resolve it with make() (or get(), a synonym). Resolution uses reflection for constructor injection.

<?php

use Forge\Core\DI\Container;

$container = Container::getInstance();
$container->register(UserService::class);
$userService = $container->make(UserService::class);

Registration happens where it makes sense — a module's registration, a service provider, or your bootstrap. The container does not recursively scan every folder for you; you register the services you want it to know about.

Interface binding and tags

Bind an interface to a concrete class with bind(), or register a shared binding with singleton(). Group services with tag() and pull them back together with tagged().

<?php

// Interface to implementation
$container->bind(LoggerInterface::class, FileLogger::class, true);

// Tag a group of services
$container->tag('middleware', [
    AuthMiddleware::class,
    CsrfMiddleware::class,
]);
$middlewares = $container->tagged('middleware');

Capability System

A capability is a self-contained module you install on top of the kernel. The kernel stays lean; capabilities add what you need — database, routing, views, auth, storage. Install what you use, skip the rest.

The Module attribute

Every capability declares itself with the #[Module] attribute. Its options control how the kernel loads it.

  • namedescriptionversion — identity metadata
  • order — load order relative to other modules
  • core: true — do not auto-load; wire it manually
  • isCli: true — load only in CLI context, not web
  • type / categorytags — descriptive metadata
<?php

namespace App\Modules\MyModule;

use Forge\Core\DI\Container;
use Forge\Core\Module\Attributes\Module;
use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\LifecycleHookName;

#[Module(
    name: 'MyModule',
    description: 'A custom capability for my application',
    version: '1.0.0',
    order: 100,
    core: false,   // auto-load
    isCli: false,  // also load in web context
)]
final class MyModule
{
    public function register(Container $container): void
    {
        // Register the module's services
        $container->bind(MyModuleInterface::class, MyModuleService::class);
    }

    #[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
    public function onAfterRegister(): void
    {
        // Run once the module is registered
    }
}

Lifecycle hooks

A capability can run code at specific points in the boot using the #[LifecycleHook] attribute. Available moments include EARLY_BOOT, BEFORE_MODULE_LOAD, AFTER_MODULE_LOAD, AFTER_MODULE_REGISTER, AFTER_CONFIG_LOADED, AFTER_BOOT, and APP_BOOTED. A routing capability, for example, registers its routes once the app has booted.

Declaring behavior

Several companion attributes let a capability declare how the kernel should treat it:

  • #[Compatibility] — required kernel and PHP versions
  • #[ConfigDefaults] — default config without a config file
  • #[PostInstall] / #[PostUninstall] — CLI commands to run after install or removal
  • #[Repository]#[Provides] / #[Requires] — registry and dependency metadata

See Lifecycle and Capabilities for how this all fits together.

Configuration

Configuration is environment-based and read through two global helpers. Your secrets live in .env; the kernel guards it from direct HTTP access.

<?php

// Secrets and environment values
$dbHost = env('DB_HOST', 'localhost');
$debug  = env('APP_DEBUG', false);

// Application config (from config/*.php)
$appName = config('app.name', 'Forge App');

Capabilities can ship their own defaults via #[ConfigDefaults] and you can override them with a config file. See API Reference for the full helper list.

CLI Kernel

The kernel ships a command system. Run php forge.php to open the interactive command browser, or call a command directly. Commands come from the kernel and from installed capabilities.

Kernel commands

The kernel itself provides a focused set:

  • Generate: generate:command, generate:entity, generate:event, generate:migration, generate:module, generate:seeder, generate:test
  • Cache: cache:flush, cache:warm, cache:rebuild
  • Assets & storage: asset:link, asset:unlink, storage:link, storage:unlink
  • Structure: structure:info, structure:init
  • Utility: key:generate, help, stats

Developer-mode registry commands use the dev: prefix (for example dev:registry:init, dev:blueprint:list). Many useful commands you will see come from capabilities — serve and the migrate family are provided by ForgeRouter and ForgeDatabaseSQL respectively, not the kernel.

Writing your own command

A command extends the Command base class, is marked with #[Cli], declares arguments with #[Arg], and implements execute(array $args): int. Commands are discovered from app/Commands/ and from each module's src/Commands/.

<?php

namespace App\Commands;

use Forge\CLI\Attributes\Arg;
use Forge\CLI\Attributes\Cli;
use Forge\CLI\Command;

#[Cli(
    command: 'hello',
    description: 'Say hello',
)]
final class HelloCommand extends Command
{
    #[Arg(name: 'name', description: 'Who to greet', default: 'world')]
    private string $name = 'world';

    public function execute(array $args): int
    {
        $this->info("Hello, {$this->name}!");
        return 0;
    }
}

Commands can pull in the CLI traits — OutputHelper for colored output, Wizard for interactive prompts, CliGenerator for file generation. See API Reference.

Beyond the Kernel

The capabilities below build on the kernel primitives above. Each has its own page with the full picture.

Browse all the capabilities in the catalog.