Hi list,

Following my last message here, it has been clear we need to clearly define 
the scope of this PSR (before going to Draft stage)
I've been working on my "ideal" scope. I'm sharing it with you here in the 
hope to gather the maximum amount of comments.

As usual, I cross-posted this proposed scope on my blog if you want a 
better formated 
version: 
https://thecodingmachine.io/psr-11-scope-of-universal-service-providers-part2

Stated goal

Each framework has it's own custom package format. What these package 
formats are doing is essentially always the same. They are used to put 
things in a container.

If the PHP-FIG could come up with a unique package format that could be 
supported by all frameworks, package developers could truly write classes 
that can be used in any framework more easily.

Hence, the stated goal of this PSR (let's call it PSR-X since it does not 
have a number yet) is to *find a common package format that could be 
supported by all frameworks*.


In the rest of this post, I'll highlight a list of "abilities" that this 
package format should have.


1: Ability to put entries in a container

The very first thing our package format should be able to do is to *put 
things in a container*.


*Use case*: 

If I wrote a new "logger" package, with a MyLogger class in it, I want to 
register an entry in the container containing a MyLogger instance.


In other words, the PSR should be able to feed the container with a 
*factory* that can create the requested service.


2: Ability to claim a default implementation for an interface

The package should be able to declare that the service it provides is the 
*default 
implementation for a given interface* (this is important for auto-wiring 
containers). 


*Use case*: 

My MyLogger class implements PSR-3. I want a way to tell the container 
explicitly that if a service has a dependency on the Psr\Log\LoggerInterface, 
the entry I provide is a good candidate and can be used.


Note: in almost all containers I've seen, this is done by relying on 
convention and "aliases". It seems there is an agreement that to "claim" 
ownership of an interface, your identifier for the entry should be the 
fully qualified name of the interface.

    "Psr\Log\LoggerInterface" => your logger instance

Since a service can implement many interfaces, the best practice here seems 
to be using aliases.

    "MyLogger" => your logger instance    "Psr\Log\LoggerInterface" => alias to 
"MyLogger"



3: Ability to "tag", with or without priorities

The package can register a provided service as part of a "group" of 
services that share the same purpose.

Those services might need to be ordered inside the tag.


*Use case*: 

My package is providing a PSR-15 middleware in charge of transforming 
exceptions into HTTP 500 pages. I don't know who will consume it (it might 
be Zend Stratigility or any other PSR-15 consumer). I want my package to 
instruct the container that:

   - my middleware should be part of the middleware pipe (i.e. it should be 
   "tagged" as belonging to a middleware) 
   - my middleware should be the first in the "middleware pipe". 

Note: PSR-11 <https://www.php-fig.org/psr/psr-11/> does not introduce the 
notion of tags. Indeed, all containers do not have the notion of tag 
built-in. But PSR-11 specifies that a container entry can be anything 
<https://www.php-fig.org/psr/psr-11/#112-reading-from-a-container> (it is 
not limited to objects only). Therefore, a "tag" can be seen as a container 
entry that contains an array of services.


4: Ability to alter a service

A package should be able to alter/modify a service stored in the container, 
either by calling methods on it or by "decorating" it.


*Use case*: 

My package is providing a Twig extension. I want my package to:

   - Create a new container entry called `MyExtension` 
   - Alter the `Twig_Engine` instance in the container to call: 
   $twig->register($myExtension);. 

5: Auto-discovery

When you install a Symfony Bundle in Symfony 4 (via composer require), Symfony 
Flex <https://github.com/symfony/flex> takes care of registering the bundle 
for you. Same thing with Laravel Service Providers since Laravel 5.5 
<https://medium.com/@taylorotwell/package-auto-discovery-in-laravel-5-5-ea9e3ab20518>
 
or with Zend Framework 
<https://docs.zendframework.com/zend-component-installer/>. Those 
frameworks are actually coming with a Composer plugin that triggers after a 
package is installed and that is looking for some configuration in the 
package (either a manifest.json file or a special "extra" section in the 
composer.json file).

If we want our package format to be as easy to use as the framework 
specific package formats, we need to have a way for underlying frameworks 
to perform auto-discovery of service providers.

*Use case*: 

I'm performing a composer require to install a new package. Some composer 
plugin specific to my framework is triggered. The framework registers the 
service provider of my package automatically.

6: Ability to expose requirements (external required services and 
configuration)

If a package has requirements (in the form of dependencies that need to be 
available in the container or in the form of required configuration), it 
should be able to publish those programmatically to the underlying 
framework.

*Use case 1*: 

I'm installing a database abstraction library (Doctrine DBAL for instance). 
The package creates a default database connection instance in the 
container. This instance requires a number of parameters (database driver, 
host, name, user, password, etc...). These parameters might not be 
available in my application yet. My framework is clever enough to notice 
this at install time (maybe via a Composer plugin) and asks me to fill the 
parameters.

*Use case 2*: 

I'm installing a library that performs heavy computation. This library 
absolutely needs a PSR-16 CacheInterface to run. I don't have such an entry 
yet in my container. My framework notifies me that my container is in an 
inconsistent state and that I need to install another package to provide 
this PSR-16 CacheInterface entry (bonus point if the framework can propose 
a package that does this).


7: Ability to provide different services based on configuration

Based on configuration, a package should be able to register or not a set 
of entries.


*Use case*: 

I'm installing a database abstraction library (Doctrine DBAL for instance). 
For my application, I don't have one but 2 databases. I can add an array of 
connections in my configuration and the service provider can create in the 
container 2 separate connection entries.




That's it! That's my dream list of requirements for this PSR (this might 
actually span over several PSRs).


If we manage to agree upon such a cross-platform package format, I'm 
absolutely sure this might be absolutely beneficial to the PHP ecosystem.


The current proposal we are working on (container-interop/service-provider 
<https://github.com/container-interop/service-provider>) does correctly 
cover features 1 to 4, but does not cover 5, 6 and 7. So my current thought 
is we should work towards filling those features. Do you see any feature 
that should be part of the PSR I missed? Or do you think the scope is too 
wide?


++

David

Twitter: @david_negrier

Github: @moufmouf

-- 
You received this message because you are subscribed to the Google Groups "PHP 
Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/e08eb67e-7bc7-4e7e-bc49-3046caced1bf%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to