| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
 |  | |||
 |  | |||
 |  | |||
 |  | |||
 |  | |||
 |  | |||
 |  | |||
 |  | |||
 |  | |||
🧅 Technically\CascadeContainer is simple yet powerful PSR-11 based service container implementation with layers and dependencies auto-wiring.
use Technically\CascadeContainer\CascadeContainer;
$container = new CascadeContainer();
$container->set('config', $config);
// Lazy-evaluated services
$container->deferred('mailer', function () {
// lazily initialize mailer service here
$mailer = /* ... */;
return $mailer;
});
// On-demand object factories (executes every time 'request' is obtained from the container)
$container->factory('request', fn () => $requestFactory->createRequest());
// ✨ CASCADING LAYERS ✨
// Fork the container into an isolated layer, inheriting everything from above.
// Override services or define new ones. The changes won't affect the parent $container instance.
$environment = $container->cascade();
// For example, we want to use a different mailer implementation in the tests environment:
$environment->deferred('mailer', fn () => new NullMailer());Use composer.
composer require technically/cascade-containerChecking presence, getting and setting service instances to the service container.
<?php
use Technically\CascadeContainer\CascadeContainer;
$container = new CascadeContainer();
// Set a service instance to the container
$container->set('acme', new AcmeService());
// Check if there is a service binding for the given service
echo $container->has('acme') ? 'ACME service is defined' : 'Nope';
// Get a service from container
$acme = $container->get('acme');
$acme->orderProducts();It's handy to bind services by their abstract interfaces to explicitly declare its interface on both definition and consumer sides.
<?php
/** @var $container \Technically\CascadeContainer\CascadeContainer */
// Definition:
// Note we bind an instance by its **abstract** interface.
// This way you force consumers to not care about implementation details, but rely on the interface.
$container->set(\Psr\Log\LoggerInterface::class, $myLogger);
// Consumer:
// Then you have a consumer that needs a logger implementation,
// but doesn't care on details. It can use any PSR-compatible logger.
$logger = $container->get(\Psr\Log\LoggerInterface::class);
assert($logger instanceof \Psr\Log\LoggerInterface);
$logger->info('Nice!');Sometimes you may also want to bind the same service by different IDs. You can use aliases for that:
<?php
/** @var $container \Technically\CascadeContainer\CascadeContainer */
$container->set(\Psr\Log\LoggerInterface::class, $myLogger);
$container->alias(\Psr\Log\LoggerInterface::class, alias: 'logger');
$logger = $container->get(\Psr\Log\LoggerInterface::class);
// ... or
$logger = $container->get('logger'); // 100% equivalent
$logger->info('Nice!');You can declare a service by providing a deferred resolver function for it. The service container will call that function for the first time the service is requested and remember the result.
This pattern is often called lazy initialization.
Note: the callback function parameters are auto-wired the same way as with the ->call() API.
<?php
/** @var $container \Technically\CascadeContainer\CascadeContainer */
$container->deferred('connection', function (ConnectionManager $manager) {
return $manager->initializeConnection();
});
// Consumer:
$connection = $container->get('connection'); // The connection object
$same_connection = $container->get('connection'); // The same connection object
assert($connection === $same_connection); // The same instanceYou can also provide a factory function to be used to construct a new service instance every time it is requested.
It works very similarly to ->deferred(), but calls the factory function every time.
Note: the callback function parameters are auto-wired the same way as with the ->call() API.
<?php
/** @var $container \Technically\CascadeContainer\CascadeContainer */
// Definition:
$container->factory('request', function (RequestFactory $factory) {
return $factory->createRequest();
});
// Consumer:
$request = $container->get('request');
$another = $container->get('request');
assert($request !== $another); // Different instancesSometimes it is necessary to extend/decorate an existing service by changing it or wrapping it into a decorator.
::extend(string $serviceId, callable $extension): void — Extend an existing service by providing a transformation function.
<?php
/** @var $container \Technically\CascadeContainer\CascadeContainer */
// Definition:
$container->deferred('cache', function () {
return new RedisCache('127.0.0.1');
});
// Wrap the caching service with a logging decorator
$container->extend('cache', function(RedisCache $cache, LoggerInterface $logger) {
return new LoggingCacheDecorator($cache, $logger);
});
// Consumer:
$cache = $container->get('cache'); // LoggingCacheDecorator object
// Uses cache seamlessly as before (implying that RedisCache and LoggingCacheDecorator have the same interface)Sometimes it is necessary to create an isolated instance of the service container, inheriting its configured services and allowing to define more, without affecting the parent container.
Think of it as JavaScript variables scopes: a nested scope inherits all the variables from the parent scope. But defining new scope variables won't modify the parent scope. That's it.
$project = new CascadeContainer();
$project->set('configuration', $config);
$environment = $project->cascade(); // MAGIC! ✨
// Override existing services. It does not affect 'configuration' service in the parent container.
$environment->set('configuration', $moduleConfig);
// Define new services. They'll only exist on the current layer.
$environment->factory('request', function () {
// ...
});
// and so on
assert($project->get('configuration') !== $environment->get('configuration')); // Parent service "configuration" instance remained unchangedYou can construct any class instance automatically injecting class-hinted dependencies from the service container. It will try to resolve dependencies from the container or construct them resolving their dependencies recursively.
<?php
/** @var $container \Technically\CascadeContainer\CascadeContainer */
// Class we need to inject dependencies into
class LoggingCacheDecorator {
public function __construct(CacheInterface $cache, LoggerInterface $logger, array $options = []) {
// initialize
}
}
$container->set(LoggerInterface::class, $logger);
$container->set(CacheInterface::class, $cache);
// Consumer:
$cache = $container->construct(LoggingCacheDecorator::class);
// you can also provide constructor arguments in the second parameter:
$cache = $container->construct(LoggingCacheDecorator::class, ['options' => ['level' => 'debug']]);You can call any callable auto-wiring its dependencies from the service container.
<?php
/** @var $container RockSymphony\ServiceContainer\ServiceContainer */
class MyController
{
public function showPost(string $url, PostsRepository $posts, TemplateEngine $templates)
{
$post = $posts->findByUrl($url);
return $templates->render('post.html', ['post' => $post]);
}
}
$container->call([new MyController(), 'showPost'], ['url' => '/hello-world']);You can as well pass a Closure to it:
$container->call(function (PostsRepository $repository) {
$repository->erase();
});This project is licensed under the terms of the MIT license.
Implemented by 👾 Ivan Voskoboinyk.
| Back | FazBrowse Home | New Git URL |