Skip to content

Bootstrap and Usage

Shesh Ghimire edited this page Jan 5, 2025 · 5 revisions

Installation

Install library using composer command:

$ composer require thewebsolver/container

Bootstrap

In the project's bootstrap file, require vendor file and initialize the container.

use WeakMap;
use TheWebSolver\Codegarage\Container\Container;

require_once . '/vendor/autoload.php';

$app = new Container();

// Or, initialize a singleton instance.
Container::boot();

Container::boot() === Container::boot(); // true.
Container::boot() === $app; // false.

// To get an instance, simply provide the classname to be resolved.
$resolved = $app->get(WeakMap::class);
$resolved instanceof WeakMap; // true.

Share initialized Container

If a Container instance is initialized in a bootstrap file and same initialized instance needs to be shared across the project:

$app = new Container();

Container::use($app);

Container::boot() === $app; // true.

This is useful when project has pre-compiled configuration files or pre-configured dependency needs to injected to the Container.

For eg: See how a pre-configured Event Manager is injected to the Container as documented in the event page.

Setters

Set/Bind Entry

If the project follows design pattern/principle, then there should an interface for a feature (Repository, Payment Method, etc) and a concrete class that implements the interface. Container makes it easier to set an entry based on interface but resolve a concrete that implements same entry interface.

It promotes Dependency Inversion Principle by allowing a concrete's dependency to be decoupled/loosely-coupled making dependency easily swappable.

For most examples, ArrayAccess will be used as an interface and WeakMap or ArrayObject will be used as a concrete implementation of ArrayAccess interface.

Once the container ($app) is initialized, register entry to the container like so:

use WeakMap;
use ArrayAccess;

// Using method call.
$app->set(ArrayAccess::class, WeakMap::class);

// Using as an array.
$app[ArrayAccess::class] = WeakMap::class;

// In either case, registration can be verified
// by using "ContainerInterface::has()` method:
$app->has(ArrayAccess::class); // true.

Sometime, there might be some data to be shared across the project or a pre-configured instance might be needed for an entry. In that case, function can be passed as concrete.

use ArrayAccess;
use ArrayObject;
use TheWebSolver\Codegarage\Container\Container;

// Setting global data.
$app->set(id: 'globalData', concrete: fn(): array => [/*some data*/]);

// Setting pre-configured concrete.
$app->set(
    id: ArrayAccess::class,
    concrete: function(Container $app): ArrayAccess {
        $arrayObject = $app->get(ArrayObject::class);

        $arrayObject->append('some initial value');

        return $arrayObject;
    }
);

Set/Bind Entry As Singleton

When an entry is to be resolved as a singleton for the application's request lifecycle, register entry to the container like so:

use WeakMap;
use ArrayAccess;

$app->setShared(ArrayAccess::class, WeakMap::class);

// After an entry is resolved the first time using Container::get(),
// it can be verified whether it is singleton or not:
$app->isInstance(ArrayAccess::class); // true.

Set/Bind Instance As Singleton

If an already instantiated object is to be shared for the application's request lifecycle, register instance to the container like so:

use WeakMap;
use ArrayAccess;

$instance = new WeakMap();

$shared = $app->setInstance(ArrayAccess::class, $instance);

$app->isInstance(ArrayAccess::class); // true.
$instance === $shared; // true;

// Classname itself or any literal string can be used
// an an entry/id as per project's requirement.
$app->setInstance('anotherWeakMap', $instance);
$app->isInstance('anotherWeakMap'); // true.

// Or
$app->setInstance(WeakMap::class, $instance);
$app->isInstance(WeakMap::class); // true.

// If an instance is registered using all three ways shown above,
// same instance'll get resolved, just with different entries.
// It is possible but NOT RECOMMENDED to register likewise.
$app->get(ArrayAccess::class) === $app->get('anotherWeakMap') === $app->get(WeakMap::class); //true.

Getters

Once entry is bound to the container, the value can be resolved using the same entry.

Instantiate object

use WeakMap;
use ArrayObject;
use ArrayAccess;

// Using method call.
$app->get(ArrayAccess::class) instanceof WeakMap; // true.

// Using as an array.
$app[ArrayAccess::class] instanceof WeakMap; // true.

// If the entry class constructor has any dependency, then those can be
// auto-wired as second argument of "Container::get()" method as
// an associative array of parameter name and its value.
$arrayObject = $app->get(ArrayObject::class, args: ['array' => [1,2,3]]);
$arrayObject->getArrayCopy() === [1,2,3]; // true.

Instantiate & invoke method

When a class method has dependencies, it can be resolved by directly invoking the method call.

class AboutUsTemplate {
    public function render(): void {
        echo 'Hello from About Us Page.';
    }
}

class Controller {
    public function renderAboutUsPage(AboutUsTemplate $engine): void {
        $engine->render();
    }
}

// Method call performs following actions:
// 1. Instantiates the Controller class
// 2. Invokes renderAboutUsPage method
// 3. Instantiates AboutUsTemplate, &
// 4. Echoes the template content.
$app->call(Controller::class, methodName: 'renderAboutUsPage');

Invoke Instance Method

Although not needed (or barely needed as per project's requirement), an instantiated object can also be invoked using Container::call() method.

use ArrayObject;

$app->setInstance(ArrayObject::class, new ArrayObject([1,2,3]));

// When value needs to be updated.
$app->call($app->get(ArrayObject::class)->append(...), args: ['value' => 4]);
// Or, setting directly without container overhead.
$app->get(ArrayObject::class)->append(4);

// When value needs to be retrieved.
$app->call($app->get(ArrayObject::class)->getArrayCopy(...)) === [1,2,3,4]; // true.
$app->call($app->get(ArrayObject::class)->count(...)) === 4; // true.
// Or, getting directly without container overhead.
$app->get(ArrayObject::class)->getArrayCopy() === [1,2,3,4]; // true.
$app->get(ArrayObject::class)->count();

Verifiers

There are several additional verifier methods than mentioned above that returns true/false.

use WeakMap;
use ArrayAccess;

$app->set(ArrayAccess::class, WeakMap::class);

// Using as an array.
isset($app[ArrayAccess::class]); // true.
isset($app[WeakMap::class]); // false.

$app->hasBinding(ArrayAccess::class); // true.
$app->has(ArrayAccess::class); // true.
$app->has(WeakMap::class); // false.
$app->hasResolved(ArrayAccess::class); // false.

// Resolve concrete for an interface.
$app->get(ArrayAccess::class) instanceof WeakMap; // true.
$app->hasResolved(ArrayAccess::class); // true.
$app->hasResolved(WeakMap::class); // false.

Aliases

Todo

An alias can be used for resolving a concrete when it is to be registered to the container with its interface.

Most use case for aliasing a concrete is to reduce fully-qualified classname imports.

use WeakMap;
use StdClass;
use ArrayAccess;

$app->setAlias(WeakMap::class, 'mapOfEnums');
$app->set(ArrayAccess::class, 'mapOfEnums');

$app->isAlias('mapOfEnums'); // true.
$app->has('mapOfEnums') // true.

$app->hasBinding(ArrayAccess::class); // true.
$app->has(ArrayAccess::class); // true.

$app->get(ArrayAccess::class) instanceof WeakMap; // true.

// If a concrete does not implement any interface,
// it can independently be aliased to container.
$app->setAlias(StdClass::class, 'anonymous');

$app->hasBinding('anonymous'); // false.
$app->isAlias('anonymous'); // true.
$app->has('anonymous'); // true.
$app->get('anonymous') instanceof StdClass; // true.

Not Todo

However, when an entry is aliased for an interface and then same alias is used to register it to the container, the alias will be removed but the literal string used as an alias will be registered/bound to the container.

use WeakMap;
use ArrayAccess;
use Psr\Container\ContainerExceptionInterface;

$app->setAlias(ArrayAccess::class, 'arrayAccessible');

// Removes alias set above. Container won't have any clue
// about "ArrayAccess" interface from now on.
$app->set('arrayAccessible', WeakMap::class);

$app->isAlias('arrayAccessible'); // false.
$app->hasBinding('arrayAccessible'); // true.
$app->hasBinding(ArrayAccess::class); // false.
$app->get('arrayAccessible') instanceof WeakMap; // true.

// A "ContainerError" exception will be thrown when an "ArrayAccess"
// is used for resolving concrete. Container cannot instantiate an
// "ArrayAccess" interface. Also, its concrete is never assigned.
try {
    $weakMap = $app->get(ArrayAccess::class);
} catch (Throwable $e) {
    $e instanceof ContainerExceptionInterface; // true.
}
Clone this wiki locally