-
Notifications
You must be signed in to change notification settings - Fork 0
Bootstrap and Usage
Install library using composer command:
$ composer require thewebsolver/container
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.
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.
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 andWeakMap
orArrayObject
will be used as a concrete implementation ofArrayAccess
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;
}
);
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.
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.
Once entry is bound to the container, the value can be resolved using the same entry.
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.
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');
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();
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.
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.
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.
}