w7corp/easywechat · error · InvalidArgumentException

The factory must return a

Error message

The factory must return a %s instance.

What it means

Application::getOAuth() supports a custom OAuth provider factory via setOAuthFactory(). After invoking the factory it requires an instance of Overtrue\Socialite\Contracts\ProviderInterface; anything else (another class, null, a builder object) triggers InvalidArgumentException. The guard exists because the rest of the OAuth flow (redirect(), userFromCode()) only works against the Socialite provider contract.

Solutions

  1. Return the provider object from the factory closure, e.g. return (new WeChat([...]))->scopes([...])
  2. Implement Overtrue\Socialite\Contracts\ProviderInterface on the custom class
  3. Type the closure's return (fn (): ProviderInterface => ...) so PHP/static analysis rejects mismatches before runtime

Example fix

// before
$app->setOAuthFactory(fn ($app) => new MyCustomSso()); // not a Socialite provider

// after
$app->setOAuthFactory(
    fn ($app) => (new \Overtrue\Socialite\Providers\WeChat([
        'client_id'     => $app->getAccount()->getAppId(),
        'client_secret' => $app->getAccount()->getSecret(),
        'redirect_url'  => 'https://example.com/oauth/callback',
    ]))->scopes(['snsapi_userinfo'])
);
Defensive patterns

Strategy: type-guard

Type guard

function isSocialiteProvider(mixed $provider): bool
{
    return $provider instanceof \Overtrue\Socialite\Contracts\ProviderInterface;
}

$provider = ($factory)($app);
if (! isSocialiteProvider($provider)) {
    throw new \UnexpectedValueException(
        'OAuth factory returned '.get_debug_type($provider).', expected ProviderInterface.'
    );
}

Try / catch

try {
    $provider = $app->getOAuth();
} catch (\EasyWeChat\Kernel\Exceptions\InvalidArgumentException $e) {
    throw new \UnexpectedValueException('Custom OAuth factory must return a Socialite provider; got '.get_debug_type($factoryResult), 0, $e);
}

Prevention

When it happens

Trigger: setOAuthFactory(fn ($app) => new MyProvider(...)) where MyProvider does not implement ProviderInterface; a factory closure that forgets its return statement; a factory returning a config array or builder instead of the provider object.

Common situations: Swapping in a custom or extended WeChat provider; refactoring a closure so the return disappears; test factories returning mocks of the wrong interface.

Related errors


AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21). Data as JSON: /api/errors/800c70b1a312b3a8. Report an issue: GitHub.

Appendix: source

Thrown at src/OfficialAccount/Application.php:179

    /**
     * @throws InvalidArgumentException
     */
    public function getOAuth(): SocialiteProviderInterface
    {
        if (! $this->oauthFactory) {
            $this->oauthFactory = fn (self $app): SocialiteProviderInterface => (new WeChat(
                [
                    'client_id' => $this->getAccount()->getAppId(),
                    'client_secret' => $this->getAccount()->getSecret(),
                    'redirect_url' => $this->config->get('oauth.redirect_url'),
                ]
            ))->scopes((array) $this->config->get('oauth.scopes', ['snsapi_userinfo']));
        }

        $provider = call_user_func($this->oauthFactory, $this);

        if (! $provider instanceof SocialiteProviderInterface) {
            throw new InvalidArgumentException(sprintf(
                'The factory must return a %s instance.',
                SocialiteProviderInterface::class
            ));
        }

        return $provider;
    }

    public function getTicket(): JsApiTicketInterface|RefreshableJsApiTicketInterface
    {
        if (! $this->ticket) {
            $this->ticket = new JsApiTicket(
                appId: $this->getAccount()->getAppId(),
                secret: $this->getAccount()->getSecret(),
                cache: $this->getCache(),
                httpClient: $this->getClient(),
                stable: $this->config->get('use_stable_access_token', false),
            );

View on GitHub (pinned to f0cf0a8b83)