Shared instances and ContainerInterface


#1

Hi,

after playing with containers in zend-expressive and noticing some inconsistencies between container implementations I would like to know how you do to make a instance shareable between calls for container->get() independently of the container you are using. Is that possible?

PSR-11 suggests (SHOULD) to return an already instantiated instance if one was already created by the container. It means it should return one instance for the same container->get() calls. Since it is a suggestion, it’s OK for container implementations to work differently.

It means it would be our responsibility to assure that it works the way we want consistently. In other words, Expressive should take care of that:

  1. Expressive should have a way (common to all containers) to express if an instance will be shared or not.

and:

  1. If an instance are configured one way or another. Expressive should allow specific calls to $container->get() to work differently based on the developer wiliness. Probably using extra (and optional) parameters to the ->get() call.

What do you think?


#2

I would like to know how you do to make a instance shareable between
calls for container->get() independently of the container you are using

PSR-11 didn’t enforce any of this by design, since some containers may act
as factories.

There is no guarantee about that, and the language doesn’t provide a
"Memoize" marker that we could use with methods.

In general, I don’t think that shared/unshared enforcement is required. I
would suggest that you show us your specific use-case scenario, and then we
see if there’s a better workaround (that doesn’t rely on shared mutable
state).


#3

SHOULD has a deeper meaning than you might think:

SHOULD This word, or the adjective “RECOMMENDED”, mean that there
may exist valid reasons in particular circumstances to ignore a
particular item, but the full implications must be understood and
carefully weighed before choosing a different course.

So unless there is a good reason, get() returns the same instance. If you ran into inconsistencies, would you mind to explain that a bit more? It might be a bug in expressive-skeleton or maybe one of the containers. As far as I know, instances are shared by default.

In case of zend-servicemanager you can configure if an instance is shared or not. So the developer is explicitly configuring the container to not share a specific instance. And I think that’s also where the responsibility is: It’s for the developer and not expressive to do that.

I haven’t seen any share/unshare functionality in the Aura.DI docs. So I guess if you want the same functionality in other containers, you would need to create PR’s to the other containers.

Luckily for us there are several containers available. You are free to choose the one you are comfortable with and has the functionality you are looking for. Enforcing all containers to have the exact same functionality is not what the PSR’s are about, nor should they.


#4

That is kinda besides the point: the code should keep working with any
container


#5

My use case is I have a service (AuthorizationService) that is required in more than one place in the same request. Even caching the acl object it still an expensive service to create more than once per request. So I would like to share that instance so it needs to be built just once.

Another example would be the router that if created twice, the second time I request it, it won’t have the routes attributed to it if the instance is not shared.

I’m using auryn but I guess the problem is independent of the implementation I’m using. Even if others do it differently it would need some way to change the default behaviour.

When looking into it a little bit deeper I have found this:

http://zendframework.github.io/zend-servicemanager/configuring-the-service-manager/

shared and shared_by_default is exactly what I was looking for. I guess that if a container is working differently than what is describe here:

By default, a service created is shared. This means that calling the get() method twice for a given service will return exactly the same service. This is typically what you want, as it can saves a lot of memory and increase performance.

it should be considered a bug. Right?


#6

Ah right, auryn. I remember you now from that Github pull request. I haven’t used auryn myself so I have no idea what it’s behavior is or how to set it up. Since that code in that PR isn’t even merged I don’t know if there is a possible bug in setting up the container, the configuration, in auryn itself or in the northwoods/container wrapper.

You can try to debug it and figure out how to setup auryn with shared instances by default, or save yourself the trouble and try one of the currently supported containers. You can swap later if that container gets added to the skeleton and is proven to be working correctly.

Edit: Looks like auryn itself can share instances https://github.com/rdlowrey/auryn#instance-sharing


#7

Hi @xtreamwayz, the auryn is no problem here. I know how to share instances with it, I’m already doing it. I was just expecting some “expressive” way (a configuration gateway) to do it that wouldn’t require a refactor if I changed the container implementation. And a default behavior we can use to configure the containers. Since the PSR doesn’t enforce it (it recommends), it could be enforced by the framework to consider a container compatible or not.


#8

What we did so far is set the zend-servicemanager configuration as a default. And then for for pimple and aura.di we translated that config and build the container. I guess the same can be done for sharing instances.

'shared' => [
    stdClass::class => false
]

Translate that part to the containers that support it.

So here is the tricky part. The code that is building the pimple and aura.di containers is inside the skeleton package. That is something we control and can adjust. However the auryn wrapper is a work in progress and is a 3rd party initiative. You can ask the maintainer to add this shared behavior.

But I think I finally get what you mean. I haven’t looked into that auryn PR since the last time I couldn’t get it working. If its behavior is different then the current 3 supported containers, it’s definitely a reason to reject that PR until it acts the same.

To enforce the same behavior, we could setup a few unit tests to make sure containers act the same.

On a side note: I think the code to build the pimple and aura.di containers should be refactored into their own repositories as well. Right now if there is a bug in that code we can’t push fixes.


#9

Just cross-linking: https://github.com/zendframework/zend-expressive-skeleton/issues/167


#10

Hello, I’m the creator northwoods/container and the person working on adding to expressive. I missed the part that instances were meant to be shared inside the container by default. Feel free to create an issue on northwoods/container to address this.