Strep throat
Strep throat

Reputation: 45

Changing default User object for in-memory provider

I'm just trying to change default User object for in-memory provider. Default User object is Symfony\Component\Security\Core\User\User. But this object doesn't fill my requirements, so i copied that object and added some properties for my requirements then named it as InMemoryUser and used it like following:

encoders:
    App\Component\Security\Core\User\InMemoryUser: plaintext

Then i got the following error when tried to run the app.

No encoder has been configured for account "App\Component\Security\Core\User\InMemoryUser"

I saw the problem after investigeting about half hour. Symfony is calling Symfony\Component\Security\Core\User\InMemoryUserProvider by default and using Symfony\Component\Security\Core\User\User object to create in-memory users in __construct method.

Then i tryed to override default InMemoryUserProvider as following.

services:

    App\Component\Security\Core\User\CustomInMemoryUserProvider:
        decorates: security.user.provider.concrete.api_user_provider
        arguments:
            $users: []  # this should be contains in-memory users defined in the security.yaml but i dont know how to do that.

This works fine except a little problem, does not inject $users like InMemoryUserProvider. Normally, $users contains in-memory user list defined in security.yaml.

Now, how can i inject $users for my CustomInMemoryUserProvider and is this a good practice to change User object for InMemoryProvider ?

Thanks for your answers in advance.

I'm using symfony 5.1 by the way.

Upvotes: 2

Views: 1251

Answers (1)

Cerad
Cerad

Reputation: 48865

I have never quite cracked the mystery of properly decorating a service whose constructor gets injected dynamically by a DI extension.

However, in this case, all you really need to do is to change the class of the in memory provider which can be done in a compiler pass in your kernel:

# src\Kernel.php
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use App\User\InMemoryUserProvider;
class Kernel extends BaseKernel implements CompilerPassInterface
...
    public function process(ContainerBuilder $container)
    {
        $id = 'security.user.provider.in_memory';
        $container->getDefinition($id)->setClass(InMemoryUserProvider::class);
    }

When testing this I originally tried to extend the existing core in memory provider but it uses a private method which can't be overridden. So I just re-implemented the complete interface

namespace App\User;
class InMemoryUserProvider implements UserProviderInterface
{
    public function __construct(array $users)
    {
        dump($users); // Confirms get the users from security.yaml
    }
    public function loadUserByUsername(string $username)
    {
        // TODO: Implement loadUserByUsername() method.
        echo "Get User {$username}\n";
    }

Confirm it has been wired up as expected:

bin/console debug:container | grep UserProv                                               
  App\User\InMemoryUserProvider = App\User\InMemoryUserProvider                                                                   
  Symfony\Component\Security\Core\User\UserProviderInterface = alias for "security.user.provider.concrete.users_in_memory"                                                                                                                    
  security.user.provider.concrete.users_in_memory = App\User\InMemoryUserProvider                                                                   
  security.user.provider.in_memory = App\User\InMemoryUserProvider                                                                                  

And made a command just to verify it all worked as expected:

class UserProviderCommand extends Command
{
    protected static $defaultName = 'user:provider';

    private $userProvider;

    public function __construct(UserProviderInterface $userProvider)
    {
        parent::__construct();
        $this->userProvider = $userProvider;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        echo "User Provider: " . get_class($this->userProvider) . "\n";
        $this->userProvider->loadUserByUsername('xxx');
        return 0;
    }
}

Upvotes: 1

Related Questions