mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-18 12:54:52 +08:00
196 lines
5.5 KiB
PHP
196 lines
5.5 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace StaticPHP\DI;
|
||
|
|
|
||
|
|
use DI\Container;
|
||
|
|
use DI\ContainerBuilder;
|
||
|
|
use Psr\Container\ContainerInterface;
|
||
|
|
use Symfony\Component\Console\Input\InputInterface;
|
||
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
||
|
|
|
||
|
|
use function DI\factory;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* ApplicationContext manages the DI container lifecycle and provides
|
||
|
|
* a centralized access point for dependency injection.
|
||
|
|
*
|
||
|
|
* This replaces the scattered spc_container()->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<T> $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),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|