Skip to content
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

refactor!: [WIP] Exploring a ConnectionResolver 2.0 #2749

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from

Conversation

justlevine
Copy link
Collaborator

@justlevine justlevine commented Mar 4, 2023

What does this implement/fix? Explain your changes.

This PR explores what an "ideal" ConnectionResolver DX would look like if we didnt need to worry about breaking changes.

The AbstractConnectionResolver is an essential yet complex part of the codebase and API. The existing class complicates dx and makes it hard to extend, and also obfuscates what a holistic Connection Resolver would look like without the tech debt.

The goal is to is to use this as a springboard to map out a path to a theoretical breaking release and the nonbreaking improvements we can already make.

Guiding Principles

  • Class properties should only be instantiated once.
  • Class properties should only be accessed via public getters.
  • Public getters should not have side effects.
  • All logical methods should be easily overloadable by children classes without unnecessary boilerplate (e.g. hooks)

Implementation Details

For better reviewability, changes have been made in situ to AbstractConnectionResolver, and a ConnectionResolverShim exists separately to illustrate the necessary changes from the existing class.

The gist of these changes boil down to splitting existing methods into those that handle the actual logic, and wrappers that filter the data and set the properties.
A working set of public getter methods allows for simpler yet more powerful interaction with the resolver instance (either in a filter or in a resolve callback).

There is also a doc at /Data/Connection/connection-resolvers.md to explain the tricky lifecycle and best ways to interact with / extend the class.

To review

Note

Changes cherrypicked from this branch into separate PRs are rebased back into this PR.

To see the relevant diff of remaining changes visit https://github.com/wp-graphql/wp-graphql/pull/2749/files/7415eca0ebd3f24c6a5c5e64289be655431bac50..217135b426d039fb95a407d31ed0e574608a533b

To do

  • Test query classes defined in the AppContext.
  • Review method visibility.
  • Decide if existing getters that doesnt use class properties should be refactored to store the values on the instance.
  • Review the internals of the child resolver classes (as of now I didnt really pay much attention beyond the method signatures), expose new getters and extend-friendly methods, and improve filter parity.
  • Review implications of and on Add post type / taxonomy args for defining the GraphQL model #2466 (including possible ways to instantiate the class indeterminately from the resolveFn() ).
  • Take a look at 3rd party Connection Resolvers and see if there's anything else we can do to make their lives easier.
  • Fix/update doc-blocks.
  • Select candidates for cherry-picking.

Does this close any currently open issues?

Any relevant logs, error output, GraphiQL screenshots, etc?

(If it’s long, please paste to https://ghostbin.com/ and insert the link here.)

Any other comments?

  • ConnectionResolverShim is not a full working shim. Method signatures (visibility, arg/return types) need to be updated in the implementing class for it to work. In a true backwards-compatible shim, we would most likely make AbstractConnectionResolver our shim, and move the v2 to a new ConnectionResolver class.

Where has this been tested?

Operating System: Ubuntu 20.04 (wsl2 + devilbox + php 8.1.14)

WordPress Version: 6.4.3

@justlevine justlevine added Type: Enhancement New feature or request Status: Discussion Requires a discussion to proceed Compat: Breaking Change This is a breaking change to existing functionality Needs: Reviewer Response This needs the attention of a codeowner or maintainer. Component: Connections Issues related to connections scope: API Issues related to access functions, actions, and filters labels Mar 4, 2023
@justlevine justlevine force-pushed the proposal/v2-connection-resolver branch from 9c2ed83 to 45a36bf Compare May 14, 2023 09:44
src/Data/Connection/UserConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/TermObjectConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/TermObjectConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/MenuItemConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/CommentConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/CommentConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/PostObjectConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/PostObjectConnectionResolver.php Outdated Show resolved Hide resolved
@justlevine
Copy link
Collaborator Author

justlevine commented May 25, 2023

aad0106 refactors the ConnectionResolver to support overloading the $query_class on a resolver level. This differs from #2821 in several ways:

  1. The default $query_class is set in the resolver with the query_class() method. It returns null by default, and should be overloaded to return the name of the query class in a Resolver that require one.
protected function query_class() ?: string {
  return 'WC_Query';
}
  1. The property is set on the constructor, using get_query_class(). That method will use $context->queryClass before defaulting to query_class(), and wraps it in the graphql_connection_query_class filter.

  2. $resolver->set_query_class() can be used to overload the query class on a specific Resolver instance. You can pass an empty string to reset the query class to the default (i.e. ignoring the $context->queryClass or the results of the filter).

...$connectionConfig,
'resolve' => function( $source, $args, $context, $info ) {
  $resolver = new PostObjectConnectionResolver( $source, $args, $context, $info );
  
  $resolver->set_query_class( WC_Query::class ); // If "" is passed, revert to default query class.
  
  return $resolver->get_connection();
},
  1. When get_query() is run, a check will be made to is_valid_query_class() to make sure that:
  • There is only a query class is the connection requires it (i.e. if query_class() is not null).
  • The query class exists.
  • The query class is compatible with our Connection Resolver. This is checked with is_valid_query_class(), which should be overloaded if you're using a class that's not compatible with WP_Query.
protected function is_valid_query_class( string $query_class ) : bool {  
  return method_exists( $query_class, 'get_results' ); // Instead of the default `WP_Query::query()`
}

As a result of these changes:

  • query() is no longer a required abstract method; however if a ConnectionResolver does not use a query_class, an invariant error will be thrown if the default query() method is not overloaded.
  • Extending ConnectionResolver is now DRYer, since in most cases developers will only need to overload the query_class method, instead of the entire query().

* @throws \Exception
*/
public function get_query_args() {
public function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method prepare_query_args has 60 lines of code (exceeds 40 allowed). Consider refactoring.

*/
public function get_query_args() {
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method prepare_query_args has 101 lines of code (exceeds 40 allowed). Consider refactoring.

src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/MenuItemConnectionResolver.php Outdated Show resolved Hide resolved
*/
public function get_query_args() {
public function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

* @throws \Exception
*/
public function get_query_args() {
public function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 25 (exceeds 5 allowed). Consider refactoring.

/**
* {@inheritDoc}
*/
public function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
src/Data/Connection/ConnectionResolver.php Outdated Show resolved Hide resolved
/**
* {@inheritDoc}
*/
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

@justlevine justlevine changed the title dev!: [WIP] Exploring a ConnectionResolver 2.0 refactor!: [WIP] Exploring a ConnectionResolver 2.0 Sep 17, 2023
public function get_args(): array {
$args = $this->args;

protected function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_args has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.

public function get_args(): array {
$args = $this->args;

protected function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method prepare_args has 48 lines of code (exceeds 40 allowed). Consider refactoring.

*/
public function get_query_args() {
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 40 (exceeds 5 allowed). Consider refactoring.

$site_plugins = apply_filters( 'all_plugins', get_plugins() );
$mu_plugins = apply_filters( 'show_advanced_plugins', true, 'mustuse' ) ? get_mu_plugins() : [];
$dropin_plugins = apply_filters( 'show_advanced_plugins', true, 'dropins' ) ? get_dropins() : [];
protected function query( array $query_args ) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function query has a Cognitive Complexity of 38 (exceeds 5 allowed). Consider refactoring.

@justlevine
Copy link
Collaborator Author

rebased on develop.

@coveralls
Copy link

coveralls commented Sep 17, 2023

Coverage Status

coverage: 84.442% (+0.1%) from 84.33%
when pulling 025d45e on justlevine:proposal/v2-connection-resolver
into cb18964 on wp-graphql:develop.

@justlevine
Copy link
Collaborator Author

Even with the indeterminate model/dataloader pattern unsolved, I'd love to start backporting some of the nonbreaking changes (and close several existing Issues).

@jasonbahl What can I do to make this proposal easier for you to review and give feedback on?

@@ -39,13 +32,13 @@ public function get_loader_name() {
*
* @throws \Exception
*/
public function get_query_args() {
public function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method prepare_query_args has 70 lines of code (exceeds 40 allowed). Consider refactoring.

public function get_args(): array {
$args = $this->args;

protected function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_args has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.

*
* @throws \GraphQL\Error\InvariantViolation If the query class is invalid.
*/
protected function validate_query_class(): void {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function validate_query_class has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.

@@ -39,13 +32,13 @@ public function get_loader_name() {
*
* @throws \Exception
*/
public function get_query_args() {
public function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 26 (exceeds 5 allowed). Consider refactoring.

public function get_args(): array {
$args = $this->args;

public function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_args has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.

public function get_args(): array {
$args = $this->args;

public function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_args has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.

@justlevine justlevine force-pushed the proposal/v2-connection-resolver branch from c7ad0bf to 152cb6c Compare May 4, 2024 11:57
@@ -32,13 +32,13 @@ protected function loader_name(): string {
*
* @throws \Exception
*/
public function get_query_args() {
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method prepare_query_args has 70 lines of code (exceeds 40 allowed). Consider refactoring.

@@ -22,35 +22,35 @@ public function __construct( $source, array $args, AppContext $context, ResolveI
/**
* {@inheritDoc}
*/
public function get_query_args() {
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

@@ -40,15 +40,15 @@ public function __construct( $source, array $args, AppContext $context, ResolveI
/**
* {@inheritDoc}
*/
public function get_query_args() {
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

@@ -32,13 +32,13 @@ protected function loader_name(): string {
*
* @throws \Exception
*/
public function get_query_args() {
protected function prepare_query_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_query_args has a Cognitive Complexity of 26 (exceeds 5 allowed). Consider refactoring.

public function get_args(): array {
$args = $this->get_unfiltered_args();

protected function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_args has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.

@justlevine justlevine force-pushed the proposal/v2-connection-resolver branch from 152cb6c to eeaed24 Compare May 4, 2024 23:46
public function get_args(): array {
$args = $this->get_unfiltered_args();

protected function prepare_args( array $args ): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function prepare_args has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.

public function has_next_page() {
if ( ! empty( $this->args['first'] ) ) {
return ! empty( $this->ids ) && count( $this->ids ) > $this->get_query_amount();
public function has_next_page(): bool {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

public function has_previous_page() {
if ( ! empty( $this->args['last'] ) ) {
return ! empty( $this->ids ) && count( $this->ids ) > $this->get_query_amount();
public function has_previous_page(): bool {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

@justlevine
Copy link
Collaborator Author

Copy link

codeclimate bot commented May 5, 2024

Code Climate has analyzed commit 025d45e and detected 17 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 15
Duplication 2

View more on Code Climate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Compat: Breaking Change This is a breaking change to existing functionality Component: Connections Issues related to connections Needs: Reviewer Response This needs the attention of a codeowner or maintainer. scope: API Issues related to access functions, actions, and filters Status: Discussion Requires a discussion to proceed Type: Enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants