Service provider PSR: proposed goal and scope

90 views
Skip to first unread message

David Négrier

unread,
Mar 22, 2018, 6:11:13 AM3/22/18
to PHP Framework Interoperability Group
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 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 (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 takes care of registering the bundle for you. Same thing with Laravel Service Providers since Laravel 5.5 or with Zend Framework. 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) 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

Pierre Mallinjoud

unread,
Mar 27, 2018, 5:47:30 AM3/27/18
to PHP Framework Interoperability Group
Hi David,

I agree with all the points but I think some are not the packages responsibility:

Point 2:

> The package should be able to declare that the service it provides is the default implementation for a given interface.

It is a bit unclear here. I think declaring which implementation of an interface is the default one should take place at the project level. Because every Psr-3 package can claim it is the default one.

So yes, a package should be able to declare it provides an implementation of LoggerInterface, but then there should be a mechanism controlled by the project developer to declare which one is the default one, in case many are installed.

Point 6 use case 2:

> If a package has requirements ... it should be able to publish those programmatically to the underlying framework.

Ok so requirements can be published and frameworks can notify me when they are missing. On the other hand, what if I have installed multiple packages which can fulfill those requirements? Obviously I don't want to use the default one every time. At the project level I should be able to choose which packages to use to fulfill another one requirements.

Point 3:


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

I also think this is not the package responsibility. Because here packages should assume there is a common tag shared by all the packages of the same kind. Again it is at the project level I want to be able to group things together.


To sum this up I think there is a "gluing" aspect which is not the package responsibility.

I think this psr should either present a full solution to this "gluing" issue or just expose enough info for some other thing to solve it.
Reply all
Reply to author
Forward
0 new messages