-
Notifications
You must be signed in to change notification settings - Fork 11.5k
[12.x] Introduce #[Bind]
attribute
#56383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@cosmastech how hard would it be to add the environment stuff in? I think it's pretty key to using this feature. |
Not hard, but tell me which you prefer: Only allow the attribute once #[Bind([
ProductionTwChatService::class => ['staging', 'production'],
StubTwChatService::class => ['local', 'testing'],
])]
interface ChatServiceInterface
{
// ...
} vs #[Bind(ProductionTwChatService::class, environments: ['staging', 'production'])]
#[Bind(StubTwChatService::class, environments: ['local', 'testing'])]
interface ChatServiceInterface
{
// ...
} Additionally: How do we signify the default fall-through if the environment is not specified? Another question is: since the Container class doesn't have access to the environment, it would have to be handled by the Application class instead. What do we do in that case? |
@cosmastech I think |
I'll mark this as draft and get to to work on it. 🙇 Thanks @taylorotwell |
Thanks - added enum support to environments 👍 |
Me--of all people--not thinking of adding enum support 😂 Let me know if you want me to take a tumble at adding docs for this. Thanks @taylorotwell 👋 See you at Laracon! |
I think this PR adds another way to do something that already works well, which could introduce unnecessary complexity. Supporting multiple ways to accomplish the same task can make the framework harder to refactor, maintain, and extend over time. As Taylor Otwell has often emphasized:
I think we need to revert this one. |
Similar to #56334, now a developer can bind a specific concrete implementation to an interface via an attribute.
In the above scenario, if the environment is staging or production, then calling
app(ChatServiceInterface::class)
will returnTwChatService
. For all other environments (eg: dev or testing),FakeTwChatService
would be returned.Why?
Our biggest use of interfaces is to provide a production version of the code, and then inside of tests, we swap out with a test implementation. All of our code type-hints the interface, but in order to figure out which concrete implementation it is, we have to jump back to the service provider to see where it's bound. This keeps it tidy and in one place.
Technical Decisions
We are keeping a cache of class strings which we have already checked for the
Bind
attribute. This cuts down on repeated reflection if we already know the class does not have the attribute. While it will increase the memory footprint of the Container class slightly, it should improve performance on repeated instantiation of objects. Maybe this should be a static property instead? 🤔If using the container outside of the Laravel Application, then you must manually set the environment resolver. If none is set, then the attributes will be ignored.
Wildcards
We are using fuzzy-matching (via the
Str::is()
method). If all environments are passed, then we will still compare all of the attributes to see if there is a more specific match.For the above,
app(MyInterface::class)
will returnMyProdClass
if the environment is production, butMyDummyClass
for all other environments.Improvements
The scoped/singleton property could possibly be shoe-horned in here.