Skip to content

Commit 19d9fc9

Browse files
committed
Write README
1 parent 829cf1a commit 19d9fc9

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed

README.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,287 @@
11
# Technically Cascade Container
22

33
🧅 `Technically\CascadeContainer` is simple yet powerful PSR-11 based service container implementation with layers and dependencies auto-wiring.
4+
5+
### Philosophy
6+
7+
- [PSR Container][psr-11] compatibility
8+
- [Semantic Versioning](http://semver.org/)
9+
- PHP 8.0+
10+
- Minimal yet elegant API
11+
12+
### Features
13+
14+
- Supports inheriting services from a parent PSR-11 Service Container
15+
- Supports forking the service container instance into an isolated layer,
16+
inheriting all existing service definitions from the original container.
17+
- [PSR Container][psr-11] compatibility
18+
- Autowiring — automatic dependencies resolution
19+
- Full PHP 8.0+ features support for auto-wiring (e.g. union types)
20+
21+
Usage
22+
-----
23+
24+
### Installation
25+
26+
Use [composer](http://getcomposer.org/).
27+
28+
```sh
29+
composer require technically/cascade-container
30+
```
31+
32+
### Basics
33+
34+
Checking presence, getting and setting service instances to the service container.
35+
36+
- `::get(string $id): mixed` — Get a service from the container by its name
37+
- `::has(string $id): bool` — Check if there is a service defined in the container with the given name
38+
- `::set(string $id, mixed $instance): void` — Define a service instance with the given name to the container
39+
40+
```php
41+
<?php
42+
43+
use Technically\CascadeContainer\CascadeContainer;
44+
45+
$container = new CascadeContainer();
46+
47+
// Set a service instance to the container
48+
$container->set('acme', new AcmeService());
49+
50+
// Check if there is a service binding for the given service
51+
echo $container->has('acme') ? 'ACME service is defined' : 'Nope';
52+
53+
// Get a service from container
54+
$acme = $container->get('acme');
55+
$acme->orderProducts();
56+
```
57+
58+
59+
#### Using abstract interfaces
60+
61+
It's handy to bind services by their abstract interfaces
62+
to explicitly declare its interface on both definition and consumer sides.
63+
64+
```php
65+
<?php
66+
67+
/** @var $container \Technically\CascadeContainer\CascadeContainer */
68+
69+
// Definition:
70+
// Note we bind an instance by its **abstract** interface.
71+
// This way you force consumers to not care about implementation details, but rely on the interface.
72+
$container->set(\Psr\Log\LoggerInterface::class, $myLogger);
73+
74+
// Consumer:
75+
// Then you have a consumer that needs a logger implementation,
76+
// but doesn't care on details. It can use any PSR-compatible logger.
77+
$logger = $container->get(\Psr\Log\LoggerInterface::class);
78+
assert($logger instanceof \Psr\Log\LoggerInterface);
79+
$logger->info('Nice!');
80+
```
81+
82+
83+
84+
### Aliases
85+
86+
Sometimes you may also want to bind the same service by different IDs.
87+
You can use aliases for that:
88+
89+
- `::alias(string $serviceId, string $alias): void` &mdash; Allow accessing an existing service by its new alias name
90+
91+
```php
92+
<?php
93+
/** @var $container \Technically\CascadeContainer\CascadeContainer */
94+
95+
$container->set(\Psr\Log\LoggerInterface::class, $myLogger);
96+
$container->alias(\Psr\Log\LoggerInterface::class, alias: 'logger');
97+
98+
$logger = $container->get(\Psr\Log\LoggerInterface::class);
99+
// ... or
100+
$logger = $container->get('logger'); // 100% equivalent
101+
102+
$logger->info('Nice!');
103+
```
104+
105+
106+
### Deferred resolvers
107+
108+
You can declare a service by providing a deferred resolver function for it.
109+
The service container will call that function for the first time the service
110+
is requested and remember the result.
111+
112+
- `::deferred(string $serviceId, callable $resolver): void` &mdash; Provide a deferred resolver for the given service name.
113+
114+
*Note: the callback function parameters are auto-wired the same way as with the `->call()` API.*
115+
116+
```php
117+
<?php
118+
/** @var $container \Technically\CascadeContainer\CascadeContainer */
119+
120+
$container->deferred('connection', function (ConnectionManager $manager) {
121+
return $manager->initializeConnection();
122+
});
123+
124+
// Consumer:
125+
$connection = $container->get('connection'); // The connection object
126+
$same_connection = $container->get('connection'); // The same connection object
127+
128+
assert($connection === $same_connection); // The same instance
129+
```
130+
131+
132+
### Factories
133+
134+
You can also provide a factory function to be used to construct a new service instance
135+
every time it is requested.
136+
137+
It works very similarly to `->resolver()`, but calls the factory function every time.
138+
139+
- `::factory(string $serviceId, callable $factory): void` &mdash; Bind a service to a factory function to be called every time it is requested.
140+
141+
*Note: the callback function parameters are auto-wired the same way as with the `->call()` API.*
142+
143+
```php
144+
<?php
145+
/** @var $container \Technically\CascadeContainer\CascadeContainer */
146+
// Definition:
147+
$container->factory('request', function (RequestFactory $factory) {
148+
return $factory->createRequest();
149+
});
150+
151+
// Consumer:
152+
$request = $container->get('request');
153+
$another = $container->get('request');
154+
155+
assert($request !== $another); // Different instances
156+
```
157+
158+
159+
160+
### Extending a service
161+
162+
Sometimes it is necessary to extend/decorate an existing service by changing it or wrapping it into a decorator.
163+
164+
- `::extend(string $serviceId, callable $extension): void` &mdash; Extend an existing service by providing a transformation function.
165+
166+
* Whatever the callback function returns will replace the previous instance.
167+
* If the service being extended is defined via a deferred resolver, the extension will become a deferred resolver too.
168+
* If the service being extended is defined as a factory, the extension will become a factory too.
169+
170+
```php
171+
<?php
172+
/** @var $container \Technically\CascadeContainer\CascadeContainer */
173+
174+
// Definition:
175+
$container->deferred('cache', function () {
176+
return new RedisCache('127.0.0.1');
177+
});
178+
179+
// Wrap the caching service with a logging decorator
180+
$container->extend('cache', function(RedisCache $cache, LoggerInterface $logger) {
181+
return new LoggingCacheDecorator($cache, $logger);
182+
});
183+
184+
// Consumer:
185+
$cache = $container->get('cache'); // LoggingCacheDecorator object
186+
// Uses cache seamlessly as before (implying that RedisCache and LoggingCacheDecorator have the same interface)
187+
```
188+
189+
190+
### Isolated layers forked from the service container
191+
192+
Sometimes it is necessary to create an isolated instance of the service container,
193+
inheriting its configured services and allowing to define more, without affecting
194+
the parent container.
195+
196+
Think of it as JavaScript variables scopes: a nested scope inherits all the variables from the parent scope.
197+
But defining new scope variables won't modify the parent scope. That's it.
198+
199+
- `::cascade(): CascadeContainer` &mdash; Create a new instance of the service container, inheriting all its defined services.
200+
201+
```php
202+
$project = new CascadeContainer();
203+
$project->set('configuration', $config);
204+
205+
$module = $project->cascade();
206+
$module->set('configuration', $config->extend($local));
207+
$module->factory('request', function () {
208+
// ...
209+
});
210+
// and so on
211+
212+
assert($project->get('configuration') !== $module->get('configuration')); // Parent service "configuration" instance remained unchanged
213+
```
214+
215+
216+
### Auto-wiring dependencies
217+
218+
#### Construct a class instance
219+
220+
You can construct any class instance automatically injecting class-hinted dependencies from the service container.
221+
It will try to resolve dependencies from the container or construct them resolving their dependencies recursively.
222+
223+
- `::construct(string $className, array $parameters = []): mixed` &mdash; Create a new instance of the given class
224+
auto-wiring its dependencies from the service container.
225+
226+
```php
227+
<?php
228+
/** @var $container \Technically\CascadeContainer\CascadeContainer */
229+
230+
// Class we need to inject dependencies into
231+
class LoggingCacheDecorator {
232+
public function __construct(CacheInterface $cache, LoggerInterface $logger, array $options = []) {
233+
// initialize
234+
}
235+
}
236+
237+
$container->set(LoggerInterface::class, $logger);
238+
$container->set(CacheInterface::class, $cache);
239+
240+
// Consumer:
241+
$cache = $container->construct(LoggingCacheDecorator::class);
242+
// you can also provide constructor arguments in the second parameter:
243+
$cache = $container->construct(LoggingCacheDecorator::class, ['options' => ['level' => 'debug']]);
244+
```
245+
246+
247+
#### Calling a method
248+
249+
You can call *any [callable]* auto-wiring its dependencies from the service container.
250+
251+
- `::call(callable $callable, array $parameters = []): mixed` &mdash; Call the given callable auto-wiring its dependencies from the service container.
252+
253+
```php
254+
<?php
255+
/** @var $container RockSymphony\ServiceContainer\ServiceContainer */
256+
257+
class MyController
258+
{
259+
public function showPost(string $url, PostsRepository $posts, TemplateEngine $templates)
260+
{
261+
$post = $posts->findByUrl($url);
262+
263+
return $templates->render('post.html', ['post' => $post]);
264+
}
265+
}
266+
267+
$container->call([new MyController(), 'showPost'], ['url' => '/hello-world']);
268+
```
269+
270+
You can as well pass a Closure to it:
271+
272+
```php
273+
$container->call(function (PostsRepository $repository) {
274+
$repository->erase();
275+
});
276+
277+
```
278+
279+
License
280+
-------
281+
282+
This project is licensed under the terms of the [MIT license].
283+
284+
285+
[psr-11]: https://github.com/container-interop/fig-standards/blob/master/proposed/container.md
286+
[callable]: http://php.net/manual/en/language.types.callable.php
287+
[MIT license]: https://opensource.org/licenses/MIT

0 commit comments

Comments
 (0)