Skip to content
On this page

Dependency Injection Container

Forme uses PHP DI under the hood, with autowiring enabled for added convenience. It creates a global singleton which is used across all Forme themes and plugins in a particular installation.

What is dependency injection?

There's a great explanation over here.

In practical development terms, it means typehinting necessary service classes (or even better, their interfaces) on class construction and saving them as properties which can then be accessed in the class methods. You don't need to instantiate those objects within those methods, nor care about what classes they need in their constructor, all object instantiation and class dependency management is delegated to the container.

You can see it at work in the following example. We're not using php 8's property promotion syntax here to make the code a bit more explicit.

php
<?php
namespace App\Services;

// declare the required classes
use Foo\BarBaz;
use AwesomeLibrary\SomeInterface;

final class MyClass
{
    // declare the properties that will hold the classes

    /** @var BarBaz */
    private $barBaz;

    /** @var SomeInterface */
    private $concreteClass;

    // typehint the classes/interfaces in your constructor
    public function __construct(BarBaz $barBaz, SomeInterface $concreteClass)
    {
        // save them as properties
        // the container looks after selection & instantiation
        // according to our typehints and automatically passes them in
        $this->barBaz = $barBaz;
        $this->concreteClass = $concreteClass;
    }

    public function doFoo($args)
    {
        // the objects are available via the class properties
        $this->barBaz->engage($args);
        $this->concreteClass->doSomething($args);
    }
}

Note that we don't need to worry about any classes that those injected classes in turn require. As long as they follow the same pattern of typehinting object dependencies, which many PHP libraries do these days, that should all be handled transparently for you by the container.

TIP

Some older classes or libraries don't follow the DI pattern at all, for example they might need configuration passed in during construct rather than typehinted objects. In those cases, you will either need to instantiate them yourself with new or specifically define them in the container (you can patch an existing PHP-DI container using its set() method)

Dependency Injection is also clearly not particularly useful for static classes. Just use those as you normally would!

Accessing the container

As long as you're within your app classes, you can assume that you're also within the container and you can and should use the above syntax.

But sometimes, you might be outside of that flow - the container might not have been instantiated yet or at all, for example during plugin activation.

Or the container might exist but your class won't get created via a hook defined in the Forme loader - for instance you might have a global helper function that needs to instantiate some class in your plugin that depends on DI to work. You can't use new as the class won't get its dependencies injected.

In this case, you could get the container with the \Forme\getContainer() helper function, and grab the class you need via the container's get method.

php
/**
 * Some global function for use in views
 */
function renderContent()
{
    // get the post id
    $postId = get_queried_object_id();
    // we need a controller
    // we can't easily use `new` as it has a bunch of typehinted dependencies
    // we are outside of the app here so let's get the container
    $container = \Forme\getContainer();
    // we then get the class we need using its fully qualified name
    $controller = $container->get('Foo\\Bar\\Controllers\\SomeController');
    // we can now interact with is as normal
    $controller->__invoke([
        'postId' => $postId,
    ]);
}

There is a slightly simpler way too, if you don't need direct access to the container, in the form of the \Forme\getInstance() helper function, which is a wrapper over the same procedure.

php
/**
 * Some global function for use in views
 */
function renderContent()
{
    // get the post id
    $postId = get_queried_object_id();
    // we need a controller
    // we can't easily use `new` as it has a bunch of typehinted dependencies
    // we are outside of the app here so let's use getInstance to instantiate
    $container = \Forme\getInstance('Foo\\Bar\\Controllers\\SomeController');
    // we can now interact with is as normal
    $controller->__invoke([
        'postId' => $postId,
    ]);
}

Further container configuration

There is nothing to stop you sorting out additional container configuration and adding further service definitions if you need to. Have a look at src/helpers.php in the core forme/framework repo to see what's going on.

TODO

  • Show some examples including definitions, interfaces and binding classes on the fly
  • Expand the bootstrap pattern in the boilerplate to include container definitions

Made by Moussa Clarke @ Sanders Web Works with ❤️