-
Notifications
You must be signed in to change notification settings - Fork 97
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
Immutability #584
Immutability #584
Conversation
|
Of note, on the documentation, we only need to update the current src. The versioned docs shouldn't be edited since they represent the defined version, which is already locked in functionality. They only need to be updated for mistakes, not changes to the API. |
Also, when it comes to defining the public API, I realize there are 2 camps. With the addition of I realize this may be where we're headed in PHP land, and it may take some time before that's a standard, if ever. The issue I take with this is that is splits your API into methods and properties and it's not always clear which you should be looking for, especially when using something like autocomplete. Sometimes you may need a getter to massage the output from a property, making it private, and other times you may not, relying on that property being public. This leaves the dev wondering and it's a bit chaotic IMO. In short, I'm not sold on the trend of moving to public properties for defining the API. It's neat, clean, less code - yes. But, it can also be confusing. Further, most of GraphQLite already uses getters/setters, so a move away from this only bifurcates the public API further. |
We can do without a separate annotation, looking for
Yep that is the result of "Replace all ocurrences", I'll fix that.
That's exactly why I proposed refactoring I can't say I'm entirely sold either. I've worked on other internal packages and I've managed to avoid getters altogether, aside from interfaces of course. I think there are three main use cases for them:
I believe that with careful planning, breaking changes can be (mostly) avoided. All of that said, I completely understand your concerns so I can revert the removal of getters for public APIs. If so, do you think this should also be applied to internal APIs or should we drop the getters (as part of a different PR, of course)? |
It sounds like you're trying to reverse the order of processing for prefetching. Instead of defining how a prefetch is handled for a given field, on that field, you're wanting to add a middleware that will prefetch for all fields based on some logic? On the APIs, public and internal, I think we should stick with getters for defining the API. Going immutable will obviously eliminate setters. We don't need getters for every property internally either. That can be something that's selectively chosen when defining the API. |
Not really, the order and all of the behaviour will stay exactly the same. I'm just not satisfied with the implementation of it. Currently public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, Type|null $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $parameterMapper): ParameterInterface
{
$prefetchAttribute = $parameterAnnotations->getAnnotationByType(Prefetch::class);
if (!$prefetchAttribute) {
return $parameterMapper->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);
}
return new PrefetchDataParameter($prefetchAttribute->method, $prefetchAttribute->args);
} and a public function resolve(object|null $source, array $args, mixed $context, ResolveInfo $info, QueryField $field = null): mixed
{
$prefetchBuffer = $context->getPrefetchBuffer($field);
$prefetchBuffer->register($source, $args); // <--- this is what `QueryField` is currently responsible for
// QueryField also defers the resolver for prefetch-annotated fields, which could be done on parameter level
return new Deferred(function () {
if (! $prefetchBuffer->hasResult($args)) {
$prefetchResult = $this->computePrefetch($source, $args, $context, $info, $field, $prefetchBuffer);
$prefetchBuffer->storeResult($prefetchResult, $args);
}
return $prefetchResult ?? $prefetchBuffer->getResult($args);
});
} That would allow future features that require deferring the parameter resolution possible. And not only in the
Got it. I'll revert the getters then on this PR, then submit two more PRs: one for resolvers, the other for |
The problem with not having a /**
* @Field(prefetchMethod="prefetchContacts")
*/
public function repeatName($arg1, $arg2, $arg3): string // <-- of the top of your head,
// which of the arguments is the prefetch result?
with prefetch that becomes obvious (and again, trivial to detect for a middleware like /**
* @Field(prefetchMethod="prefetchContacts")
*/
public function repeatName($arg1, #[Prefetch('something')] $arg2, $arg3): string This problem is avoided in other places of |
@oprypkhantc not opposed to a In designing it, I think the |
This is somewhat done. A PR for prefetch & resolvers refactor is coming; for now I've used some workarounds (with comments and TODOs for them). Aside from |
Regarding |
Also while working on I believe the more logical solution is to call the method statically when only a method name is specified. Not only will this avoid problems for the users, it'll also simplify the code for graphqlite. Obviously when a full callable is specified we can just resolve the class from the container instead of calling the method statically. Wdyt? |
Are there any issues or limitations in keeping it deprecated? It'd be ideal to deprecate before removal. |
No I don't think so; I'll add deprecations then. Also is there a point in supporting the annotation version of |
Annotation support is already deprecated. No need to add an annotation. https://graphqlite.thecodingmachine.io/docs/doctrine-annotations-attributes |
@oprypkhantc We still need to get that deprecation added. Is there anything else remaining on this? |
@oojacoboo Are you talking about Prefetch-related deprecations? If so, this is included in a different in a different PR, where I refactored Prefetch: https://github.com/thecodingmachine/graphqlite/pull/588/files#diff-44cccfa8f1939494e34146f50de7ef43e88de7e1789ddc53ec4dd4a813bed6c8R67 |
@oojacoboo Hey. Any chance to review this and after this - the prefetch refactor? There are some other issues I'd like to tackle, but first I want these to get merged. |
@oprypkhantc this looks great :) My apologies for the delay in reviewing this. I'll get the others reviewed as well and look forward to any additional PRs. Before merging this one, can you confirm that we have adequate test coverage for |
No need to apologize, I know the drill :) Overall coverage is reported at 96% lines and all of the prefetch seems to be covered, but it's hard to tell for sure because tests don't use Should we reorganize tests a bit? Separate fixtures, unit tests and integration tests and add |
That'd certainly be nice. It'd give us a little more confidence with these merges. Everything else looks good though. I'll go ahead and merge and we can monitor this on master for a bit before tagging a release. |
Thanks! It'd also be awesome to include prefetch and resolvers refactor in that release as well. |
Yes - agreed. |
Closes #581
This is still a wip, but I need your feedback to finish it. Don't mind the code style & PHPStan, I'll fix it later.
A couple of notes on the implementation:
Cloneable
is a tiny temporary workaround to allow cloning readonly objects. Once PHP 8.3 is out, it can be refactored to regularclone + $this->prop = $prop
combinationResolverInterface
instances on each resolve to avoid mutations, so instead went with passing theobject $source
parameterResolverInterface::executionSource
is the replacement forResolverInterface::getObject()
- it allows getting the object that the field/method is called on. This is used by#[Security('this.someMethod()')]
and for prefetching data#[Field(prefetchMethod: 'someMethodOnThis')]
QueryFieldDescriptor
andInputFieldDescriptor
are gone since properties are now readonlyAnd a lot of questions @oojacoboo:
Currently
prefetch
is tied intographqlite
, such that it's impossible to implement such a feature in userland: there a hard dependency on it inQueryField
. What do you think of extracting it into a separate attribute and a parameter middleware? Something like#[Prefetch('methodName', ['arg1'])]
+PrefetchParameterMiddleware
? That would simplify theQueryField
further and allow implementation of similar features in userland, withoutgraphqlite
having to implement them. I know you're against adding more attributes, but this seems logical. ExistingprefetechMethod
andprefetechArgs
parameters of#[Field]
can be deprecated and removed later on.Although
ResolverInterface::executionSource
works, I think it's worthwhile thinking on how to improve that. There are multiple problems currently:null
in resolvers, because of the__invoke(object|null $source)
signature forced byServiceResolver
, which doesn't need a source. All other resolvers do need one and have ugly runtime checks->setCallable()
onQueryFieldDescriptor
which duplicates the logic of some of the existing resolversWhat do you think of refactoring them too in the scope of 7.x? I have a few ideas on mind.
QueryFieldDescriptor
andInputFieldDescriptor
are both bloated with logic of selecting the$originalResolver
, being passed 6 properties$callable
,$targetMethodOnSource
,$targetPropertyOnSource
,$magicProperty
,$refMethod
and$refProperty
solely for that purpose. Instead of doing so we can just accept the$originalResolver
as a constructor parameter and make respective properties of resolverspublic readonly
, so that in the rare cases it's needed they can just do$descriptor->originalResolver->methodName
I've only added
readonly
in places where I refactored things, but it's probably worthy adding everywhere, also dropping the getters. Maybe as part of a different PR. Thoughts?