{"action":"created","issue":{"url":"https://api.github.com/repos/container-interop/service-provider/issues/44","repository_url":"https://api.github.com/repos/container-interop/service-provider","labels_url":"https://api.github.com/repos/container-interop/service-provider/issues/44/labels{/name}","comments_url":"https://api.github.com/repos/container-interop/service-provider/issues/44/comments","events_url":"https://api.github.com/repos/container-interop/service-provider/issues/44/events","html_url":"https://github.com/container-interop/service-provider/issues/44","id":273940441,"number":44,"title":"Advocating for a ->getRequirements() method","user":{"login":"pmall","id":741677,"avatar_url":"https://avatars2.githubusercontent.com/u/741677?v=4","gravatar_id":"","url":"https://api.github.com/users/pmall","html_url":"https://github.com/pmall","followers_url":"https://api.github.com/users/pmall/followers","following_url":"https://api.github.com/users/pmall/following{/other_user}","gists_url":"https://api.github.com/users/pmall/gists{/gist_id}","starred_url":"https://api.github.com/users/pmall/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pmall/subscriptions","organizations_url":"https://api.github.com/users/pmall/orgs","repos_url":"https://api.github.com/users/pmall/repos","events_url":"https://api.github.com/users/pmall/events{/privacy}","received_events_url":"https://api.github.com/users/pmall/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":7,"created_at":"2017-11-14T20:58:44Z","updated_at":"2017-11-16T13:43:32Z","closed_at":null,"author_association":"NONE","body":"Im a huge supporter of the service provider autoloading idea. But no matter how hard I'm trying to implement it in my own personal projects, there is some issues which cant be avoided. Lets face it: it will allways be up to the end developer to choose which implementation to use for some dependencies. What if we specify a way for service provider to declare those dependancies?\r\n\r\nLet's say I want my session, cache and queue management based on redis. Obviously I can require a package providing a session handler, one providing a Psr-6 cache and another one providing a queue management service, all three requiring a Predis client instance. Some autoloader can automatically provide those three packages service providers to my project container but there is absolutely no way in the current specification for them to know what concrete instance of Predis client they should get from the container. My assumption here is there will never be a way because it is up to the end developer to choose how many redis servers he wants to use and which one go with one service.\r\n\r\nMy wish is to have the container to fail nicely when a requirement is not met. By nicely I mean throwing an exception containing all the info a clever framework would need to catch it and indicate to the end developer what he should do in order to make everything works.\r\n\r\nAlso - this must be noted but I didn't made my mind about it - based on this assumption, this is a bad practice to use `\\Predis\\Client::class` as a factory id (following the previous example), because there is no such thing as universal redis connection, only dependencies to higer order services. Unless you want to play around with a single Predis client without injecting it. This is wried.\r\n\r\nI think service providers should have the ability to tell they need a specific class instance to be mapped to a package vendor specific id. Then it is up to the end developper application service provider to associate a factory to this id. This is what I learnt from using the service provider interface, I'm very open to hear suggestions which would prove me wrong.\r\n\r\nAlso I read somewhere about havig one scoped container per service provider/package (@mindplay-dk). As I think the concept is elegant, I have a hard time figuring out how it would work. I guess it would be hard to implement. Also who need a scoped container if we have scoped ids? Not sure about this but I throw the idea.\r\n\r\nMy proposal here is a simple `->getRequirements(): array` method can solve a lot of issues regarding its simplicity. Lets go back to the above example. I want session, cache and queues to be handled by Predis clients. Obviously I don't want to code session handler, Psr-6 caches and queue manager so I autoload the three packages. I is up to me to specify which Predis instance should go with each service:\r\n\r\n```php\r\n<?php\r\n\r\nclass RedisSessionHandlerServiceProvider implements ServiceProviderInterface\r\n{\r\n    public function getRequirements()\r\n    {\r\n        return [\r\n            \\Predis\\Client::class => '{session.vendor.namespace}.client',\r\n        ];\r\n    }\r\n\r\n    public function getFactories()\r\n    {\r\n        return [\r\n            // As we say so we expect a redis connection in '{vendor.session.handler}.client' id.\r\n            RedisSessionHandler::class => function ($container) {\r\n\r\n                $client = $container->get('{session.vendor.namespace}.client');\r\n\r\n                return new RedisSessionHandler($client);\r\n\r\n            },\r\n        ];\r\n    }\r\n}\r\n```\r\n\r\n`'{vendor.session.handler}'` is a placeholder for example. Would be replaced with a concrete string. Same for the cache implementation and the queue management service provider.\r\n\r\nThen in your app, you map factories with the vendor ids:\r\n\r\n```php\r\n<?php\r\n\r\nclass AppServiceProvider implements ServiceProviderInterface\r\n{\r\n    public function getRequirements()\r\n    {\r\n        return [];\r\n    }\r\n\r\n    public function getFactories()\r\n    {\r\n        // Here we map the session and cache to the same Predis client but the\r\n        // queue one to a different one. This is what the developer wants there\r\n        // is no way an autoloaded package can figure this out.\r\n        return [\r\n            '{session.vendor.namespace}.client' => function ($container) {\r\n\r\n                return $container->get('app.redis.default');\r\n\r\n            },\r\n\r\n            '{cache.vendor.namespace}.client' => function ($container) {\r\n\r\n                return $container->get('app.redis.default');\r\n\r\n            },\r\n\r\n            '{queue.vendor.namespace}.client' => function ($container) {\r\n\r\n                return $container->get('app.redis.queue');\r\n\r\n            },\r\n\r\n            'app.redis.default' => function () {\r\n\r\n                return new \\Predis\\Client(...);\r\n\r\n            },\r\n\r\n            'app.redis.queue' => function () {\r\n\r\n                return new \\Predis\\Client(...);\r\n\r\n            },\r\n        ];\r\n    }\r\n}\r\n```\r\n\r\nThis is the simplest approach and yet we can take benefits from it. It is so simple to implements for containers they could register all the factories then inspect all the requirements. When one is missing it can fail nicely by telling which service provider requires an instance of what class under what id. What do you think about this?\r\n"},"comment":{"url":"https://api.github.com/repos/container-interop/service-provider/issues/comments/344926195","html_url":"https://github.com/container-interop/service-provider/issues/44#issuecomment-344926195","issue_url":"https://api.github.com/repos/container-interop/service-provider/issues/44","id":344926195,"user":{"login":"mindplay-dk","id":103348,"avatar_url":"https://avatars0.githubusercontent.com/u/103348?v=4","gravatar_id":"","url":"https://api.github.com/users/mindplay-dk","html_url":"https://github.com/mindplay-dk","followers_url":"https://api.github.com/users/mindplay-dk/followers","following_url":"https://api.github.com/users/mindplay-dk/following{/other_user}","gists_url":"https://api.github.com/users/mindplay-dk/gists{/gist_id}","starred_url":"https://api.github.com/users/mindplay-dk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mindplay-dk/subscriptions","organizations_url":"https://api.github.com/users/mindplay-dk/orgs","repos_url":"https://api.github.com/users/mindplay-dk/repos","events_url":"https://api.github.com/users/mindplay-dk/events{/privacy}","received_events_url":"https://api.github.com/users/mindplay-dk/received_events","type":"User","site_admin":false},"created_at":"2017-11-16T13:43:32Z","updated_at":"2017-11-16T13:43:32Z","author_association":"NONE","body":"> What is the point of a has() check when the get() check potentially can still fail?\r\n\r\nThat's not what I'm proposing - a `has()` check is an immediate check, at call to `depends()` is you ordering the container-factory to validate itself before creating a container, e.g. when you're done bootstrapping everything.\r\n\r\n> Just wondering if that idea really makes sense or if it is not sufficient to throw exceptions in the callables when dependencies cannot be retrieved?\r\n\r\nThat's already what happens automatically in almost every container implementation.\r\n\r\nIt's not sufficient, because missing dependencies will not surface before you hit a feature in your app that depends on one of the missing dependencies - you won't know about missing dependencies before it's too late. Knowing at container instanciation that something is missing would be much more meaningful.\r\n"}}
{"actor":{"display_login":"mindplay-dk"}}