Allow registering already-instantiated custom GQL directives.
Currently, it's only possible to define custom GQL directives statically, by providing a class name, on which Craft expects to invoke Directive::create().
But what if we want to generate custom directives dynamically?
- We will need the ability to register already-instantiated
Directiveinstances in addition to static class names. - And, we will need to move apply() into the instance context, to involve instance-specific behavior, rather than just static logic.
@michaelrog Is there a reason this was submitted as a draft PR?
Is there a reason this was submitted as a draft PR?
After I submitted the original PR, I realized there was more to the problem, so I converted it back to a draft.
Initially, I thought I could just amend the registration logic to accept already-instantiated Directives. However, since apply() is static, allowing users to construct their own instances doesn't ultimately gain us anything, because the Directive can't use instance-specific behavior when applied.
My current plan:
- add an
applyFromInstance()method to theDirectiveclass, which by default will just pass-through to the existingapply()(so this is non-breaking) - swap API usages of
Directive::apply()forDirective::applyFromInstance() - deprecate
Directive::apply()in favor of using instance methods going forward.
@brandonkelly Does this sound like an acceptable shift? Or, is there a reason to prefer Directive::apply() being static in the first place?
My current plan:
- add an
applyFromInstance()method to theDirectiveclass, which by default will just pass-through to the existingapply()(so this is non-breaking)- swap API usages of
Directive::apply()forDirective::applyFromInstance()- deprecate
Directive::apply()in favor of using instance methods going forward.
Maybe we can actually accomplish a static->instance transition without requiring a new method name, by using __call() and __callStatic() to route to the appropriate logic based on the context.
That way, we don't have to create, and subsequently deprecate and remove, a temporary instance method.
It'd be a bit hacky. Something like:
- Rename the current (static)
Directive::apply()to a privateDirective::_applyStatic(), and add a branch in__callStatic()to route to that private method ifapply()is called statically. - Add a branch in
__call()that checks to see if the Directive has an instanceapply()method. If so, run it. Otherwise, callapply()statically. - Swap internal API usages of static
Directive::apply()to use instanceDirective->apply() - Maybe file a deprecation warning if
Directive::apply()is called statically, to encourage third-party updates.
This should still be backwards-compatible:
- The base
Directiveand native directives will all use the instance->apply()going forward. - Third-party directives can update to using an instance method at/before the next major break.
- Third-party directives that haven't updated will try to call the old static
::apply(), which will get handled virtually via__callStatic().
I don't love it, but I think it'd work, and might be the lowest-friction "just works" way forward...