Search code examples
symfonyserviceormdoctrineauthenticator

Too few arguments to function `LoginFormAuthenticator::__construct()`, 0 passed exactly 4 expected


Need to connect to multiple databases and followed the Symfony documentation on this matter.

I have created multiple doctrine connections and orm entity managers, and disabled autowiring.

# config/packages/doctrine.yaml
doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        # configure these for your database server
        url: "%env(resolve:DATABASE_URL)%"
        driver: "pdo_mysql"
        server_version: "5.7"
        charset: utf8mb4
      lc_cvo:
        # configure these for your database server
        url: "%env(resolve:DATABASE_LC_CVO_URL)%"
        driver: "pdo_mysql"
        server_version: "5.7"
        charset: utf8mb4
      lc_cvt:
        # configure these for your database server
        url: "%env(resolve:DATABASE_LC_CVT_URL)%"
        driver: "pdo_mysql"
        server_version: "5.7"
        charset: utf8mb4
      lc_ewi:
        # configure these for your database server
        url: "%env(resolve:DATABASE_LC_EWI_URL)%"
        driver: "pdo_mysql"
        server_version: "5.7"
        charset: utf8mb4
      lc_tbo:
        # configure these for your database server
        url: "%env(resolve:DATABASE_LC_TBO_URL)%"
        driver: "pdo_mysql"
        server_version: "5.7"
        charset: utf8mb4
      lc_users:
        # configure these for your database server
        url: "%env(resolve:DATABASE_LC_USERS_URL)%"
        driver: "pdo_mysql"
        server_version: "5.7"
        charset: utf8mb4

orm:
    entity_managers:
      default:
        connection: default
        mappings:
          Main:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_lemoncake"
            prefix: 'App\Entity\lmc_lemoncake'
            alias: Main
      lc_cvo:
        connection: lc_cvo
        mappings:
          lc_cvo:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_lemoncake-cvo"
            prefix: 'App\Entity\lmc_lemoncake_cvo'
            alias: lc_cvo
      lc_cvt:
        connection: lc_cvt
        mappings:
          lc_cvt:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_lemoncake-cvt"
            prefix: 'App\Entity\lmc_lemoncake_cvt'
            alias: lc_cvt
      lc_ewi:
        connection: lc_ewi
        mappings:
          lc_ewi:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_lemoncake-ewi"
            prefix: 'App\Entity\lmc_lemoncake_ewi'
            alias: lc_ewi
      lc_tbo:
        connection: lc_tbo
        mappings:
          lc_tbo:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_lemoncake-tbo"
            prefix: 'App\Entity\lmc_lemoncake_tbo'
            alias: lc_tbo
      lc_users:
        connection: lc_users
        mappings:
          lc_users:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_users"
            prefix: 'App\Entity\lmc_users'
            alias: lc_users

My services.yaml file looks like this

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:

services:
  # default configuration for services in *this* file
  _defaults:
    autowire: false # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

  # makes classes in src/ available to be used as services
  # this creates a service per class whose id is the fully-qualified class name
  App\:
    resource: "../src/"
    exclude:
      - "../src/DependencyInjection/"
      - "../src/Entity/"
      - "../src/Kernel.php"
      - "../src/Tests/"

  # controllers are imported separately to make sure services can be injected
  # as action arguments even if you don't extend any base controller class
  App\Controller\:
    resource: "../src/Controller/"
    tags: ["controller.service_arguments"]

  # add more service definitions when explicit configuration is needed
  # please note that last definitions always *replace* previous ones

Unfortunately I am receiving the following error when trying to access the login page.

Too few arguments to function App\Security\LoginFormAuthenticator::__construct(), 0 passed in /var/www/html/app/var/cache/dev/Container12fc4el/getSecurity_Firewall_Map_Context_MainService.php on line 53 and exactly 4 expected

The LoginFormAuthenticator it is referring to is listed here and needs to connect to the lc_users connection where the users' info (username, pw) is located. I will be needing the other connections to get the client data.

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
    use TargetPathTrait;

    public const LOGIN_ROUTE = 'app_login';

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

I believe I need to add something to my services so the Authenticator can retrieve the correct connection, unfortunately my knowledge on the matter is not sufficient.

I need to use multiple databases for multiple clients.

  • How do I fix the issue at hand?
  • How do I prevent this issue from occurring with the other connections?
  • Am I handling this in a correct way, or is there a better approach for connecting to multiple databases?

Thank you in advance for your help, feel free to ask for more info.

EDIT: Thanks to @msg for the answer; I've managed to make it work through the following code:

app/config/services.yaml:

App\Security\LoginFormAuthenticator:
    autowire: true
    tags: ["doctrine.repository_service"]
    arguments:
      $entityManager: "@doctrine.orm.lc_users_entity_manager"

app/config/doctrine.yaml:

  orm:
    default_entity_manager: default
    entity_managers:
      auto_generate_proxy_classes: "%kernel.debug%"
      auto_mapping: true
      default:
         ...

      lc_users:
        connection: lc_users
        mappings:
          App\Entity\lmc_lemoncake:
            is_bundle: false
            type: annotation
            dir: "%kernel.project_dir%/src/Entity/lmc_users"
            prefix: 'App\Entity\lmc_users'
            alias: lc_users

(part of) the getUser function of the LoginFormAuthenticator:

$em = $this->entityManager;
$repo = $em->getRepository(Users::class, 'lc_users');
$user = $repo->findOneBy(['username' => $credentials['username']]);

Solution

  • As already said, injecting EntityManager will get you the default one. Doctrine will register one service per manager with the name doctrine.orm.%manager_name%_entity_manager.

    So if you need a different one, you have several options:

    1. Explicitly configure your service to use the manager you need:

    services:
        App\Security\LoginFormAuthenticator: 
            arguments:
                # Override the Manager, other arguments will be autowired.
                $entityManager: '@doctrine.orm.lc_users_entity_manager'
    
    

    2. Create different bindings for the different entity managers:

    _defauts:
        bind:
            $cvoManager: '@doctrine.orm.lc_cvo_entity_manager'
            $usersManager: '@doctrine.orm.lc_users_entity_manager'
            # Other managers...
    
    

    Based on the variable name on your dependency, the appropiate manager will be injected:

    public function __construct(
        EntityManagerInterface $entityManager, // Default manager
        EntityManagerInterface $usersManager,
    )
    

    3. If you have many dependencies, you can also inject ManagerRegistry (the object you get in an AbstractController when you call getDoctrine()). It acts as a service locator from where you can retrieve managers or repositories:

        use Doctrine\Persistence\ManagerRegistry;
    
        public function __construct(ManagerRegistry $registry) {
            $userManager = $registry->getManager('users');
        }
    

    The first option can get unwieldy fast if you have many services with different dependencies, and the third one will mask your real dependencies and complicate testing if you need to mock the registry.