Search code examples
phpformssymfonyentitymanager

Symfony 3 form with multiple databases always uses default entity manager


I have recently created an admin application to manage all our users, customers etc...

The application is configured with 2 entitymanagers to get and set data to a 'staging' database and a 'production' database.

All fine, fetching data from the different databases is no problem but when a form is submitted, it always takes the default entity manager to validate and save the information entered.

Can I specify somewhere which entity manager should be used by a form on creation or at $form->handleRequest($request); ?

Config:

doctrine:
dbal:
    default_connection: default
    connections:
        default:
            driver:   pdo_mysql
            host:     '%database_host%'
            port:     '%database_port%'
            dbname:   '%database_name2%'
            user:     '%database_user2%'
            password: '%database_password2%'
            charset:  UTF8
        prod:
            driver:   pdo_mysql
            host:     '%database_host%'
            port:     '%database_port%'
            dbname:   '%database_name%'
            user:     '%database_user%'
            password: '%database_password%'
            charset:  UTF8
orm:
    auto_generate_proxy_classes: '%kernel.debug%'
    default_entity_manager: staging
    entity_managers:
        staging:
            naming_strategy: doctrine.orm.naming_strategy.underscore
            connection: default
            mappings:
                AppBundle: ~
        prod:
            naming_strategy: doctrine.orm.naming_strategy.underscore
            connection: prod
            mappings:
                AppBundle: ~

CustomerController@CreateAction:

public function createAction(Request $request, $id, RegistryInterface $registry, $_env)
{
    $em = $registry->getManager($_env);
    $customer = $em->getRepository('AppBundle:Customer')->find($id);

    $customer->init($this->getUser());

    $form = $this->createForm(CustomerForm::class, $customer, ['action' => $this->generateUrl('customers_edit', ['id' => $id, '_env' => $_env]), 'trait_choices' => [$em]]);

    $form->handleRequest($request);

I can't find a word on this subject in the docs of Symfony...

EDIT

CustomerType code:

<?php

namespace AppBundle\Form;

use AppBundle\Entity\Role;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CustomerType extends AbstractType
{
public function init($em)
{
    $this->em = $em;
}

public function buildForm(FormBuilderInterface $builder, array $options)
{
    if (isset($options['trait_choices'])) {
        call_user_func_array(array($this, 'init'), $options['trait_choices']);
    }

    $builder->add('companyName', TextType::class, [
        'label' => 'label.name',
        'required' => true
    ]);
    $builder->add('street', TextType::class, [
        'label' => 'label.street',
        'required' => true
    ]);
    $builder->add('streetNr', NumberType::class, [
        'label' => 'label.street_nr',
        'required' => true
    ]);
    $builder->add('streetBox', TextType::class, [
        'label' => 'label.street_box',
        'required' => false
    ]);
    $builder->add('postalCode', TextType::class, [
        'label' => 'label.postal_code',
        'required' => true
    ]);
    $builder->add('city', TextType::class, [
        'label' => 'label.city',
        'required' => true
    ]);
    $builder->add('country', EntityType::class, [
        'class' => 'AppBundle\Entity\Country',
        'choice_label' => 'name',
        'label' => 'label.country',
        'required' => true,
        'choice_translation_domain' => 'messages',
        'em' => $this->em
    ]);
    $builder->add('vatNr', TextType::class, [
        'label' => 'label.vat',
        'required' => false
    ]);
    $builder->add('phone', TextType::class, [
        'label' => 'label.phone',
        'required' => false
    ]);
    $builder->add('email', TextType::class, [
        'label' => 'label.email',
        'required' => true
    ]);
    $builder->add('roles', EntityType::class, [
        'class' => Role::class,
        'query_builder' => function (EntityRepository $er) {
            return $er->createQueryBuilder('r')
                ->where('r.type = ?1')
                ->setParameter(1, Role::TYPE_CUSTOMER);
        },
        'label' => 'Roles',
        'multiple' => true,
        'expanded' => true,
        'choice_label' => 'displayName',
        'attr' => [
            'class' => 'roles'
        ],
        'choice_translation_domain' => 'messages',
        'em' => $this->em
    ]);
    $builder->add('leadSources', EntityType::class, [
        'label' => 'label.origin_lead',
        'class' => 'AppBundle\Entity\LM\LeadSource',
        'multiple' => true,
        'expanded' => true,
        'choice_label' => 'name',
        'choice_translation_domain' => 'messages',
        'em' => $this->em
    ]);

    $builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmitListener'));
}

public function onPreSubmitListener(FormEvent $event)
{
    $customer = $event->getForm()->getData();
    if ($customer->getIdentifier() == null) {
        $customer->setIdentifier(uniqid('dummy'));
        $event->getForm()->setData($customer);
    } else {
        $event->getForm()->setData($customer);
    }
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => 'AppBundle\Entity\Customer',
        'trait_choices' => null,
        'allow_extra_fields' => true,

    ]);
}
}

Solution

  • How is the value for $_env injected? Try to dump it to see that it actually returns what you expect it to return ("prod" or "staging")

    The behavior you experience implies that it is probably always null, so the default entity manager is created in all cases.

    Try something like $env = $container->getParameter("kernel.environment")

    PS Your use of environments is a bit weird. It is probably not such a good idea to risk accessing your prod db from your dev or staging environments. Try to use the parameters and .env files as they should be used.

    UPDATE:

    You can pass an entity manager to your form type, using the options array when you call the createForm function

    Something like

    $form = $this->createForm(
         YourFormType::class,
         $yourEntity,
         [
             'entityManager' => $this->getDoctrine()->getManager($_env),
         ]
    );
    

    And then in your buildForm function you can retrieve it from the options array.

    UPDATE 2

    public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults([
                'entityManager' => null,
            ]);
        }