Hi everyone,
This is an email to let you know of the research work that has been done under the PSR-11/container-interop scope.
Summary of previous discussions:
container-interop has a ContainerInterface that allows frameworks to decouple from containers. It is implemented by a lot of containers, and used by some frameworks like e.g. Zend Expressive, Slim, … PSR-11 is about standardizing that into a PSR.
ContainerInterface was also offered as a solution for framework-agnostic modules (aka bundles, plugins, …) by letting each module provide its container, and by chaining them all together. This raised a lot of discussions, and several other approaches to reach the same goal were mentioned
We can identify two different needs from that:
decouple frameworks from containers, to let users choose their own container (e.g. Zend Expressive, Slim, …)
cross-framework modules/bundles, so that package authors don't have to write an extra package per framework out there (a good illustration: https://github.com/thephpleague?utf8=%E2%9C%93&query=glide)
We believe ContainerInterface solves the first problem.
Lately, we have been working on the second problem through discussions on this mailing list and Gitter and through experiments.
Approaches for cross-framework modules
We have identified the following main approaches:
1. each module can provide its own container, all containers can be chained to build a main "composite" container
This approach was the first considered. It has limitations and cannot cover all features you would expect in a module system, e.g. extend previously defined entries, etc. It is also a bit complex to explain and use.
2. standard PHP objects/interfaces representing “dumpable” container definitions.
These are objects that can cast themselves to a string of PHP code. Those objects can be consumed by compilers to generate a PSR-11 compliant container that could itself work side-by-side with another container of an application. We worked on an interface for such objects here: https://github.com/moufmouf/compiler-interop. This experiment is a mitigated success. It does work, but somehow, it requires the notion of “compiling” a container which most of the frameworks out there do not deal with. It forces existing frameworks to work side-by-side with a compiled container, which feels weird to some people.
3. standard PHP objects/interfaces describing container definitions.
This was the third approach we considered. We worked on interfaces for such objects: https://github.com/container-interop/definition-interop This experiment is a mitigated success: it works, we managed to integrate it into several containers and we wrote several experimental modules with it. However it is complex: to understand, to integrate into containers, to use to write modules.
4. standard container configuration format (e.g. XML, …)
This approach was then considered, as it is similar to the previous one but simpler to use for module authors. It is also easier to understand as it is similar to Symfony configuration (in YAML or XML files), or Spring in Java.
This work has not be formalized yet because of the amount of work needed. This approach would also suffer from a few of the limitations identified in the first approach, plus others related to the fact that you cannot use PHP code in XML or YAML. It would also require the inclusion in the standard of many specific features: the standard must define many different ways for how objects can be created and dependencies injected. That makes the standard complex to define, and would force all containers (even simple ones) to support all the features.
It is however the only approach that allows to perform static analysis of the provided services. For instance, a tool like Packagist could be written to scan the configuration files and allow searching services inside packages. This, however, is not a primary goal we are seeking.
5. standard service providers
This is the approach we have been experimenting with lately and it has turned out to be simpler on many level:
the standard is much simpler, which means it is easier to explain and understand
it is easier to use as it relies on plain old PHP code
it is easier to implement support in containers
This is the approach we think has the most potential.
Standard service providers
In order to work and try the approach #5 (standard service providers) we have created a repository: https://github.com/container-interop/service-provider
This repository contains the following interface:
interface ServiceProvider
{
public static function getServices() : array;
}
This is close to what service providers are in, for example, Pimple, Laravel, etc. Except instead of relying on methods on the container like "register()" or "bind()", the service providers only expose a list of factories.
Please read the "Usage" documentation as it will be easier than copy-pasting the instructions here: https://github.com/container-interop/service-provider#usage
On the consuming side, integrating such service providers into containers was fairly easy. We have also implemented some example modules to illustrate how this can be used: https://github.com/container-interop/service-provider#compatible-projects
What's next?
We now need more framework, container and module authors to take a look at all that. We do not take our conclusions as absolute truth but rather we expose them as "lessons learned". The discussion is open on all topics/approaches.
We need to move forward on:
validate which approach we will choose - on that we recommend the "service providers"
once that is done, we need to make it perfect, leading to a PSR
I would expect service providers provide services (aka. configure) to the container and not return actual instances. That seems to me an inappropriate responsibility. Can you explain why you chose this way?
However, I see a danger of overstandardization (tm) here: while I like the idea of a generic package integration, it won't be possible in every case (at least not in the near future): as mentioned earlier, configuration for example is very specific to each framework.
These are my thoughts. Keep up the good work, I am really looking forward to use this thing.
$simplex->register(new TheCodingMachine\DbalServiceProvider(
'docrine.dbal.1st' // prefix for provided/expected values/services
));
$simplex->register(new TheCodingMachine\DbalServiceProvider(
'docrine.dbal.2nd' // prefix for provided/expected values/services
));
$simplex->register(new TheCodingMachine\DbalServiceProvider(
$inputMapping1 = [
'dbal.host' => 'doctrine.dbal.common.host',
'dbal.user' => 'doctrine.dbal.common.user',
'dbal.password' => 'doctrine.dbal.common.password',
'dbal.dbname' => 'doctrine.dbal.1st.dbname',
'Doctrine\DBAL\Driver' => 'doctrine.dbal.1st.driver',
],
$outputMapping1 = [
'Doctrine\DBAL\Connection' => 'doctrine.dbal.1st.connection',
],
));
$simplex->register(new TheCodingMachine\DbalServiceProvider(
$inputMapping2 = [
'dbal.host' => 'doctrine.dbal.common.host',
'dbal.user' => 'doctrine.dbal.common.user',
'dbal.password' => 'doctrine.dbal.common.password',
'dbal.dbname' => 'doctrine.dbal.2nd.dbname',
'Doctrine\DBAL\Driver' => 'doctrine.dbal.2nd.driver',
],
$outputMapping2 = [
'Doctrine\DBAL\Connection' => 'doctrine.dbal.2nd.connection',
],
));
$simplex->register('docrine.dbal.1st', TheCodingMachine\DbalServiceProvider::class);
$simplex->register('docrine.dbal.2nd', TheCodingMachine\DbalServiceProvider::class);
$simplex->register('docrine.dbal.1st', TheCodingMachine\DbalServiceProvider::class, $inputMapping1, $outputMapping1);
$simplex->register('docrine.dbal.2nd', TheCodingMachine\DbalServiceProvider::class, $inputMapping2, $outputMapping2);
--You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/xsY8bRG5K0M/unsubscribe.To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.To post to this group, send email to php...@googlegroups.com.To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/d17bd1ed-1cf1-4025-8195-d45318c703ea%40googlegroups.com.For more options, visit https://groups.google.com/d/optout.
our draft voluntarily doesn't enforce any rules on entry names
You can extend (or decorate) a service provider (as they are today) to prefix them
By the way what do you mean by "output mapping"?
The standard is very open thanks to its simplicity, and I think we should preserve that when possible.
const DEFAULT_PREFIX = 'my.dbal';
public function getServices(string $prefix)
{
return ["$prefix.connection" => 'createConnection'];
}
public function createConnection(string $prefix, ContainerInterface $c)
{
// PREFER LOGGER SPECIFIC FOR THIS SERVICE PROVIDER, FALLBACK TO GLOBAL
$logger = $c->has("$prefix.logger") ? $c->get("$prefix.logger") : $c->get('psr.logger');
return new MyDbal\Connection($logger);
}
...
--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/xsY8bRG5K0M/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/c0adda03-fc8c-427d-aa25-66c5fd06d8e0%40googlegroups.com.