{"id":20315994,"url":"https://github.com/edmonddantes/di","last_synced_at":"2025-07-24T22:06:42.765Z","repository":{"id":248335498,"uuid":"828414044","full_name":"EdmondDantes/di","owner":"EdmondDantes","description":"Dependency Injection (DI) is a lightweight PHP library for dependency injection for stateful application for PHP 8.4.","archived":false,"fork":false,"pushed_at":"2024-11-24T07:31:58.000Z","size":203,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-17T13:54:47.949Z","etag":null,"topics":["dependency-injection","php84","stateful"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EdmondDantes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-14T04:34:38.000Z","updated_at":"2024-11-24T07:31:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"cbf3a13b-a247-4bdd-bf82-55ac77e2d4e9","html_url":"https://github.com/EdmondDantes/di","commit_stats":null,"previous_names":["edmonddantes/di"],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/EdmondDantes/di","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdmondDantes%2Fdi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdmondDantes%2Fdi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdmondDantes%2Fdi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdmondDantes%2Fdi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EdmondDantes","download_url":"https://codeload.github.com/EdmondDantes/di/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdmondDantes%2Fdi/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266161919,"owners_count":23885940,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","php84","stateful"],"created_at":"2024-11-14T18:23:34.785Z","updated_at":"2025-07-24T22:06:42.715Z","avatar_url":"https://github.com/EdmondDantes.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dependency Injection [![PHP Composer](https://github.com/EdmondDantes/di/actions/workflows/php.yml/badge.svg)](https://github.com/EdmondDantes/di/actions/workflows/php.yml)\n\n`Dependency Injection` (DI) is a lightweight **extensible** `PHP` library for dependency injection\nfor `stateful` application.\n\nThe library is designed for `PHP 8.4` using the `LazyProxy API`.\n\n### Features\n\n* [Zero configuration](#zero-configuration-principle).\n  (*Ability to inject dependencies without modifying the code in the dependent class*).\n* [Support for the concept of `environment`/`scope`](#environment-and-scope-concepts) for dependency lookup. \n* [Support parent/child containers](#container-inheritance).\n* [Constructor injection](#initialization-through-a-constructor) of dependencies\n* [Injection of dependencies into properties](#initialization-through-a-method)\n* Injecting configuration values as a Dependency\n* [Lazy loading](#lazy-loading) of dependencies\n* [Auto dereferencing a `WeakReference` inside the container](#dereferencing-a-weakreference)\n* [Handling circular dependencies](#circular-dependencies)\n* [Support php-attributes for describing dependencies](#special-attributes)\n* [Custom dependency providers](#custom-attributes-and-providers)\n* [Custom descriptor providers](#descriptor-provider)\n\n### Installation\n\nYou can install Dependency Injector using Composer. Run the following command:\n\n```bash\ncomposer require ifcastle/di\n```\n\n### Basic Usage\n\n\u003e Please read the [Project Philosophy](#project-philosophy) section before using the library.\n\nThe example below demonstrates how the library works with the `SomeClass` class, \nwhich implements the SomeInterface interface.\n\nThe class definition does not depend on the `Dependency Injection` implementation. \nDependencies are injected via the class `constructor`.\n\nThe library automatically binds dependencies to their interfaces.\n\n```php\ndeclare(strict_types=1);\n\nuse IfCastle\\DI\\ContainerBuilder;\nuse IfCastle\\DI\\Lazy;\n\nreadonly class SomeClass implements SomeInterface\n{\n    public function __construct(\n        // Required dependency\n        private SomeRequiredInterface $required,\n        // Optional dependency (can be null)\n        private SomeOptionalInterface $optional = null,\n        // Support a complex dependency type\n        private Interface1|Interface2 $someElseUnion,\n        // Support a complex dependency type with interception\n        private Interface1\u0026Interface2 $someElseInterception,\n        // Dependency as configuration value\n        private int $configValue = 42,        \n    ) {}\n}\n\n// 1. Create a container builder\n\n$builder                    = new ContainerBuilder();\n// 2. Define the constructible dependencies\n$builder-\u003ebindConstructible(SomeInterface::class, SomeClass::class);\n// 2. Bind several interfaces-aliases or string-key to one class\n$builder-\u003ebindConstructible([Interface1::class, Interface2::class, 'string-key'], SomeElseClass::class);\n// 2. Support WeakReference dereferencing\n$builder-\u003ebindObject(SomeOptionalInterface::class, WeakReference::create($someObject));\n// 2. Define the configuration values\n$builder-\u003eset('configValue', 42);\n\n// 3. Build the container\n$container                  = $builder-\u003ebuildContainer(new Resolver());\n\n// 4. Get the dependency\n$some                       = $container-\u003eresolveDependency(SomeInterface::class);\n\n```\n\n### Special Attributes\n\nAttributes provide a more precise way to describe features for dependency resolution.\nThis library supports several attributes that can be used:\n\n * `Dependency`     - a general descriptor for a dependency.\n * `FromConfig`     - indicates that the dependency should be retrieved from the configuration.\n * `FromRegistry`   - indicates that the dependency should be retrieved from the registry.\n\n```php\n\nuse IfCastle\\DI\\Dependency;\nuse IfCastle\\DI\\FromConfig;\n\nreadonly class SomeClass implements SomeInterface\n{\n    public function __construct(\n        #[Dependency(key: SomeRequiredInterface::class)]\n        private mixed $required,\n        #[Dependency(isLazy: true)]\n        private SomeOptionalInterface $optional = null\n        #[FromConfig('someClass.configValue')]\n        private int $configValue = 0\n    ) {}\n}\n\n\n```\n\n### Lazy Loading\n\nThe library supports lazy loading of dependencies by special attribute:\n\n```php\nuse IfCastle\\DI\\Lazy;\n\nreadonly class SomeClass implements SomeInterface\n{\n    public function __construct(\n        #[Lazy] private SomeRequiredInterface $lazy,\n    ) {}\n}\n```\n\n\u003e **Warning**: Lazy dependencies are implemented using the PHP `LazyProxy` API, \n\u003e so the same dependencies in different classes will be **different objects**!\n\u003e \n\u003e This means the `===` operation will return `false`, \n\u003e and `spl_object_id()` will return different values.\n\n\n### Circular Dependencies\n\nThe library allows resolving circular dependencies if the dependency is not used during resolution.\nIf a circular dependency occurs, the library will create a `LazyProxy` object and return it.\n\n\u003e **Warning**: Lazy or circular dependencies cannot be used **BEFORE** \n\u003e the process of resolving all dependencies is completed!\n\u003e ```php\n\u003e readonly class SomeClass implements SomeInterface\n\u003e {\n\u003e    public function __construct(\n\u003e    #[Lazy] private SomeRequiredInterface $lazy,\n\u003e    ) {\n\u003e       $lazy-\u003esomeMethod(); // Error: CircularDependencyException\n\u003e   }\n\u003e }\n\u003e ```\n\n### Custom attributes and Providers\n\nYou can create your own attributes to describe dependencies by implementing the DescriptorInterface.\n\nTo define a custom algorithm for dependency resolution, \n`DescriptorInterface` can implement the `getProvider` method, \nwhich returns the dependency.\n\nBelow is an example of a method that retrieves a value from the configuration:\n\n```php\n    #[\\Override]\n    public function provide(\n        ContainerInterface  $container,\n        DescriptorInterface $descriptor,\n        ?DependencyInterface $forDependency = null,\n        array $resolvingKeys = []\n    ): mixed {\n        $config                     = $container-\u003efindDependency(ConfigInterface::class);\n\n        if ($config === null) {\n            return null;\n        }\n\n        if ($config instanceof ConfigInterface === false) {\n            throw new \\TypeError('Config is not an instance of ' . ConfigInterface::class);\n        }\n\n        return $config-\u003efindValue($this-\u003egetKey());\n    }\n\n```\nThis way, you can extend the `DI` logic without modifying the library's code.\n\n## Project Philosophy\n\nThis project implements a **particular algorithm** for dependency management, \nadhering to the following rule:\n\n\u003e If **Class A** requires a dependency by the contract **Interface**, \n\u003e and **Class B** also requires a dependency **Interface**, \n\u003e both classes will receive the same dependency **Class D** within a single **runtime environment**.\n\n**The following statements are true:**\n\n* The mapping scheme between `contracts` (`interfaces`) and `dependencies` is called the `Runtime Environment`.\n* The mapping scheme is shared across all dependencies.\n* A single `runtime environment` cannot have two different dependencies linked to the same contract (interface).\n* A single `dependency` can be associated with `multiple contracts`.\n* An application can have multiple `runtime environments` simultaneously, \neach with its own mapping schemes and `dependencies`.\n* Two `runtime environments` can have a relationship: **Parent -\u003e Child**.\n\n### Comparison of IfCastle DI vs. Symfony and other DI implementations\n\n| **Aspect**                              | **IfCastle DI**                                                                               | **Symfony**                                                                                 |\n|-----------------------------------------|-----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|\n| **Dependency Resolution**               | Single container for all dependencies in one runtime environment (`Service Locator` approach) | Each service explicitly defines its dependencies, configured individually                   |\n| **Same Contract Handling**              | One contract always linked to one dependency in a runtime environment                         | Allows different implementations of the same interface through qualifiers, tags, or aliases |\n| **Runtime Environments**                | Supports multiple runtime environments with hierarchical relationships (**Parent -\u003e Child**)  | Global container; separate environments typically require separate configurations           |\n| **Different Dependencies for Same Key** | Not possible within the same runtime environment                                              | Fully supported using tags, aliases, or contextual bindings                                 |\n| **Approach to Dependency Management**   | Service Locator-like: dependencies resolved from a shared container                           | Strict DI Container approach: dependencies injected explicitly                              |\n| **Parent-Child Relationship**           | Direct support for hierarchical environments with inheritance                                 | No direct concept of parent-child environments; containers are isolated                     |\n\nThe approach of this library has **several benefits**:\n\n* Minimal code volume due to significant simplification of logic.\n* The ability to inject dependencies into a class that has not been preconfigured, \nprovided the class has access to the `Runtime Environment`.\n* The dependency container is created **once** at the application startup (`Bootloader Strategy`) \nand can be reused by various components without prior configuration.\n* This project does not mention **AutoWire** because dependencies are already linked \nbased on interface names, and this method is the *PRIMARY* approach.\n\n## Zero configuration principle\n\nThe task of dependency resolution is typically addressed by separating the information \nabout how to locate dependencies from the objects that require them. For example:\n\n* There is a `Target` class.\n* The Target class has an array of dependencies.\n* Define a `TargetDependencies` class, responsible for resolving these dependencies.\n\nDoes this approach meet the SOLID principles?\nThe `TargetDependencies` class must duplicate *knowledge* about the required dependencies \nof the `Target` class.\nHowever, the `Target` class is the single source of truth.\n\nAnother drawback of this solution is the increased code volume and complexity. \nThe developer must now remember two points of definition related to initialization.\n\nFor this reason, modern DI approaches combine metadata for dependency resolution \ndirectly within the Target class.\n\nTo store metadata, attributes are used, which are directly placed in the `Target` class.\nNow the knowledge about how to resolve dependencies flows into the `Target` class, \nbinding it to the DI implementation.\n\nThis coupling results in components requiring a specific library for dependency resolution \nand being unable to function independently. In the absence of a standard, this hinders code reusability.\n\nThe `Zero configuration principle` suggests avoiding the use of metadata within the `Target` class, \nmaking it independent of any specific DI implementation.\n\nTo reduce the coupling of `Target` classes, \nour library proposes moving dependency metadata knowledge to the **contract domain**. \nIn other words, information about how to resolve dependencies can be stored \nin an interface rather than in the implementation. \nThis keeps dependency definitions as transparent as possible.\n\nExample:\n\n```php\ndeclare(strict_types=1);\n\nuse IfCastle\\DI\\DependencyContract;\n\n#[DependencyContract(new DependencyProvider())]\ninterface InterfaceWithDependencyContact\n{\n    public function someMethod(): void;\n}\n\nfinal readonly class ClassWithDependencyContact\n{\n    public function __construct(\n        private InterfaceWithDependencyContact $some\n    ) {}\n}\n\n```\n\nIn this case, the `ClassWithDependencyContact` class is a `Target` class,\nand the `InterfaceWithDependencyContact` interface is a `Contract`.\n\nUsing the `DependencyContract` attribute, \nthe interface specifies how the dependency should be resolved. \nThis solution is also not ideal, but in many cases, it results in cleaner code.\n\nSee more: [Complex use cases](#complex-use-cases)\n\n## Performance considerations\n\nThe library does not include any compilers for `dependency descriptors`, \nalthough their implementation is possible. \nAll dependency descriptors are resolved dynamically at application startup using the `Reflection API`. \n\nIs this a performance issue? \nYes, if you are using `PHP` in a stateless mode, where the PHP process terminates after each request.\n\nFor **stateful applications**, dependency resolution occurs once during the application's `warm-up phase` \nor `on-demand`, which aligns with the purpose of this library.\n\n## Architecture\n\n![Architecture](docs/images/components.svg)\n\nThe library consists of four core **components** that interact with each other:\n\n* `Container` — a dependency container. A key-value object that stores dependencies, where the key is typically the full name of an interface, and the value is a scalar value, a dependency descriptor, or an already initialized object.\n* `Builder` — a container builder that constructs the container based on the specified dependencies.\n* `Resolver` — a strategy that directly resolves dependencies.\n* `Dependency` — an object that describes a dependency.\n\nAll these components are interchangeable, and by modifying them, you can change the behavior.\n\n### Container\n\nThe dependency container follows the `ServiceLocator`/`Environment` **pattern**. \nThis means that when attempting to resolve a dependency, the dependencies required by it will also be retrieved \nfrom the same container. \n\nIn other words, \n\n\u003e All dependencies in the `container` share the `container` as a common execution `environment`.\n\nYou can leverage this fact to create multiple execution environments, each holding its unique dependencies. \nThis allows you to implement `Scope logic`, where dependency initialization depends on the environment.\n\nBy default, the container is an **immutable object** in terms of associating keys with dependencies. \nHowever, the container's values change during execution, as dependencies are initialized on their first use, \nreplacing dependency descriptors with the actual value.\n\nThis container behavior ensures that dependencies are single instances, meaning they are the same object.\n\n#### Container Inheritance\n\nTo provide developers with a powerful tool for managing dependencies, \nthe `container` supports inheritance based on the override principle. \nThis means you can create two separate containers \nwith different sets of dependencies and then link them as **PARENT** and **CHILD** containers.\n\n```php\n\nuse IfCastle\\DI\\Container;\n\n$parent = new Container(new Resolver(), ['dependency1' =\u003e 'value1']);\n$child = new Container(new Resolver(), ['dependency1' =\u003e 'value2'], $parent);\n\necho $child-\u003eresolveDependency('dependency1'); // value2\n\n```\n\nIn this case, an attempt to resolve a dependency in the child container will result in the following behavior: \n* if the dependency is not found in the child container, the search will continue in the parent container. \n* however, if the child container has a definition for the dependency, it will be used.\n\n#### Dereferencing a WeakReference.\n\nThe container supports **dereferencing weak references** \nif they are detected as a value. \n\nUsing weak references is typically useful \nwhen defining multiple aliases for dependencies or a reference to the container itself. \nIn such cases, weak references help avoid additional work for the garbage collector and prevent memory leaks. \n\n#### Initializer\n\nSometimes, it is necessary to initialize a dependency not directly through the constructor but with additional code. \nFor example, loading a database driver based on configuration or context.\n\nTo solve this problem, an initializer can be used: \na special object executed only once at the moment of dependency resolution.\n\nTo implement this approach, the library uses the `InitializerInterface`. \nIf the container holds a value of type `InitializerInterface`, the container uses the `executeInitializer` method \nto obtain the dependency.\n\nSee [SelfReferenceInitializer](./src/SelfReferenceInitializer.php) as an example of an initializer \nthat resolves a dependency from the container.\n\n#### Throwable values\n\nContainer values can be `exception` objects (`Throwable`).\nWhen attempting to resolve a dependency whose value is an exception object, \nthat exception will be thrown.\n\nThis applies to both the `resolveDependency` method and the `findDependency` method. \nHowever, you can change this behavior by specifying the `$returnThrowable` flag \nfor the `findDependency`.\n\nThus, \n\n\u003e if a dependency was resolved **with an error once**, any later attempt \n\u003e to retrieve this dependency will result in **the same error** without \n\u003e attempting to resolve it again.\n\n## Builder\n\nThe **container builder** is responsible for constructing the container based on the specified dependencies.\n\nThe current implementation uses `PHP Reflection` \nto build the container and supports the following types of dependencies:\n\n* Dependency initialized through a constructor.\n* Dependency initialized through a specific method.\n* Dependency initialized via an Initializer or a closure.\n* Dependency with a ready-to-use object that does not require resolution.\n* Constant value.\n\n```php\n\nuse IfCastle\\DI\\ContainerBuilder;\nuse IfCastle\\DI\\ContainerInterface;\n\n$builder                    = new ContainerBuilder();\n// Constructible dependencies\n$builder-\u003ebindConstructible(SomeInterface::class, SomeClass::class);\n// Injectable dependencies\n$builder-\u003ebindInjectable(SomeRequiredInterface::class, SomeRequiredClass::class);\n// Bind to Initializer \n$builder-\u003ebindInitializer(SomeOptionalInterface::class, static function (?ContainerInterface $container = null, array $resolvingKeys = []) {\n    return $container-\u003eresolveDependency(SomeOptionalInterface::class, resolvingKeys: $resolvingKeys);\n});\n// Bind to Object\n$builder-\u003ebindObject('stdClass', new stdClass());\n\n// 3. Build the container\n$container                  = $builder-\u003ebuildContainer(new Resolver());\n```\n\nThe builder creates a dependency descriptor where the key for the dependency is the data type. \nIf a parameter has a default value, the dependency is marked as optional. \nIf there is no default value, the dependency is marked as required.\n\nA dependency can have a complex data type defined through PHP `UNION` or `INTERSECTION` expressions.\nIn this case, the container will look for the dependency using multiple type keys.\n\n#### Descriptor Provider\n\nYou can override the behavior of the `Builder` by implementing the `DescriptorProviderInterface`.\nThere are two ways to do this:\n\n* Implement the `DescriptorInterface::getDescriptorProvider()` method in a custom attribute.\n\n```php\n\nuse Attribute;\nuse IfCastle\\DI\\Dependency;\nuse IfCastle\\DI\\DescriptorInterface;\nuse IfCastle\\DI\\DescriptorProviderInterface;\n\n#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]\nfinal class CustomDescriptor extends Dependency implements DescriptorProviderInterface\n{\n    #[\\Override]\n    public function getDescriptorProvider(): DescriptorProviderInterface|null\n    {\n        return $this;\n    }\n\n    #[\\Override]\n    public function provideDescriptor(\n        DescriptorInterface $descriptor,\n        \\ReflectionClass $reflectionClass,\n        \\ReflectionParameter|\\ReflectionProperty $reflectionTarget,\n        object|string $object,\n    ): DescriptorInterface {\n        // Custom logic here\n    }\n}\n\n```\n\n* Use `DependencyContract` on Interface.\n\n```php\n\nuse IfCastle\\DI\\DependencyContract;\n\n// Define custom descriptor provider for the interface\n#[DependencyContract(descriptorProvider: new DescriptorProvider())]\ninterface InterfaceWithDependencyContact\n{\n    public function someMethod(): void;\n}\n\nfinal readonly class ClassWithDependencyContact\n{\n    public function __construct(\n        private InterfaceWithDependencyContact $some\n    ) {}\n}\n\n```\n\n#### DependencyContract lookup logic\n\nThe `DependencyContract` attribute is inherited according to specific rules:\n\n1. First, `DependencyContract` is checked in the current class or interface.\n2. If it is not found, `DependencyContract` is checked in the **FIRST inherited interface** of the class.\n3. If it is still not found, `DependencyContract` is checked further across all first descendants of the interfaces.\n4. If it is not found there either, `DependencyContract` is checked for the child class, and the algorithm loops back.\n\nIn other words, the first interface has priority in the DependencyContract inheritance logic.\n\nThis inheritance algorithm is chosen to reduce the complexity for developers when searching for the `DependencyContract`.\n\n### Initialization through a constructor\n\nInitialization through the constructor assumes that all dependencies are defined as constructor parameters. \nIn this case, the builder uses the PHP Reflection API to read the constructor's parameter list and their data types.\n\n### Initialization through a method\n\nFor **method-based injection**, a special interface `InjectableInterface` and a trait `InjectorTrait` are used, \nwhich implement the injection method.\n\nIn this case, dependencies are described using attributes above the class properties. \nThe class can be constructed before the dependencies are resolved.\n\n```php\nuse IfCastle\\DI\\Dependency;\nuse IfCastle\\DI\\InjectableInterface;\nuse IfCastle\\DI\\InjectorTrait;\nuse IfCastle\\DI\\Lazy;\n\nfinal class InjectableClass implements InjectableInterface\n{\n    use InjectorTrait;\n\n    #[Dependency]\n    protected UseConstructorInterface $required;\n\n    #[Dependency]\n    protected UseConstructorInterface|null $optional;\n\n    #[Dependency] #[Lazy]\n    protected UseConstructorInterface $lazy;\n\n    protected string $data = '';\n}\n\n```\n\n### Resolver\n\nThe Resolver component is responsible for the dependency resolution process. \nIt handles finding dependencies of a dependency and initializing the dependency's class.\n\n## Complex use cases\n\nLet's consider a realistic example of using the library. \nOur goal is to develop a telemetry component (Prometheus-like) that allows defining counters as dependencies.\n\n```php\ndeclare(strict_types=1);\n\nclass DataBase \n{\n    public function __construct(\n        private string $dsn,\n        private string $user,\n        private string $password,\n        private TelemetryCounterInterface $queryCounter,\n        private TelemetryCounterInterface $errorCounter,\n    ) {}\n}\n\n```\n\nIn the example above, we define two dependencies of type `TelemetryCounterInterface`. \nTelemetryCounterInterface is a telemetry counter contract that increments over time.\n\nThe initialization of such a counter is performed in two stages:\n\n```php\n// 1. Look up the registry\n$registry   = $container-\u003eresolveDependency(TelemetryRegistryInterface::class);\n// 2. Register the counter\n$counter    = $registry-\u003egetOrRegisterCounter('test', 'some_counter', 'it increases', ['type']);\n```\n\nLet's define the `TelemetryCounterInterface` interface:\n\n```php\nclass TelemetryProvider implements \\IfCastle\\DI\\ProviderInterface\n{\n    public function provide(\n        ContainerInterface $container,\n        DescriptorInterface $descriptor,\n        ?DependencyInterface $forDependency = null,\n        array $resolvingKeys = [],\n    ): mixed\n    {\n        // Here, you can use the ReflectionAPI for more complex logic\n        // to determine counter-attributes\n        // based on knowledge about the class where they need to be injected.\n        $forClass   = $forDependency-\u003egetDependencyName() ?? 'global';        \n        $registry   = $container-\u003eresolveDependency(TelemetryRegistryInterface::class);\n        return $registry-\u003egetOrRegisterCounter($forClass.'_'.$descriptor-\u003egetDependencyProperty());\n    }\n}\n\n#[\\IfCastle\\DI\\DependencyContract(new TelemetryProvider())]\ninterface TelemetryCounterInterface  \n{\n    public function inc(): void;\n}\n\n```\n\nNow we can define telemetry counters in `Target` classes, fully hiding their implementation \ndetails as well as the method of dependency resolution from the `Target` object.\n\n## Environment and Scope concepts\n\nThe library's design is oriented towards usage following the `Environment` and `Scope` paradigm. \nThe `Container` component acts as the `environment`, \ndefining and limiting the set of dependencies that will be interconnected.\n\nAn application can define multiple containers, managing which dependencies will be applied in each case. \nUsing the override mechanism, containers can inherit or override each other's dependencies.\n\nBy managing the logic of the ContainerBuilder and Resolver, which are unique to each container, \nthe developer can modify the dependency resolution logic in different contexts \nwithout altering the code of `Target` classes.\n\n## ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedmonddantes%2Fdi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedmonddantes%2Fdi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedmonddantes%2Fdi/lists"}