Attribute Injection#

InterMix supports PHP 8+ native attributes for expressive, declarative injection. Two families exist:

  • Built-in tags shipped with InterMix (Infuse / Autowire / Inject)

  • Custom attributes you register at runtime through Infocyph\InterMix\DI\Attribute\AttributeRegistry::register

Built-in Tags (Infuse / Autowire / Inject)#

  • #[Infuse] – canonical

  • #[Autowire] – Spring-style alias

  • #[Inject] – common DI alias

  • #[DeferredInitializer] – marks classes for lazy initialization

They are identical and can inject via :

  • Type-hint (class / interface)

  • Container key ('cfg.debug', 'db.host', …)

  • Global callable (e.g. #[Inject(strtotime: 'next monday')])

Quick syntax#

use Infocyph\InterMix\DI\Attribute\{Infuse, Autowire, Inject};

class Service {
    #[Infuse]                        private LoggerInterface $logger; // by type
    #[Autowire('cfg.debug')]         private bool $debug;             // by key
    #[Inject(strtotime: '+1 day')]   private int  $expires;           // via function
}

class App {
    #[Infuse(user: 'admin')]       // method-level default
    public function boot(
        #[Inject('cfg.env')] string $env   // parameter-level override
    ) {}
}

Custom Attribute Support#

Create any attribute & a resolver that implements Infocyph\InterMix\DI\Attribute\AttributeResolverInterface.

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class UpperCase {
    public function __construct(public string $text) {}
}

use Infocyph\InterMix\DI\Attribute\AttributeResolverInterface;

class UpperCaseResolver implements AttributeResolverInterface {
    public function resolve(
        object     $attribute,
        Reflector  $target,
        Container  $c
    ): mixed {
        return strtoupper($attribute->text);
    }
}

// Register once during bootstrap
$c->attributeRegistry()->register(
    UpperCase::class,
    UpperCaseResolver::class
);

Usage:

class Banner {
    #[UpperCase('hello world')]
    public string $title;
}

echo $c->get(Banner::class)->title;    // HELLO WORLD

Hint

Multiple attributes may decorate the same target. InterMix calls each registered resolver in discovery order; the first non-null, non-IMStdClass result becomes the injected value. Later resolvers can still run side-effect logic even if they don’t inject.

Method & Parameter Injection#

class Mailer {
    public function send(
        #[Infuse('cfg.smtp')] array $config,
        #[Inject]  LoggerInterface $log
    ) {}
}

Whole-method defaults:

class Worker {
    #[Autowire(retries: 2, delay: 5)]
    public function execute(int $retries, int $delay) {}
}

Arguments provided via Infocyph\InterMix\DI\Container::call, Infocyph\InterMix\DI\Managers\RegistrationManager::registerMethod or explicit arrays always override attributes.

Property Injection#

Enable with propertyAttributes: true:

class Controller {
    #[Infuse]        private Request $request;          // by type
    #[Autowire('cfg.csrf')] private string $csrf;       // by key
    #[UpperCase('admin')]  private string $role;        // custom
}

Properties are injected after construction. Values set via Infocyph\InterMix\DI\Managers\RegistrationManager::registerProperty win over attributes.

Resolution Workflow#

  1. Built-in tag (Infuse / Autowire / Inject) – first match wins

  2. Custom attributes – executed in registration order:

    • if a resolver returns non-null & not ``IMStdClass`` → injected

    • if resolver returns null or IMStdClass → treated as “logic-only”

Enabling Attributes#

$c->options()->setOptions(
    injection:           true,
    methodAttributes:    true,   // enable #[Infuse] on params / methods
    propertyAttributes:  true    // enable #[Infuse] on properties
);

You may enable only one flag to limit scope.

Resolution Priority (high → low)#

  1. registerClass() / registerMethod() / registerProperty()

  2. Supplied args (call(), make(), etc.)

  3. definitions() map

  4. Built-in tags (Infuse / Autowire / Inject)

  5. Custom attributes via AttributeRegistry

Examples#

Inject scalar config:

class Analytics {
    #[Inject('cfg.api_key')] private string $apiKey;
}

Global callable:

class Session {
    #[Infuse('uuid_create')] private string $sessionId;
}

Logic-only attribute (no injection):

#[Attribute(Attribute::TARGET_METHOD)]
class LogCall {
    public function __construct(public string $level = 'info') {}
}

class LogCallResolver implements AttributeResolverInterface {
    public function resolve(object $attr, Reflector $target, Container $c): mixed {
        $c->get('logger')->log($attr->level, "[DI] $target handled");
        return null;       // no injection, marks as handled
    }
}

Debugging#

$c->options()->enableDebugTracing(true);
$c->get(MyService::class);
print_r($c->debug(MyService::class));

Summary#

  • Built-in tags: Infuse / Autowire / Inject

  • Register unlimited custom attributes with resolvers

  • Works on properties, parameters, or whole methods

  • First non-null result wins; others may perform side-effects only

  • Fully traceable with enableDebugTracing()

Next → Service Lifetimes