Skip to content

[FEATURE] Add Auto-Registration and Dependency Resolution to DI Container in PancakeΒ #237

@guibranco

Description

@guibranco

Description:
I would like to enhance the DI Container in the Pancake project to support auto-registration for services that are not explicitly registered in the container. This auto-registration feature should be enabled by default but configurable via a flag. Additionally, the container should be able to automatically resolve all dependencies, including constructor dependencies, for services that are auto-registered.

Related to #235

Below is an updated code example for the DIContainer class illustrating the requested functionality:

Updated DIContainer Example

<?php

namespace GuiBranco\Pancake;

class DIContainer
{
    private array $services = [];
    private array $sharedInstances = [];
    private bool $autoRegisterEnabled = true;

    public function __construct(bool $autoRegisterEnabled = true)
    {
        $this->autoRegisterEnabled = $autoRegisterEnabled;
    }

    public function register(string $name, callable $resolver, bool $shared = false)
    {
        $this->services[$name] = [
            'resolver' => $resolver,
            'shared' => $shared,
        ];
    }

    public function resolve(string $name)
    {
        if (isset($this->services[$name])) {
            if ($this->services[$name]['shared']) {
                if (!isset($this->sharedInstances[$name])) {
                    $this->sharedInstances[$name] = $this->services[$name]['resolver']($this);
                }
                return $this->sharedInstances[$name];
            }
            return $this->services[$name]['resolver']($this);
        }

        if ($this->autoRegisterEnabled) {
            return $this->autoRegister($name);
        }

        throw new \Exception("Service '{$name}' not registered.");
    }

    private function autoRegister(string $name)
    {
        if (!class_exists($name)) {
            throw new \Exception("Class '{$name}' does not exist.");
        }

        $reflectionClass = new \ReflectionClass($name);
        $constructor = $reflectionClass->getConstructor();

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

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

        foreach ($parameters as $parameter) {
            $parameterClass = $parameter->getType() ? $parameter->getType()->getName() : null;

            if ($parameterClass && class_exists($parameterClass)) {
                $dependencies[] = $this->resolve($parameterClass);
            } else {
                throw new \Exception("Cannot resolve parameter '{$parameter->getName()}' for class '{$name}'.");
            }
        }

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

    public function registerSingleton(string $name, callable $resolver)
    {
        $this->register($name, $resolver, true);
    }

    public function registerTransient(string $name, callable $resolver)
    {
        $this->register($name, $resolver, false);
    }

    public function setAutoRegisterEnabled(bool $enabled)
    {
        $this->autoRegisterEnabled = $enabled;
    }
}

New Features:

  1. Auto-Registration of Services:

    • If a service is not found in the registration array, the DI container will attempt to auto-register it by resolving its constructor dependencies.
    • This auto-registration feature is enabled by default, but can be controlled using the setAutoRegisterEnabled method.
  2. Dependency Resolution:

    • The DI container will automatically resolve constructor dependencies for both explicitly registered and auto-registered services.
    • It will recursively resolve dependencies for services that require other services as constructor arguments.

Example of Auto-Registration:

<?php

// Assume a service with dependencies
class ServiceA {
    public function __construct(ServiceB $serviceB) { }
}

class ServiceB {
    public function __construct(ServiceC $serviceC) { }
}

class ServiceC {
    public function __construct() { }
}

$container = new \GuiBranco\PocMvc\Src\Core\DIContainer();

// Automatically registers and resolves ServiceA, along with its dependencies (ServiceB, ServiceC)
$serviceA = $container->resolve(ServiceA::class);

Task Requirements:

  • Add auto-registration functionality to the DI Container with support for resolving dependencies via reflection.
  • Include a flag to enable/disable auto-registration (enabled by default).
  • Ensure that both registered and auto-registered services have their dependencies resolved recursively.

Additional Requirements:

  • Provide unit tests to verify:
    • Auto-registration of services when not explicitly registered.
    • Dependency resolution, including recursive dependencies.
    • Correct behavior when auto-registration is disabled.
  • Include integration tests to validate the new functionality in practical use cases.

Acceptance Criteria:

  • The DI container supports auto-registration and dependency resolution.
  • Services with complex dependencies can be automatically resolved.
  • Unit and integration tests are provided.
  • Documentation is updated with usage examples for both manual and auto-registration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestgitautoGitAuto label to trigger the app in a issue.♻️ code qualityCode quality-related tasks or issuesπŸ“ documentationTasks related to writing or updating documentationπŸ•” high effortA task that can be completed in a few daysπŸ›  WIPWork in progressπŸ§‘β€πŸ’» tech-debitTechnical debt that needs to be addressed🧠 backlogItems that are in the backlog for future workπŸ§ͺ testsTasks related to testing

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions