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

Optimize config processing #772

Merged
merged 8 commits into from
Nov 4, 2020

Conversation

murtukov
Copy link
Member

@murtukov murtukov commented Nov 3, 2020

Q A
Bug fix? no
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Documented? no

Config processors are runtime processors, that change the config of types during the initialization.
There are currently 3 config processors:

  • AclConfigProcessor - checks permissions to fields and if permission is not granted, it replaces the resolver with the following one:
    function (): void {
        throw new UserWarning('Access denied to this field.');
    }
  • PublicFieldsFilterConfigProcessor - this processor dynamically removes fields if the public callback returns false
  • WrapArgumentConfigProcessor - this processor wraps ALL resolver closures into another closure only to wrap the $args param into Argument object.

Although these processor could also be optimized (they don't have to be executed always), this PR is focused on other things.

If we look at any generated type (in the branch master), we can see that configs are wrapped into an arrow function:

final class AddressType extends InputObjectType implements GeneratedTypeInterface
{
    public const NAME = 'Address';
    
    public function __construct(ConfigProcessor $configProcessor, GlobalVariables $globalVariables = null)
    {
        $configLoader = fn() => [
            'name' => self::NAME,
            'fields' => fn() => [
                // ...
            ],
        ];
        $config = $configProcessor->process(LazyConfig::create($configLoader, $globalVariables))->load();
        parent::__construct($config);
    }
}

The webonyx/graphql-php library doesn't accept closures as config, but arrays. In the code above the $configLoader is passed to LazyConfig where it is unwrapped by calling:

$config = $configLoader($globalVariables);

LazyConfig is a simple class, that just takes a config closure, unwraps it, applies all 3 processors to it and returns config back as array. I don't know why config is wrapped into a closure, maybe for some historical reasons, because in previous versions it was used to pass $globalVariables to it, although we could just bind it with use:

final class AddressType extends InputObjectType implements GeneratedTypeInterface
{
    public const NAME = 'Address';
    
    public function __construct(ConfigProcessor $configProcessor, GlobalVariables $globalVariables = null)
    {
        $configLoader = function ($globalVariables) {
            return [
                'name' => self::NAME,
                'fields' => function() use ($globalVariales) { 
                    return [
                        // ...
                    ],
                }
            ];
        };
        $config = $configProcessor->process(LazyConfig::create($configLoader, $globalVariables))->load();
        parent::__construct($config);
    }
}

Whatever the reason was, wrapping configs into a closure and passing $globalVariables into LazyConfig is an unnecessary overhead, therefore the generated classes can look like this:

final class AddressType extends InputObjectType implements GeneratedTypeInterface
{
    public const NAME = 'Address';
    
    public function __construct(ConfigProcessor $configProcessor, GlobalVariables $globalVariables = null)
    {
        $config = [
            'name' => self::NAME,
            'fields' => fn() => [
                // ...
            ],
        ];
        $config = $configProcessor->process(LazyConfig::create($config))->load();
        parent::__construct($config);
    }
}

Furthermore the instantiation of the LazyConfig object doesn't have to be done inside the type's constructor, but inside the $configProcessor:

final class AddressType extends InputObjectType implements GeneratedTypeInterface
{
    public const NAME = 'Address';
    
    public function __construct(ConfigProcessor $configProcessor, GlobalVariables $globalVariables = null)
    {
        $config = [
            'name' => self::NAME,
            'fields' => fn() => [
                // ...
            ],
        ];
        parent::__construct($configProcessor->process($config));
    }
}

And after all these changes, if you look at the LazyConfig class, you will see that its task could be moved directly into ConfigProcessor. Therefore the LazyConfig class is not needed anymore and can be removed.

This PR removes the LazyConfig class and reduces the number of function calls by type instantiations.

Note: don't be confused with the name LazyConfig because it doesn't really mean lazy loading

@murtukov murtukov requested a review from mcg-web November 3, 2020 14:48
@mcg-web mcg-web merged commit 8d1d870 into overblog:master Nov 4, 2020
@murtukov murtukov deleted the refactor/remove-lazyconfig branch November 4, 2020 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants