Getting Started

Get up and running with Wirebox in under 5 minutes.

Table of contents

Installation

Install Wirebox via Composer:

composer require ascetic-soft/wirebox

Requirements:

  • PHP >= 8.4
  • psr/container ^2.0

Your First Container

Step 1: Create your services

// src/Mailer.php
namespace App;

class Mailer
{
    public function send(string $to, string $message): void
    {
        // send email...
    }
}
// src/UserService.php
namespace App;

class UserService
{
    public function __construct(
        private Mailer $mailer,
    ) {
    }

    public function register(string $email): void
    {
        // create user...
        $this->mailer->send($email, 'Welcome!');
    }
}

Step 2: Build the container

// bootstrap.php
use AsceticSoft\Wirebox\ContainerBuilder;

require_once __DIR__ . '/vendor/autoload.php';

$builder = new ContainerBuilder(projectDir: __DIR__);
$builder->scan(__DIR__ . '/src');
$container = $builder->build();

Step 3: Resolve and use services

$userService = $container->get(App\UserService::class);
$userService->register('user@example.com');

Wirebox automatically resolves Mailer as a dependency of UserService — no manual wiring required.


How Autowiring Works

When you call $container->get(UserService::class), Wirebox:

  1. Inspects the constructor of UserService via reflection
  2. Sees that it requires a Mailer instance
  3. Recursively resolves Mailer (creating it if needed)
  4. Injects it into UserService’s constructor
  5. Returns the fully constructed UserService

All of this happens automatically. No configuration files, no manual bindings.

By default, all services are singletons — the same instance is returned on every get() call. Use the #[Transient] attribute to get a new instance each time.


Working with Interfaces

When your code depends on interfaces, Wirebox auto-binds them if there’s exactly one implementation:

// src/LoggerInterface.php
namespace App;

interface LoggerInterface
{
    public function log(string $message): void;
}

// src/FileLogger.php
namespace App;

class FileLogger implements LoggerInterface
{
    public function log(string $message): void
    {
        file_put_contents('/var/log/app.log', $message . "\n", FILE_APPEND);
    }
}

// src/OrderService.php
namespace App;

class OrderService
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }
}

Since FileLogger is the only class implementing LoggerInterface in the scanned directory, Wirebox binds the interface automatically:

$builder->scan(__DIR__ . '/src');
$container = $builder->build();

$orderService = $container->get(App\OrderService::class);
// LoggerInterface is auto-bound to FileLogger

If there are multiple implementations, you need an explicit binding:

$builder->bind(App\LoggerInterface::class, App\FileLogger::class);

Adding Environment Variables

Create a .env file in your project root:

DB_HOST=localhost
DB_PORT=5432
APP_DEBUG=true

Use the #[Param] attribute to inject environment variables:

use AsceticSoft\Wirebox\Attribute\Param;

class DatabaseService
{
    public function __construct(
        #[Param('DB_HOST')] private string $host,
        #[Param('DB_PORT')] private int $port,
    ) {
    }
}

Or define parameters on the builder:

$builder->parameter('db.host', '%env(DB_HOST)%');
$builder->parameter('db.port', '%env(int:DB_PORT)%');

A Complete Bootstrap Example

<?php
// bootstrap.php

use AsceticSoft\Wirebox\ContainerBuilder;

require_once __DIR__ . '/vendor/autoload.php';

$builder = new ContainerBuilder(projectDir: __DIR__);

// Exclude non-service classes
$builder->exclude('Entity/*');
$builder->exclude('Migration/*');

// Auto-register all classes in src/
$builder->scan(__DIR__ . '/src');

// Explicit bindings for ambiguous interfaces
$builder->bind(LoggerInterface::class, FileLogger::class);
$builder->bind(CacheInterface::class, RedisCache::class);

// Parameters from environment
$builder->parameter('db.host', '%env(DB_HOST)%');
$builder->parameter('db.port', '%env(int:DB_PORT)%');
$builder->parameter('app.debug', '%env(bool:APP_DEBUG)%');

// Custom factory for complex instantiation
$builder->register(PDO::class, function ($c) {
    return new PDO(
        sprintf('mysql:host=%s;port=%d;dbname=app',
            $c->getParameter('db.host'),
            $c->getParameter('db.port'),
        ),
    );
});

// Build and go
$container = $builder->build();
$app = $container->get(App\Kernel::class);
$app->run();

What’s Next?