Skip to content

amphp-injector in Flyokai

Flyokai-specific DI configuration patterns and conventions built on flyokai/amphp-injector.

For the core amphp-injector API, see vendor/flyokai/amphp-injector/README.md (and the deeper AGENTS.md).

DI Config File Structure

Each Flyokai module returns an array from its diconfig*.php:

return [
    // Interface → Implementation mappings
    'aliases' => [
        ConnectionPool::class => AsyncMysqliConnectionPool::class,
        AclRoleRepository::class => AclRoleRepositoryImpl::class,
    ],

    // Constructor argument overrides (by parameter name)
    'arguments' => [
        TwoLevelConnectionPool::class => [
            'firstPool' => $service(AsyncMysqliConnectionPool::class),
            'secondPool' => $service(AmpConnectionPool::class),
        ],
        ApplicationLogger::class => [
            'name' => $rootBootstrap->applicationType->buildName($rootBootstrap->name),
            'handlers' => [
                'default' => $streamHandlerDefinition
            ]
        ],
    ],

    // Non-service definitions (prototypes, shared non-service singletons)
    'definitions' => [
        ApplicationLogger::class => singleton(object(ApplicationLogger::class)),
        Controller\Ping::class => object(Controller\Ping::class),
    ],

    // Runtime attribute-based definitions
    'runtimeDefinitions' => [
    ]
];

Build Flow in Flyokai

  1. Module bootstraps call addDiConfigFile() during init phase
  2. Each diconfig file returns the array above
  3. RootBootstrap merges all configs, creates Definitions + Arguments (from weavers)
  4. Application is constructed — calls definition->build(injector) for every definition, registering providers in Container
  5. application->start() walks all providers, starts Lifecycle instances in dependency order
  6. application->stop() stops in reverse order

Services vs Definitions

  • addService(id, definition) — registered as a named service in the container, accessible via $container->get(id)
  • addDefinition(id, definition) — registered in definitions collection, used for wiring but not directly retrievable as a service
  • The $service(...) and $definition(...) closures (from $rootBootstrap->getDiConfig()) reference previously registered services/definitions for cross-wiring

Config File Scoping

Files are loaded per ApplicationType: - diconfig.php — base, always loaded - diconfig_web.php — Web and Worker types - diconfig_cli.php — Cli type - diconfig_cluster.php — Cluster type - diconfig_worker.php — Worker type - diconfig_setup.php — Setup type

Arguments Merging

Arguments from the 'arguments' key in diconfig are merged into existing definitions by matching class/service name. This allows modules to inject their own parameters into services defined by other modules:

// Module A defines:
->addService(RepositoryContainer::class, singleton(object(RepositoryContainer::class)))

// Module A's arguments:
RepositoryContainer::class => [
    'repositories' => [
        AclRoleDto::entityType() => $service(AclRoleRepositoryImpl::class),
    ]
]

// Module B adds to the same composition:
RepositoryContainer::class => [
    'repositories' => [
        UserDto::entityType() => $service(UserRepositoryImpl::class),
    ]
]

Composition Pattern (Ordered Collections)

Used for extensible collections where modules contribute items with ordering:

// 1. Define the composition container
->addDefinition('cli:command:loader', object(
    CommandLoader::class,
    arguments(names()
        ->with('commands', compositionFactory(CompositionOrdered::selfFactory()))
    )
))

// 2. Modules add items via arguments merging
'arguments' => [
    'cli:command:loader' => [
        'commands' => [
            'ping' => compositionItem(object(PingCommand::class)),
            'http:start' => compositionItem(object(HttpStartCommand::class), after: ['ping']),
        ]
    ]
]

Flyokai-Specific Gotchas

  • Init phase has no container: DI config files run during init — the container doesn't exist yet. Use $rootBootstrap methods only, not $container->get().
  • Arguments merging for compositions: Array keys in arguments are merged across modules — use unique keys to avoid overwriting another module's items.
  • mustStart singletons: Used for HTTP server runner — singleton(object(ServerRunnerImpl::class), true). Calling get() before application->start() throws LifecycleException.