Download the PHP package stellarwp/container without Composer
On this page you can find all versions of the php package stellarwp/container. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download stellarwp/container
More information about stellarwp/container
Files in stellarwp/container
Package container
Short Description A PSR-11 Dependency Injection (DI) container for use in WordPress codebases
License MIT
Informations about the package container
StellarWP Dependency Injection (DI) Container
This library contains a PSR-11-compatible Dependency Injection (DI) container to aid in resolving dependencies as needed throughout various applications.
What is Dependency Injection?
In its simplest terms, Dependency Injection is providing dependencies to an object rather than making the object try to create/retrieve them.
For instance, imagine that we're building a plugin that contains different "modules", each of which might receive a global Settings
object.
With dependency injection, our module definition might look like this:
By injecting the Settings
object, we're able to create a single instance of the object and more-easily inject test doubles in our tests.
Now, compare this to a version of the same class that doesn't use dependency injection:
Under this model, each instance of the module will be responsible for instantiating their own instance of the Settings
object, and we lack the ability to inject test doubles.
Furthermore, if the Settings
class changes its constructor method signature, we'd have to update calls to new Settings()
throughout the application.
This is one of the major benefits of a DI container: we can define how an object gets constructed in one place, and then recursively resolve dependencies.
Dependency Injection vs Service Location
It's worth mentioning that the container is designed to be used for Dependency Injection, not as a Service Locater.
What's a Service Locater? Imagine instead of injecting the Settings
object into our integrations, we instead injected the entire Container
object. Instead of giving the class the tools it needs to do its job, we're instead throwing the entire application at it and saying "here, you figure it out."
The PSR-11 meta documentation has a good breakdown of these patterns.
Installation
It's recommended that you install the DI container as a project dependency via Composer:
Next, create a new class within your project that extends the StellarWP\Container\Container
class:
You're free to customize anything you'd like, but there's one abstract method that needs filled in: config()
.
Defining the config() method
A key part of any DI container is the mapping between abstract dependencies (for example, interfaces and/or class names) and concrete instances; in the StellarWP container, this is defined via the StellarWP\Container\Container::config()
method.
The config()
method should return a one-dimensional, associative array mapping abstract identifiers to callables that will produce concrete instances.
A very simple example might look something like this: imagine we have an interface, SandwichInterface
, that describes how to make a sandwich.
Now, let's assume we have an implementation of this interface, PBandJ
, that defines a Peanut Butter and Jelly (PB&J) sandwich. As you might have guessed, our PBandJ
class has three dependencies: bread, peanut butter, and jelly. The definition for this class might look something like this:
Now, let's assume that any time we want a sandwich throughout our application, it should be a PB&J. Within our container's config()
method, we'll define an anonymous function that will return an instance of PBandJ
, bound to the SandwichInterface
:
Whenever we request a sandwich from the DI container, we'll now get the PB&J we've defined above:
If we wanted to define multiple types of sandwiches, we could also use PBandJ
as the abstract (array key), then request it via $container->get(PBandJ::class)
.
⚠️ A note on abstract identifiers
While it's probably most-useful to use a class or interface name as the abstract identifier, this can be any string (e.g. "peanut_butter").
Recursive definitions
In our PB&J example above, notice that the callback for SandwichInterface
was given the $container
parameter: this is the current container instance, letting us recursively define our dependencies.
For example, if we were using homemade bread, we might have an implementation for Bread
defined that accepts Flour
, Yeast
, Water
, and Salt
as dependencies. We would define Bread
in the config with these dependencies and, upon calling $container->make(Bread::class)
within the definition for SandwichInterface
the container would automatically resolve Bread
before injecting it.
Please note that a StellarWP\Container\Exceptions\RecursiveDependencyException
will be thrown if a recursive loop is detected when resolving dependencies (e.g. DrinkingCoffee
depends on MakingCoffee
, which depends on BeingFunctionalInTheMorning
, which depends on DrinkingCoffee
).
Aliases
Sometimes it's helpful to add one container definition to point to another, especially when building base containers meant to be extended or introducing a container to an existing codebase.
The StellarWP container supports alias definitions where the "concrete" value in the configuration array points to another abstract:
⚡️ Performance recommendation
For the best performance, it's recommended that you try to settle on a single abstract rather than relying on aliases, but they're there if you need them.
Using the DI container
Once you've defined your container's configuration, it's time to start using it in your project!
First, you'll need to construct an instance of your container:
Now that we have our container instance, let's try resolving some dependencies. In order to do so, we can use one of two methods: get()
or make()
.
The get()
method will resolve the dependency and cache the result, so subsequent calls for that same dependency will return the same value:
The make()
method, on the other hand, will return a fresh copy of the dependency each time:
It's worth noting, however, that calling get()
on a dependency will always cache it (and any recursive dependencies), while make()
will only cache recursive dependencies if resolved via get()
. Imagine our container contains the following definitions:
When Lunch
is resolved through the container, the caching behavior will be different based on whether make()
or get()
is used within the definitions:
Using $container->get()
:
Using $container->make()
:
As you can see, the Fruit
and Apple
definitions will always be cached, as they use get()
within the definition for Lunch
. In some situations this may be desirable, but generally it's best to use $container->make()
in your resolutions.
If the container is asked for a dependency for which it doesn't have a definition, it will throw a StellarWP\Container\Exceptions\NotFoundException
. In order to avoid this, you may see if a definition exists via $container->has(SomeAbstract::class)
. You may also see whether or not the container has a cached resolution with $container->resolved(SomeAbstract::class)
.
Clearing cached dependencies
If you need to clear the cache for a particular dependency, you may call $container->forget(SomeAbstract::class)
and subsequent calls to $container->get()
will re-generate the cached value.
It's important to note that calling $container->forget()
on a dependency will not recursively remove its sub-dependencies, e.g.:
If you need to forget multiple dependencies, you may pass them as separate arguments to $container->forget()
:
Using the container as a Singleton
If you need to be able to access the container from within dependencies (not uncommon when introducing a DI container into an existing codebase), you may use the static Container::getInstance()
to return a Singleton version of the container (meaning each call to Container::getInstance()
will return the same instance):
However, this could result in two separate container instances: the Singleton and the instance created via new Container()
:
To reduce this duplication, the getInstance()
method accepts an optional $instance
argument that overrides the container's internal $instance
property:
Extending definitions
The use of a DI container also makes testing easier, especially when we leverage the extend()
method.
This method lets us override the DI container's definition for a given abstract, letting us inject test doubles and/or known values.
For example, pretend we have a ServiceSdk
dependency, which is a third-party SDK for interacting with some service. We don't necessarily want our automated tests to actually hit the service (which can make our tests slow and brittle), so we might replace our definition for the service in our tests:
You may also pass the resolved instance directly into the container with extend()
:
⏮ Restoring original definitions
If you need to restore the original definition for an abstract, you may remove its extension(s) using
$container->restore()
.
Contributing
If you're interested in contributing to the project, please see our contributing documentation.
License
This library is licensed under the terms of the MIT license.