PHPackages                             ascetic-soft/wirebox - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PSR &amp; Standards](/categories/psr-standards)
4. /
5. ascetic-soft/wirebox

ActiveLibrary[PSR &amp; Standards](/categories/psr-standards)

ascetic-soft/wirebox
====================

Lightweight, zero-config PSR-11 dependency injection container for PHP 8.4+ with autowiring, PHP attributes, compiled container generation, and built-in dotenv support

v1.3.0(2mo ago)28MITPHPPHP &gt;=8.4CI passing

Since Feb 11Pushed 2mo agoCompare

[ Source](https://github.com/ascetic-soft/Wirebox)[ Packagist](https://packagist.org/packages/ascetic-soft/wirebox)[ RSS](/packages/ascetic-soft-wirebox/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (7)Dependencies (4)Versions (8)Used By (0)

Wirebox
=======

[](#wirebox)

[![CI](https://github.com/ascetic-soft/Wirebox/actions/workflows/ci.yml/badge.svg)](https://github.com/ascetic-soft/Wirebox/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/0b1b9b1feea318ec5169428e5536167d9654fd029bfed96dfe04be03c9404943/68747470733a2f2f636f6465636f762e696f2f67682f617363657469632d736f66742f57697265626f782f67726170682f62616467652e7376673f746f6b656e3d796f74464857694d7450)](https://codecov.io/gh/ascetic-soft/Wirebox)[![PHPStan Level 9](https://camo.githubusercontent.com/9c47b48f30dd28687f283075af582f1981f0a86034becf261e60aa08b8a0bd96/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d6c6576656c253230392d627269676874677265656e)](https://phpstan.org/)[![Latest Stable Version](https://camo.githubusercontent.com/590b8a5d28de272a1909a7d8297eb9fa17db636671cc6951bfcaab544e5d0f4b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f617363657469632d736f66742f77697265626f78)](https://packagist.org/packages/ascetic-soft/wirebox)[![Total Downloads](https://camo.githubusercontent.com/20ed858e4e7e281861867204b88088b6b1f68bb46954bc11a5e149f5ea2efb37/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f617363657469632d736f66742f77697265626f78)](https://packagist.org/packages/ascetic-soft/wirebox)[![PHP Version](https://camo.githubusercontent.com/7ae30c9acc171de2a8d131449f1926508d060f5825c5c8871a67ba2dc812d7e7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f617363657469632d736f66742f77697265626f782f706870)](https://packagist.org/packages/ascetic-soft/wirebox)[![License](https://camo.githubusercontent.com/afe632c86154ea9b22d3f9053a2399ae2973cebb61a23b6e7b3bbf58f63628b5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f617363657469632d736f66742f77697265626f78)](https://packagist.org/packages/ascetic-soft/wirebox)

Lightweight PHP 8.4 DI container with autowiring, directory scanning, PHP attributes, and dotenv support.

📖 **[Full Documentation](https://ascetic-soft.github.io/Wirebox/)** | **[Документация на русском](https://ascetic-soft.github.io/Wirebox/ru/)** | **[中文文档](https://ascetic-soft.github.io/Wirebox/zh/)**

Features
--------

[](#features)

- **PSR-11** compatible (`Psr\Container\ContainerInterface`)
- **Autowiring** — automatic constructor dependency resolution via reflection
- **Directory scanning** — point at a directory, all classes are auto-registered
- **PHP Attributes** — `#[Inject]`, `#[Singleton]`, `#[Transient]`, `#[Lazy]`, `#[Eager]`, `#[Tag]`, `#[Param]`, `#[Exclude]`, `#[AutoconfigureTag]`
- **Autoconfiguration** — automatically tag services by interface or attribute (Symfony-style)
- **Dotenv** — built-in `.env` parser with 3-level priority (no external dependencies)
- **Tagged services** — group services by tag and retrieve them as a collection
- **Lazy proxies** — deferred instantiation via PHP 8.4 native lazy objects
- **Compiled container** — generate a PHP class with zero reflection at runtime
- **Setter injection** — configure method calls on services after instantiation
- **Circular dependency detection** — unsafe cycles detected at build time with clear error messages

Requirements
------------

[](#requirements)

- PHP &gt;= 8.4
- [psr/container](https://packagist.org/packages/psr/container) ^2.0

Installation
------------

[](#installation)

```
composer require ascetic-soft/wirebox
```

Quick Start
-----------

[](#quick-start)

```
use AsceticSoft\Wirebox\ContainerBuilder;

$builder = new ContainerBuilder(projectDir: __DIR__);

// Scan a directory — all concrete classes are auto-registered
$builder->scan(__DIR__ . '/src');

// Build the container
$container = $builder->build();

// Resolve any service
$service = $container->get(App\UserService::class);
```

Configuration
-------------

[](#configuration)

### ContainerBuilder

[](#containerbuilder)

The `ContainerBuilder` is the main entry point for configuring the container.

```
$builder = new ContainerBuilder(projectDir: __DIR__);
```

The `projectDir` is used as the base path for resolving `.env` files.

### Directory Scanning

[](#directory-scanning)

Scan directories to auto-register all concrete classes (abstract classes, interfaces, traits, and enums are skipped):

```
$builder->scan(__DIR__ . '/src');
$builder->scan(__DIR__ . '/modules');
```

Exclude files by glob pattern:

```
$builder->exclude('Entity/*');
$builder->exclude('*Test.php');
$builder->scan(__DIR__ . '/src');
```

Classes marked with `#[Exclude]` are skipped automatically:

```
use AsceticSoft\Wirebox\Attribute\Exclude;

#[Exclude]
class InternalHelper
{
    // Will not be registered in the container
}
```

### Interface Binding

[](#interface-binding)

Bind an interface to a concrete implementation:

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

When scanning, if an interface has exactly one implementation in the scanned directory, it is auto-bound automatically. If two or more implementations of the same interface are found, the auto-binding becomes ambiguous and `build()` will throw a `ContainerException`. Use an explicit `bind()` call to resolve the ambiguity:

```
$builder->scan(__DIR__ . '/Services');
// PaymentInterface has StripePayment and PayPalPayment — ambiguous!
$builder->bind(PaymentInterface::class, StripePayment::class);
```

Alternatively, if you don't need a specific binding but want to suppress the ambiguity error (e.g. the interface is resolved at runtime, or you only use tagged iteration), use `excludeFromAutoBinding()`:

```
$builder->excludeFromAutoBinding(PaymentInterface::class);
$builder->scan(__DIR__ . '/Services');
// No error — PaymentInterface is excluded from the auto-binding check
$container = $builder->build();
```

Multiple interfaces can be excluded at once:

```
$builder->excludeFromAutoBinding(
    PaymentInterface::class,
    NotificationChannelInterface::class,
);
```

> **Note:** Unlike `registerForAutoconfiguration()`, this does not apply any autoconfiguration rules (tags, lifetime, etc.) — it only suppresses the ambiguity error.

### Factory Registration

[](#factory-registration)

Register a service with a custom factory closure:

```
$builder->register(Connection::class, function (Container $c) {
    return new Connection(
        host: $c->getParameter('db.host'),
        port: $c->getParameter('db.port'),
    );
});
```

### Fluent Definition API

[](#fluent-definition-api)

Override or configure individual service definitions:

```
$builder->register(FileLogger::class)
    ->transient()                                   // New instance every time
    ->lazy()                                        // Deferred instantiation
    ->tag('logger')                                 // Add a tag
    ->call('setFormatter', [JsonFormatter::class]);  // Setter injection
```

### Parameters and Environment Variables

[](#parameters-and-environment-variables)

Set parameters that can reference environment variables:

```
$builder->parameter('db.host', '%env(DB_HOST)%');
$builder->parameter('db.port', '%env(int:DB_PORT)%');
$builder->parameter('app.debug', '%env(bool:APP_DEBUG)%');
$builder->parameter('rate.limit', '%env(float:RATE_LIMIT)%');
```

Supported type casts: `string` (default), `int`, `float`, `bool`.

Parameters can also contain env expressions embedded in a larger string:

```
$builder->parameter('dsn', 'mysql:host=%env(DB_HOST)%;port=%env(DB_PORT)%');
```

Environment Variables
---------------------

[](#environment-variables)

Wirebox resolves environment variables with 3-level priority (highest first):

PrioritySourceDescription1`.env.local.php`Generated by `composer dump-env`. A PHP file returning an array. Fastest.2`$_ENV` / `getenv()`Real system environment variables.3`.env`Parsed by built-in `DotEnvParser`. Development fallback.All files are resolved relative to `projectDir`.

### `.env` File Format

[](#env-file-format)

```
APP_NAME=Wirebox
DB_HOST=localhost
DB_PORT=5432
APP_DEBUG=true

# Comments are supported
QUOTED="hello world"
SINGLE='literal value'

# Variable interpolation
BASE_PATH=/opt
FULL_PATH="${BASE_PATH}/app"

# Export prefix is stripped
export SECRET_KEY=abc123
```

### Production: `composer dump-env`

[](#production-composer-dump-env)

For production, use Symfony's `composer dump-env` to generate `.env.local.php`:

```
composer dump-env prod
```

This creates a PHP file that returns an array — no file parsing at runtime.

PHP Attributes
--------------

[](#php-attributes)

### `#[Singleton]`

[](#singleton)

Marks a class as singleton (this is the default behavior, use for explicitness):

```
use AsceticSoft\Wirebox\Attribute\Singleton;

#[Singleton]
class DatabaseConnection
{
}
```

### `#[Transient]`

[](#transient)

A new instance is created on every `get()` call:

```
use AsceticSoft\Wirebox\Attribute\Transient;

#[Transient]
class RequestContext
{
}
```

### `#[Lazy]`

[](#lazy)

Return a lightweight proxy immediately; the real instance is created only when a property or method is first accessed. Uses PHP 8.4 native lazy objects (`ReflectionClass::newLazyProxy`):

```
use AsceticSoft\Wirebox\Attribute\Lazy;

#[Lazy]
class HeavyReportGenerator
{
    public function __construct(
        private Connection $db,
        private CacheInterface $cache,
    ) {
        // expensive setup...
    }
}
```

Can be combined with `#[Transient]` to create a new lazy proxy on every `get()` call.

The same behavior is available via the fluent API:

```
$builder->register(HeavyReportGenerator::class)->lazy();
```

Lazy proxies are fully supported by the compiled container.

#### Default lazy mode

[](#default-lazy-mode)

`ContainerBuilder` enables lazy mode by default — all services without an explicit `#[Lazy]` or `#[Eager]` attribute are created as lazy proxies. You can disable this:

```
$builder->defaultLazy(false);
```

### `#[Eager]`

[](#eager)

Opt out of lazy instantiation when the container's default lazy mode is enabled:

```
use AsceticSoft\Wirebox\Attribute\Eager;

#[Eager]
class AppConfig
{
    // Always created immediately, even when defaultLazy is on
}
```

The same behavior is available via the fluent API:

```
$builder->register(AppConfig::class)->eager();
```

### `#[Tag]`

[](#tag)

Tag a class for grouped retrieval. Repeatable:

```
use AsceticSoft\Wirebox\Attribute\Tag;

#[Tag('event.listener')]
#[Tag('audit')]
class UserCreatedListener
{
}
```

Retrieve tagged services:

```
foreach ($container->getTagged('event.listener') as $listener) {
    $listener->handle($event);
}
```

### `#[AutoconfigureTag]`

[](#autoconfiguretag)

Automatically tag all classes that implement an interface or are decorated with a custom attribute. Place `#[AutoconfigureTag]` on the interface or attribute class.

On an **interface** — all implementing classes receive the tag:

```
use AsceticSoft\Wirebox\Attribute\AutoconfigureTag;

#[AutoconfigureTag('command.handler')]
interface CommandHandlerInterface
{
    public function __invoke(object $command): void;
}

// Automatically receives the 'command.handler' tag when scanned
class CreateUserHandler implements CommandHandlerInterface
{
    public function __invoke(object $command): void
    {
        // ...
    }
}
```

On a **custom attribute** — all classes decorated with that attribute receive the tag:

```
use AsceticSoft\Wirebox\Attribute\AutoconfigureTag;

#[Attribute(Attribute::TARGET_CLASS)]
#[AutoconfigureTag('scheduler.task')]
class AsScheduled {}

// Automatically receives the 'scheduler.task' tag when scanned
#[AsScheduled]
class DailyReportTask
{
    public function run(): void { /* ... */ }
}
```

Repeatable — multiple tags can be applied:

```
#[AutoconfigureTag('command.handler')]
#[AutoconfigureTag('auditable')]
interface CommandHandlerInterface {}
```

### Programmatic Autoconfiguration

[](#programmatic-autoconfiguration)

For more control (lifetime, lazy, multiple tags), use `registerForAutoconfiguration()` on the builder:

```
$builder->registerForAutoconfiguration(EventListenerInterface::class)
    ->tag('event.listener')
    ->singleton()
    ->lazy();
```

Any class implementing `EventListenerInterface` found during `scan()` will automatically get the `event.listener` tag, be configured as a singleton, and use lazy proxies.

This also works with attributes:

```
$builder->registerForAutoconfiguration(AsScheduled::class)
    ->tag('scheduler.task')
    ->transient();
```

### CQRS Example

[](#cqrs-example)

Autoconfiguration makes it easy to set up command and query handlers:

```
use AsceticSoft\Wirebox\Attribute\AutoconfigureTag;

#[AutoconfigureTag('command.handler')]
interface CommandHandlerInterface
{
    public function __invoke(object $command): void;
}

#[AutoconfigureTag('query.handler')]
interface QueryHandlerInterface
{
    public function __invoke(object $query): mixed;
}

// Handlers — no manual tagging needed
class CreateUserHandler implements CommandHandlerInterface
{
    public function __invoke(object $command): void { /* ... */ }
}

class DeleteUserHandler implements CommandHandlerInterface
{
    public function __invoke(object $command): void { /* ... */ }
}

class GetUserHandler implements QueryHandlerInterface
{
    public function __invoke(object $query): mixed { /* ... */ }
}
```

Build and retrieve. Autoconfigured interfaces are excluded from the ambiguous auto-binding check, so multiple implementations work seamlessly:

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

// No need for bind() — CommandHandlerInterface is autoconfigured
$container = $builder->build();

// Iterate all command handlers
foreach ($container->getTagged('command.handler') as $handler) {
    // CreateUserHandler, DeleteUserHandler
}

// Iterate all query handlers
foreach ($container->getTagged('query.handler') as $handler) {
    // GetUserHandler
}
```

### `#[Inject]`

[](#inject)

Specify a concrete implementation for a type-hinted parameter:

```
use AsceticSoft\Wirebox\Attribute\Inject;

class NotificationService
{
    public function __construct(
        #[Inject(SmtpMailer::class)]
        private MailerInterface $mailer,
    ) {
    }
}
```

### `#[Param]`

[](#param)

Inject a scalar value from environment variables:

```
use AsceticSoft\Wirebox\Attribute\Param;

class DatabaseService
{
    public function __construct(
        #[Param('DB_HOST')] private string $host,
        #[Param('DB_PORT')] private int $port,
        #[Param('APP_DEBUG')] private bool $debug = false,
    ) {
    }
}
```

The value is automatically cast to the parameter's type hint (`string`, `int`, `float`, `bool`).

### `#[Exclude]`

[](#exclude)

Exclude a class from auto-registration during directory scanning:

```
use AsceticSoft\Wirebox\Attribute\Exclude;

#[Exclude]
class InternalHelper
{
}
```

Container API
-------------

[](#container-api)

### PSR-11

[](#psr-11)

```
$service = $container->get(UserService::class);
$exists  = $container->has(UserService::class);
```

### Tagged Services

[](#tagged-services)

```
$loggers = $container->getTagged('logger'); // iterable
```

### Parameters

[](#parameters)

```
$host = $container->getParameter('db.host');
$all  = $container->getParameters();
```

### Self-Resolution

[](#self-resolution)

The container registers itself under three keys, so you can type-hint whichever you prefer:

```
use Psr\Container\ContainerInterface;
use AsceticSoft\Wirebox\WireboxContainerInterface;

class ServiceLocator
{
    public function __construct(
        // Any of the three works:
        private ContainerInterface $psr,              // PSR-11
        private WireboxContainerInterface $wirebox,   // Wirebox extended contract
    ) {
    }
}
```

`WireboxContainerInterface` extends PSR-11 with `getTagged()`, `getParameter()`, and `getParameters()`. Both `Container` and `CompiledContainer` implement it.

Compiled Container
------------------

[](#compiled-container)

For production, compile the container to a PHP class. This eliminates reflection at runtime:

```
$builder = new ContainerBuilder(projectDir: __DIR__);
$builder->scan(__DIR__ . '/src');
$builder->bind(LoggerInterface::class, FileLogger::class);
$builder->parameter('db.host', '%env(DB_HOST)%');

// Generate the compiled container
$builder->compile(
    outputPath: __DIR__ . '/var/cache/CompiledContainer.php',
    className: 'CompiledContainer',
    namespace: 'App\Cache',
);
```

Use the compiled container in production:

```
require_once __DIR__ . '/var/cache/CompiledContainer.php';

$container = new App\Cache\CompiledContainer();
$service = $container->get(UserService::class);
```

The compiled container:

- Extends `AsceticSoft\Wirebox\Compiler\CompiledContainer`
- Implements `WireboxContainerInterface` (and PSR-11 `ContainerInterface`)
- Has a dedicated factory method for each service
- Supports singleton caching, interface bindings, parameters, and tags
- Binding aliases are folded into the method map at compile time for single-lookup resolution
- Does **not** support factory closures (they require runtime evaluation)

Circular Dependencies
---------------------

[](#circular-dependencies)

Wirebox detects circular dependencies **at build time** (`build()` / `compile()`) and throws `CircularDependencyException` for unsafe cycles before the container is ever used.

### When is a cycle safe?

[](#when-is-a-cycle-safe)

A circular dependency is safe **only** when **all** services in the cycle are **lazy singletons**. The proxy is cached before real instantiation begins, so when the dependency chain loops back, it finds the proxy in the cache instead of re-entering construction:

```
// Safe — both are lazy singletons (the default)
#[Lazy]
class ServiceA
{
    public function __construct(public readonly ServiceB $b) {}
}

#[Lazy]
class ServiceB
{
    public function __construct(public readonly ServiceA $a) {}
}

$container = $builder->build(); // OK
$a = $container->get(ServiceA::class);
assert($a->b->a === $a); // same proxy
```

### When is a cycle unsafe?

[](#when-is-a-cycle-unsafe)

ScenarioResultAll services are lazy singletonsSafe — proxy cached before instantiationAny service is **eager**Unsafe — Autowirer hits the same class twiceAny service is **lazy transient**Unsafe — proxy is not cached, infinite recursionUnsafe cycles are reported with a clear message:

```
Circular dependency detected: ServiceA -> ServiceB -> ServiceA.
All services in a circular dependency must be lazy singletons.
Unsafe: ServiceB (not lazy)

```

> **Note:** Factory-based definitions (`register(..., fn() => ...)`) are skipped during cycle analysis because their dependencies cannot be determined statically.

Error Handling
--------------

[](#error-handling)

Wirebox throws specific exceptions for common issues:

ExceptionWhen`NotFoundException`Service not found and cannot be auto-wired`AutowireException`Cannot resolve a constructor parameter`CircularDependencyException`Unsafe circular dependency detected at build or runtime`ContainerException`General container error (e.g. ambiguous bindings)All exceptions implement `Psr\Container\ContainerExceptionInterface`.

```
use AsceticSoft\Wirebox\Exception\CircularDependencyException;

try {
    $builder->build();
} catch (CircularDependencyException $e) {
    // "Circular dependency detected: ServiceA -> ServiceB -> ServiceA. ..."
    echo $e->getMessage();
}
```

Full Example
------------

[](#full-example)

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

$builder = new ContainerBuilder(projectDir: __DIR__);

// Exclude entities and migrations from the container
$builder->exclude('Entity/*');
$builder->exclude('Migration/*');

// Scan application classes
$builder->scan(__DIR__ . '/src');

// Explicit bindings where needed
$builder->bind(LoggerInterface::class, FileLogger::class);
$builder->bind(CacheInterface::class, RedisCache::class);

// Environment-based parameters
$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
$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 use
$container = $builder->build();
$app = $container->get(App\Kernel::class);
$app->run();
```

Testing
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance83

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

7

Last Release

88d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/67126642d0e8ae8837b3104dcd120f7992ed0837538a7d140914e17420eb17c2?d=identicon)[borodulin](/maintainers/borodulin)

---

Top Contributors

[![borodulin](https://avatars.githubusercontent.com/u/8121448?v=4)](https://github.com/borodulin "borodulin (27 commits)")

---

Tags

containerPSR-11Autowiringdependency-injectiondiiocdotenvattributesservice container

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ascetic-soft-wirebox/health.svg)

```
[![Health](https://phpackages.com/badges/ascetic-soft-wirebox/health.svg)](https://phpackages.com/packages/ascetic-soft-wirebox)
```

###  Alternatives

[php-di/php-di

The dependency injection container for humans

2.8k48.9M994](/packages/php-di-php-di)[slince/di

A flexible dependency injection container

20260.4k6](/packages/slince-di)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
