set() calls throughout the codebase. */ class ApplicationContext { private static ?Container $container = null; private static ?CallbackInvoker $invoker = null; private static bool $debug = false; /** * Initialize the container with configuration. * Should only be called once at application startup. * * @param array $options Initialization options * - 'debug': Enable debug mode (disables compilation) * - 'definitions': Additional container definitions * * @throws \RuntimeException If already initialized */ public static function initialize(array $options = []): Container { if (self::$container !== null) { throw new \RuntimeException('ApplicationContext already initialized. Use reset() first if you need to reinitialize.'); } $builder = new ContainerBuilder(); $builder->useAutowiring(true); $builder->useAttributes(true); // Load default definitions self::configureDefaults($builder); // Add custom definitions if provided if (isset($options['definitions']) && is_array($options['definitions'])) { $builder->addDefinitions($options['definitions']); } // Set debug mode self::$debug = $options['debug'] ?? false; self::$container = $builder->build(); self::$invoker = new CallbackInvoker(self::$container); return self::$container; } /** * Get the container instance. * If not initialized, initializes with default configuration. */ public static function getContainer(): Container { if (self::$container === null) { self::initialize(); } return self::$container; } /** * Get a service from the container. * * @template T * * @param class-string $id Service identifier * * @return T */ public static function get(string $id): mixed { return self::getContainer()->get($id); } /** * Check if a service exists in the container. */ public static function has(string $id): bool { return self::getContainer()->has($id); } /** * Set a service in the container. * Use sparingly - prefer configuration-based definitions. */ public static function set(string $id, mixed $value): void { self::getContainer()->set($id, $value); } /** * Bind command-line context to the container. * Called at the start of each command execution. */ public static function bindCommandContext(InputInterface $input, OutputInterface $output): void { $container = self::getContainer(); $container->set(InputInterface::class, $input); $container->set(OutputInterface::class, $output); self::$debug = $output->isDebug(); } /** * Get the callback invoker instance. */ public static function getInvoker(): CallbackInvoker { if (self::$invoker === null) { self::$invoker = new CallbackInvoker(self::getContainer()); } return self::$invoker; } /** * Invoke a callback with automatic dependency injection and context. * * @param callable $callback The callback to invoke * @param array $context Context parameters for injection */ public static function invoke(callable $callback, array $context = []): mixed { logger()->debug('[INVOKE] ' . (is_array($callback) ? (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]) . '::' . $callback[1] : (is_string($callback) ? $callback : 'Closure'))); return self::getInvoker()->invoke($callback, $context); } /** * Check if debug mode is enabled. */ public static function isDebug(): bool { return self::$debug; } /** * Set debug mode. */ public static function setDebug(bool $debug): void { self::$debug = $debug; } /** * Reset the container. * Primarily used for testing to ensure isolation between tests. */ public static function reset(): void { self::$container = null; self::$invoker = null; self::$debug = false; } /** * Configure default container definitions. */ private static function configureDefaults(ContainerBuilder $builder): void { $builder->addDefinitions([ // Self-reference for container ContainerInterface::class => factory(function (Container $c) { return $c; }), Container::class => factory(function (Container $c) { return $c; }), // CallbackInvoker is created separately to avoid circular dependency CallbackInvoker::class => factory(function (Container $c) { return new CallbackInvoker($c); }), // Command context (set at runtime via bindCommandContext) InputInterface::class => \DI\value(null), OutputInterface::class => \DI\value(null), ]); } }