Container.php

Code Hygiene Score: 100

Keine Issues gefunden.

Dependencies 3

Klassen 1

Funktionen 6

Verwendet von 6

Versionen 3

Code

<?php

declare(strict_types=1);

namespace Framework;

// @responsibility: DI-Container mit Autowiring und Singleton-Caching

use ReflectionClass;
use ReflectionNamedType;
use ReflectionParameter;

final class Container
{
    /** @var array<string, object> Cached singleton instances */
    private array $instances = [];

    /** @var array<string, callable> Factory functions */
    private array $factories = [];

    /**
     * Register a factory for a service.
     *
     * @param string $id Service identifier (usually class name)
     * @param callable(Container): object $factory Factory function
     */
    public function set(string $id, callable $factory): void
    {
        $this->factories[$id] = $factory;
        // Clear cached instance if re-registering
        unset($this->instances[$id]);
    }

    /**
     * Register an existing instance as a singleton.
     *
     * @param string $id Service identifier
     * @param object $instance The instance to register
     */
    public function instance(string $id, object $instance): void
    {
        $this->instances[$id] = $instance;
    }

    /**
     * Check if a service is registered.
     */
    public function has(string $id): bool
    {
        return isset($this->instances[$id]) || isset($this->factories[$id]) || class_exists($id);
    }

    /**
     * Get a service by ID.
     *
     * Resolution order:
     * 1. Check cached instances
     * 2. Check registered factories
     * 3. Try autowiring if ID is a class name
     *
     * @param string $id Service identifier (usually class name)
     * @return object The resolved service instance
     * @throws ContainerException If service cannot be resolved
     */
    public function get(string $id): object
    {
        // Return cached instance if available
        if (isset($this->instances[$id])) {
            return $this->instances[$id];
        }

        // Use factory if registered
        if (isset($this->factories[$id])) {
            $this->instances[$id] = ($this->factories[$id])($this);

            return $this->instances[$id];
        }

        // Try autowiring for class names
        if (class_exists($id)) {
            $this->instances[$id] = $this->autowire($id);

            return $this->instances[$id];
        }

        throw new ContainerException("Service not found: {$id}");
    }

    /**
     * Autowire a class by resolving constructor dependencies.
     *
     * @param class-string $class
     * @throws ContainerException
     */
    private function autowire(string $class): object
    {
        $reflection = new ReflectionClass($class);

        if (!$reflection->isInstantiable()) {
            throw new ContainerException("Cannot instantiate {$class}");
        }

        $constructor = $reflection->getConstructor();

        // No constructor = no dependencies
        if ($constructor === null) {
            return new $class();
        }

        $parameters = $constructor->getParameters();
        $dependencies = [];

        foreach ($parameters as $param) {
            $dependencies[] = $this->resolveParameter($param, $class);
        }

        return $reflection->newInstanceArgs($dependencies);
    }

    /**
     * Resolve a single constructor parameter.
     *
     * @throws ContainerException
     */
    private function resolveParameter(ReflectionParameter $param, string $class): mixed
    {
        $type = $param->getType();

        // Handle nullable/optional parameters with default values
        if ($param->isDefaultValueAvailable()) {
            // Try to resolve the type, but fall back to default if it fails
            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
                $typeName = $type->getName();
                // Only try to resolve if explicitly registered (not just class_exists)
                if (isset($this->instances[$typeName]) || isset($this->factories[$typeName])) {
                    try {
                        return $this->get($typeName);
                    } catch (ContainerException) {
                        // Fall through to default value
                    }
                }
            }

            return $param->getDefaultValue();
        }

        // No type hint
        if ($type === null) {
            throw new ContainerException(
                "Cannot resolve parameter \${$param->getName()} in {$class}: no type hint"
            );
        }

        // Union types not supported
        if (!$type instanceof ReflectionNamedType) {
            throw new ContainerException(
                "Cannot resolve parameter \${$param->getName()} in {$class}: union types not supported"
            );
        }

        // Built-in types (string, int, etc.) cannot be autowired
        if ($type->isBuiltin()) {
            if ($type->allowsNull()) {
                return null;
            }

            throw new ContainerException(
                "Cannot resolve parameter \${$param->getName()} in {$class}: built-in type {$type->getName()}"
            );
        }

        // Resolve the dependency
        return $this->get($type->getName());
    }
}
← Übersicht Graph