Download the PHP package suhock/php-dependency-injection without Composer
On this page you can find all versions of the php package suhock/php-dependency-injection. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download suhock/php-dependency-injection
More information about suhock/php-dependency-injection
Files in suhock/php-dependency-injection
Package php-dependency-injection
Short Description PHP Dependency Injection Library
License MIT
Homepage https://github.com/suhock/php-dependency-injection
Informations about the package php-dependency-injection
Dependency Injection Library for PHP
The PHP Dependency Injection library provides a customizable dependency injection framework for projects running on PHP 8.1 or later.
Out of the box, this library provides singleton and
transient lifetime strategies and a variety ways of
provisioning instances of specific
types, as well as specifying factories for all classes in a particular
namespace or implementing a specific
interface. You can easily extend the default Container
implementation with your own custom lifetime strategies, instance providers, or
nested containers to fit your needs.
The library also provides a ContextContainer
class for
cascading dependency resolution down a nested context hierarchy and an
Injector
class for injecting dependencies and explicit
parameters into a specific function or constructor.
Table of Contents
- Installation
- Basic usage
- Instance lifetime
- Singleton
- Transient
- Adding dependencies to the container
- Autowire a class
- Map an interface to an implementation
- Call a factory method
- Provide a specific instance
- Nested containers
- Namespace container
- Interface container
- Attribute container
- Customizing the container
- Custom lifetime strategies
- Custom instance providers
- Custom nested containers
- Context Container
- Dependency Injector
- Specifying dependencies
- Named object types
- Nullable types
- Builtin types with default values
- Union types
- Intersection types
- Appendix
- A note on service locators
- Refactoring toward dependency injection
Installation
Add suhock/dependency-injection
to the require
section of your project's
composer.json
file.
Alternatively, use the command line from your project's root directory.
Basic Usage
The basic Container
class contains methods for building the container and
retrieving instances. Start by constructing an instance.
Next, build your container, i.e., tell the container how it should resolve specific dependencies in your application.
Finally, call the get()
method on the container to retrieve an instance of
your application and run it.
The container will autowire the class constructor and provide your application the instance.
If your application has other entry points (e.g. controllers), it might be useful to inject the container into the part of your application that invokes those entry points (e.g. a router).
Instance lifetime
The lifetime of an instance determines when the container should request a fresh instance of a class. There are two builtin lifetime strategies for classes: singleton and transient. You can also add your own custom lifetime strategies.
Singleton
Singleton instances are persisted for the lifetime of the container. When the
container receives a request for a singleton instance for the first time, it
will call the factory that you specified for that class, store the result, and
then return it. Any time the container receives a subsequent request for that
class, it will return that same instance. The default Container
provides
convenience methods for adding singleton factories, all starting with the prefix
addSingleton
.
Transient
Transient instances are never persisted and the container provides a fresh
value each time an instance is requested. Each time the container receives a
request for a transient instance, it will call the factory you specified for
that class. The default Container
provides convenience methods for adding
transient factories, all starting with the prefix addTransient
.
Adding dependencies to the container
There are a number of built-in ways to specify how new instances should be created.
- Autowire a class
- Map an interface to an implementation
- Call a factory method
- Provide a specific instance
If needed, can also specify your own custom instance providers.
This document uses a modified PHP syntax for conveying API information.
Autowire a class
The container will construct classes by calling the class's constructor, automatically resolving any dependencies in the constructor's parameter list.
If the class has any methods with an Autowire
attribute, the container will
call those methods, resolving and injecting any dependencies listed in the
parameter list.
The optional $mutator
callback allows additional configuration of the object
after the container has initialized it. The callback must take an instance of
the class as its first parameter. Additional parameters will be autowired.
Examples
Autowiring a class constructor
In the following example, when the container provides an instance of MyService
it will automatically inject all dependencies into its constructor to create an
instance.
Using mutators to set optional properties
When the container provides instances of CurlHttpClient
, after autowiring the
constructor, it will also set its logger
property.
Using attributes to set optional properties
When the container provides an instance of CurlHttpClient
, it will see that
setLogger()
has an Autowire
attribute and call it passing in a Logger
instance resolved from the container.
Map an interface to an implementation
The container will provide classes by using the instance provider of the specified implementing subclass. You must therefore also add the implementing class to the container.
Examples
Mapping an interface to a concrete implementation
When your application requests an instance of HttpClient
, the container will
see that it should actually provide an instance of CurlHttpClient
. It will
then autowire the CurlHttpClient
constructor to provide an instance.
Chaining implementations
When your application requests an instance of Throwable
, the container will
see that it should actually provide an instance of Exception
. Next it will
see that instances of Exception
should be created using LogicException
.
Finally, it will provide an instance of LogicException
for Throwable
by
autowiring its constructor. If your application instead requests an instance of
Exception
then the container will also provide an instance of
LogicException
.
Unresolved mappings
The container must know how to provide the implementation or an exception will be thrown:
Call a factory method
The container will provide class instances by requesting them from a factory method. Any parameters in the factory method will be autowired.
Examples
Inject a configuration value
When your application requests an instance of Mailer
from the container, it
will call the specified factory, injecting the AppConfig
dependency. The
factory then manually constructs an instance, specifying the mailer transport
from that config.
Inline class implementation
Provide a specific instance
The container will provide a pre-constructed instance of a class.
Examples
Basic usage
Anytime your application requires a Request
object, the container will provide
the exact same instance that was passed in with the $request
variable.
Nested containers
If the container cannot find a way to provide an instance of a specific class,
it will next check to see if there are any nested containers that can provide
the value. Two built-in nested container implementations are provided: namespace
and implementation. You can also add custom containers that implement
ContainerInterface
using the addContainer()
method. Nested containers are
searched sequentially in the order they are added.
Namespace container
Namespace containers provide an instance of the requested class if it is in the configured namespace. By default, the namespace container will autowire the constructor for all classes in the namespace.
The namespace container accepts an optional $factory
parameter that specifies
a method which provides instances of classes in the namespace. The factory must
take the name of the class being instantiated as the first parameter. The outer
container will provide any additional dependencies.
Examples
Interface container
Interface containers provide an instance of the requested class if it is a subclass of the specified interface or base class. Instances are acquired from the given factory, or by autowiring the constructor if no factory is provided. The factory must take the class name as the first parameter. The outer container will provide any additional dependencies.
Examples
The following example retrieves repository instances from a third-party library's container.
Attribute container
Attribute containers will provide an instance of any class that has the specified attribute. Instances are acquired from the given factory, or by autowiring the constructor if no factory is provided. The factory must take the class name as the first parameter and an attribute instance as the second. The outer container will provide any additional dependencies.
Examples
The following example provides an alternative to the example under interface container section, using an attribute to designate metadata rather than an interface.
Customizing the Container
Custom Lifetime Strategies
Implement LifetimeStrategy
and optionally extend Container
with convenience
methods for your new lifetime strategy.
Custom Instance Providers
Implement InstanceProvider
and add it to your container using one of the basic
add methods. You can also extend Container
to add convenience methods for
using your new instance provider.
Custom Nested Containers
Implement ContainerInterface
and pass into the container using one of the
methods below. If your custom container needs to be able to autowire objects,
you can pass in the outer container to its constructor.
Context Container
The ContextContainer
class provides a collection of named containers
(contexts) that can be used for providing different construction for the same
class in different parts of your application. Contexts can be named with strings
or enum values.
The context container utilizes a context stack for resolving dependencies. The
stack can be managed by the push()
and pop()
methods, or using the Context
attribute on class, function, or parameter declarations.
Dependency Injector
The library also provides a dependency injector, Injector
that can be used for
directly calling constructors and functions, injecting any dependencies from a
container. The injector also lets you directly inject specific values for named
or indexed parameters.
Example
The following is an example where dependencies need to be injected into a function in a controller instead of the constructor.
Specifying dependencies
A function specifies its dependencies by listing them in its parameter list. A class specifies its dependencies by listing them in the parameter list of its constructor. Dependencies must be specified as either named object types, union types, or intersection types.
Named object types
If a dependency is specified as a named object type, the container will only provide a value if it can resolve a factory for that type.
In the example above, the container will attempt to resolve an instance of
HttpClient
. If it cannot resolve HttpClient
it will throw an
ParameterResolutionException
.
Nullable types
If the container cannot resolve a dependency, but the dependency is nullable, then the container will provide a null value.
In the example above, the container will attempt to resolve an instance of
HttpClient
. If it cannot resolve HttpClient
it will inject a null
value
instead.
Builtin types with default values
The container is not able to resolve builtin types. However, if the function or class takes a builtin type and that parameter specifies a default value, the default value will be used.
In the example above, although the container cannot resolve string
, int
, or
array
types, it will autowire the constructor with the specified default
values. If you need to inject non-default values for builtin types, use a
factory method.
Union types
If a dependency is specified as a union type, the container will search sequentially through all named object types in the union list. It will provide a value using the first type it is able to resolve. Builtin types are ignored.
In the example above, the container will attempt to resolve an instance of
HttpClient
first. If it cannot resolve HttpClient
, it will attempt to
resolve an instance of GopherClient
. If it cannot resolve GopherClient
, it
will ignore string
and then throw an ParameterResolutionException
.
Intersection types
If a dependency is specified as an intersection type, the container will attempt to fetch an instance of each type in the list until it finds one that satisfies all the types in the list. Since an instance must be retrieved in order to test whether it is a match, the use of intersection types may be slow and could have unintended consequences if the construction of any non-matching instances have side effects.
In the example above, the container will first attempt to resolve an instance of
HttpClient
. If it succeeds, it will check see
Appendix
A note on the service locator pattern
The previous example resembles a service locator pattern. Please note that while
the Container
class is functionally equivalent to a service locator, it is
usually best to avoid the service locator pattern, since it makes testing,
refactoring, and reasoning about your application more difficult.
Only places in your application that invoke entry points should directly use the container. If you know the specific object type required before runtime, you should rely on the container's automatic dependency injection capabilities instead of directly invoking the container. In the prior example, the application cannot know which container to invoke until it receives an actual request, so injecting the container is necessary.
The following is an example of what not to do.
In the above example, the required type, HttpClient
is known before runtime
and can be requested directly by the constructor. This change will make
MyClass
's actual dependencies much clearer thereby making testing and
refactoring far easier.
Refactoring toward the dependency injection pattern
An exception to the rule against the service locator pattern might be if you are refactoring a legacy application toward the dependency injection pattern: you want to reuse the container's dependency building logic as much as possible, but there is still code where it is difficult to inject dependencies properly.
In this case, the application container can be built off a singleton instance and made available to legacy code as an intermediate step. Once you finally refactor all uses of the singleton container to use proper dependency injection, the singleton container can be removed.