Skip to content

Application

Dependency Injection

Assemble a small application composition root with a PSR-11 container when a consumer does not provide its own framework container.

The container is an Application utility; framework configuration and Adapter wiring remain consumer-owned boundaries.

The container is a small Application-level PSR-11 implementation for an explicit composition root. Use it where a consumer needs direct portable wiring; when a framework supplies its own container, keep framework configuration and Adapter registration in that consumer rather than treating this class as a framework integration.

Ownership. Application\Service\Container and its PSR-11 exception are Application code. A consumer owns service IDs, factories, framework bindings, secrets, lifecycle policy, and the choice to use a framework container instead. The package does not provide autowiring, reflection-based resolution, or an Adapter configuration format.

Dependencies. The container requires PHP 8.5+ and the required psr/container package. No framework dependency is required.

Install.

composer require johnnickell/fight-common

Start with an explicit application composition root.

use Fight\Common\Application\Service\Container;

$container = new Container();
$container->set('clock', fn (): SystemClock => new SystemClock());
$container->factory('request-id', fn (): string => bin2hex(random_bytes(16)));

$clock = $container->get('clock');       // shared within this registration
$requestId = $container->get('request-id'); // regenerated on every get()

get() only resolves registered services: a missing ID throws Fight\Common\Application\Service\Exception\NotFoundException, which implements PSR-11's NotFoundExceptionInterface. ArrayAccess and parameter methods are a separate key-value store, not aliases for services. Keep credentials out of source and provide them through the consuming application's secure configuration boundary.

Reference

A lightweight PSR-11-compatible Application container for explicit composition roots. It is not an Adapter or framework configuration layer: a consumer using Symfony or another framework should make its own composition choice and keep that framework's bindings at its boundary.

Application\Service
├── Container                     (implements PSR-11 ContainerInterface + ArrayAccess)
└── Exception\NotFoundException   (implements PSR-11 NotFoundExceptionInterface)

Table of Contents

  1. When to Use This Container
  2. PSR-11 Compliance
  3. Services (Shared)
  4. Factories (Prototype)
  5. Parameters
  6. ArrayAccess
  7. Complete Example
  8. NotFoundException

When to Use This Container

  • Stand-alone CLI scripts or daemons that need simple, explicit wiring
  • Small application roots that choose not to use a framework container
  • Test or integration composition where direct factories make dependencies clear
  • Not a replacement for consumer-owned framework configuration — a framework application may use its own container instead

PSR-11 Compliance

Fight\Common\Application\Service\Container implements Psr\Container\ContainerInterface:

public function get(string $id): mixed;
public function has(string $id): bool;

get() throws NotFoundException (implements Psr\Container\NotFoundExceptionInterface) when the requested service ID has not been registered.


Services (Shared)

set(string $id, callable $factory): void

The factory receives the container as its only argument. The return value is cached — subsequent get($id) calls return the same instance.

use Fight\Common\Application\Service\Container;

$container = new Container();

$container->set('logger', function (Container $c): LoggerInterface {
    return new Monolog\Logger('app');
});

$logger = $container->get('logger');      // same instance every time

Use set() for objects that should be singletons within the container: database connections, repositories, routers, mailers, etc.


Factories (Prototype)

factory(string $id, callable $factory): void

Same signature as set(), but the factory is invoked fresh on every get() call.

$container->factory('request', fn ($c) => Request::fromGlobals());

$req1 = $container->get('request');   // new instance
$req2 = $container->get('request');   // different instance

Use factory() for objects that must not be shared: request objects, command instances, or any stateful value that should be created anew each time.


Parameters

Simple key-value store completely separate from services. Parameters do not overlap with get()/has().

$container->setParameter('db.host', 'localhost');
$container->setParameter('db.name', 'myapp');
$container->setParameter('db.port', 3306);

$container->getParameter('db.host');              // 'localhost'
$container->getParameter('db.port', 5432);        // 3306 (default unused)
$container->getParameter('db.user', 'root');      // 'root' (no such parameter)
$container->hasParameter('db.host');              // true
$container->removeParameter('db.host');

ArrayAccess

Container implements ArrayAccess, which delegates to the parameter store:

$container['db.host'] = 'localhost';

echo $container['db.host'];                       // 'localhost'
isset($container['db.host']);                     // true
unset($container['db.host']);

This is a convenience shorthand for parameter access — it does not interact with set()/get() services.


Complete Example

A stand-alone application wired through the container:

use Fight\Common\Application\Service\Container;

$c = new Container();

// --- Parameters ---
$c->setParameter('db.host', 'localhost');
$c->setParameter('db.name', 'myapp');
$c->setParameter('db.user', 'root');
$c->setParameter('db.pass', 'secret');

// --- Shared services ---
$c->set('pdo', function (Container $c): PDO {
    return new PDO(
        sprintf(
            'mysql:host=%s;dbname=%s',
            $c->getParameter('db.host'),
            $c->getParameter('db.name')
        ),
        $c->getParameter('db.user'),
        $c->getParameter('db.pass')
    );
});

$c->set('user_repository', function (Container $c): UserRepository {
    return new UserRepository($c->get('pdo'));
});

$c->set('mailer', function (Container $c): MailerInterface {
    return new SmtpMailer('smtp.example.com', 587);
});

// --- Prototype factories ---
$c->factory('request', fn (Container $c) => Request::fromGlobals());

// --- Usage ---
$repo = $c->get('user_repository');
$users = $repo->findAll();

$mailer = $c->get('mailer');
$mailer->send(new WelcomeEmail($users[0]));

NotFoundException

Fight\Common\Application\Service\Exception\NotFoundException

Extends \Exception and implements Psr\Container\NotFoundExceptionInterface. Thrown when get() is called with a service ID that has not been registered via set() or factory().

use Fight\Common\Application\Service\Exception\NotFoundException;

try {
    $c->get('nonexistent');
} catch (NotFoundException $e) {
    echo $e->getMessage();       // "Service 'nonexistent' not found."
}

Note that getParameter() does not throw — it returns the provided default (or null) for missing keys.