cms icon indicating copy to clipboard operation
cms copied to clipboard

Allow registering already-instantiated custom GQL directives.

Open michaelrog opened this issue 3 years ago • 3 comments

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 Directive instances 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 avatar Dec 16 '22 22:12 michaelrog

@michaelrog Is there a reason this was submitted as a draft PR?

brandonkelly avatar Dec 23 '22 19:12 brandonkelly

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 the Directive class, which by default will just pass-through to the existing apply() (so this is non-breaking)
  • swap API usages of Directive::apply() for Directive::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?

michaelrog avatar Dec 23 '22 20:12 michaelrog

My current plan:

  • add an applyFromInstance() method to the Directive class, which by default will just pass-through to the existing apply() (so this is non-breaking)
  • swap API usages of Directive::apply() for Directive::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 private Directive::_applyStatic(), and add a branch in __callStatic() to route to that private method if apply() is called statically.
  • Add a branch in __call() that checks to see if the Directive has an instance apply() method. If so, run it. Otherwise, call apply() statically.
  • Swap internal API usages of static Directive::apply() to use instance Directive->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 Directive and 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...

michaelrog avatar Jan 08 '23 09:01 michaelrog