Juste
Juste

Reputation: 5

The contents of the service container differ for no obvious reason

I work with Symfony 5.1.2 and my tests worked until I introduced a chain of user providers. I derive all of my test classes from a class I created in order to put common methods and properties. Among these methods there is a method that I use to connect a user.

public function connectUser(string $username)
{
    $userProvider = static::$container->get(UserProviderInterface::class);
    $user = $userProvider->loadUserByUsername($username);
    $this->assertNotNull($user);
    $this->kernelBrowser->loginUser($user);
}

With the following settings there were no problems

providers:
    backend_users:
        memory:
            users:
                [email protected]: { password: '$argon2id$v=19$m=65536,t=4,p=1$c0F2RmVYa21RclE4ZXJkTA$mvXd/skXaV9w1rqmWb6B5MTtgkP86inWSkj0E8hjtTA', roles: ['ROLE_ADMIN'] }
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        anonymous: true
        lazy: true
        logout:
            path: security.authentication.logout
        provider: backend_users
        guard:
            authenticators:
                - App\Security\LoginFormAuthenticator

I then introduced a new user source ; a database.

With the following settings there is a problem

providers:
     # used to reload user from session & other features (e.g. switch_user)
    backend_users:
        memory:
            users:
                [email protected]: { password: '$argon2id$v=19$m=65536,t=4,p=1$c0F2RmVYa21RclE4ZXJkTA$mvXd/skXaV9w1rqmWb6B5MTtgkP86inWSkj0E8hjtTA', roles: ['ROLE_ADMIN'] }
    frontend_users:
        entity:
            class: App\Entity\User
            property: email
    all_users:
        chain:
            providers: ['backend_users','frontend_users']
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        anonymous: true
        lazy: true
        logout:
            path: security.authentication.logout
        guard:
            authenticators:
                - App\Security\LoginFormAuthenticator
        provider: all_users

With this configuration, the service container no longer seems to contain the UserProviderInterface class. Because I receive this message :

You have requested a non-existent service "Symfony\Component\Security\Core\User\UserProviderInterface".

I figured I might need to implement a custom user provider, but after doing some research in the source code, I realized that there is a class that seems specific to user provider chains that is :Symfony\Component\Security\Core\User\ChainUserProvider

As the name suggests, the UserProviderInterface class is an interface, so I'm not supposed to worry about the internal implementation.

Why is this interface no longer in the service container ? How to reintroduce it properly ?

Thank you !

Upvotes: 0

Views: 138

Answers (1)

Cerad
Cerad

Reputation: 48865

Suppose you have multiple providers all implementing UserProviderInterface. When you type hint against the interface, which service do you want injected and how would the container know? The container does not know anything about your firewalls so it can't guess that you want the chain provider. So things worked when you only had one provider but will fail when you have multiple providers.

The same question arises anytime you have multiple implementations of the same interface. You either need to typehint against a specific implementation or inject the desired service manually or create an alias which will tie the interface to one specific implementation.

In your case:

bin/console debug:container | grep UserProvider
  doctrine.orm.security.user.provider                                                  Symfony\Bridge\Doctrine\Security\User\EntityUserProvider                                        
  security.user.provider.chain                                                         Symfony\Component\Security\Core\User\ChainUserProvider                                          
  security.user.provider.concrete.all_users                                            Symfony\Component\Security\Core\User\ChainUserProvider                                          
  security.user.provider.concrete.backend_users                                        Symfony\Component\Security\Core\User\InMemoryUserProvider                                       
  security.user.provider.concrete.frontend_users                                       Symfony\Bridge\Doctrine\Security\User\EntityUserProvider                                        
  security.user.provider.in_memory                                                     Symfony\Component\Security\Core\User\InMemoryUserProvider                                       
  security.user.provider.ldap                                                          Symfony\Component\Ldap\Security\LdapUserProvider                                                
  security.user.provider.missing                                                       Symfony\Component\Security\Core\User\MissingUserProvider                                        
  security.user_providers                                                              Symfony\Component\Security\Core\User\ChainUserProvider             

If you always want the all_users chain provider to be injected then add an alias:

config/services.yaml
    Symfony\Component\Security\Core\User\UserProviderInterface : '@security.user.provider.concrete.all_users'

And you should be good to go.

Upvotes: 2

Related Questions