Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony forms (as standalone component with Doctrine) EntityType not working

I'm using Symfony forms (v3.0) without the rest of the Symfony framework. Using Doctrine v2.5.

I've created a form, here's the form type class:

class CreateMyEntityForm extends BaseFormType {

    public function buildForm(FormBuilderInterface $builder, array $options){
        $builder->add('myEntity', EntityType::class);
    }
}

When loading the page, I get the following error.

Argument 1 passed to Symfony\Bridge\Doctrine\Form\Type\DoctrineType::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, none given, called in /var/www/dev3/Vendor/symfony/form/FormRegistry.php on line 85

I believe there's some configuration that needs putting in place here, but I don't know how to create a class that implements ManagerRegistryInterface - if that is the right thing to do.

Any pointers?

Edit - here is my code for setting up Doctrine

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;

class Bootstrap {

    //...some other methods, including getCredentials() which returns DB credentials for Doctrine

    public function getEntityManager($env){

        $isDevMode = $env == 'dev';

        $paths = [ROOT_DIR . '/src'];

        $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, null, null, false);

        $dbParams = $this->getCredentials($env);

        $em = EntityManager::create($dbParams, $config);

        return $em;
    }
}
like image 648
Will Avatar asked Jan 11 '16 17:01

Will


3 Answers

Believe me, you're asking for trouble!

EntityType::class works when it is seamsly integrated to "Symfony" framework (there's magic under the hoods - via DoctrineBundle). Otherwise, you need to write a lot of code for it to work properly.
Not worth the effort!

It's a lot easier if you to create an entity repository and inject it in form constructor, then use in a ChoiceType::class field. Somethink like this:

<?php
# you form class
namespace Application\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class InvoiceItemtType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('product', ChoiceType::class, [
            'choices' => $this->loadProducts($options['products'])
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(['products' => [],]); # custom form option
    }

    private function loadProducts($productsCollection)
    {
        # custom logic here (if any)
    }
}

And somewhere in application:

$repo = $entityManager->getRepository(Product::class);
$formOptions = ['products' => $repo->findAll()];
$formFactory = Forms::createFormFactory();
$formFactory->create(InvoiceItemtType::class, new InvoiceItem, $formOptions);

That's the point!

like image 154
felipsmartins Avatar answered Nov 02 '22 03:11

felipsmartins


Expanding on the answer by xabbuh.

I was able to implement EntityType in the FormBuilder without too much extra work. However it does not work with the annotations in order to use Constraints directly inside the entity, which would require a lot more work.

You can easily facilitate the ManagerRegistry requirement of the Doctrine ORM Forms Extension, by extending the existing AbstractManagerRegistry and making your own container property within the custom ManagerRegistry.

Then it's just a matter of registering the Form extension just like any other extension (ValidatorExtension, HttpFoundationExtension, etc).

The ManagerRegistry

use \Doctrine\Common\Persistence\AbstractManagerRegistry;

class ManagerRegistry extends AbstractManagerRegistry
{

    /**
     * @var array
     */
    protected $container = [];

    public function __construct($name, array $connections, array $managers, $defaultConnection, $defaultManager, $proxyInterfaceName)
    {
        $this->container = $managers;
        parent::__construct($name, $connections, array_keys($managers), $defaultConnection, $defaultManager, $proxyInterfaceName);
    }

    protected function getService($name)
    {   
        return $this->container[$name];
       //alternatively supply the entity manager here instead
    }

    protected function resetService($name)
    {
        //unset($this->container[$name]);
        return; //don't want to lose the manager
    }


    public function getAliasNamespace($alias)
    {
        throw new \BadMethodCallException('Namespace aliases not supported');
    }

}

Create the Form

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserType extends AbstractType 
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder->add('field_name', EntityType::class, [
           'class' => YourEntity::class,
           'choice_label' => 'id'
       ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
       $resolver->setDefaults(['data_class' => YourAssociatedEntity::class]);
    }
}

Configure the Form Builder to use the extension and use the Form

$managerRegistry = new \ManagerRegistry('default', [], ['default' => $entityManager], null, 'default', 'Doctrine\\ORM\\Proxy\\Proxy');

$extension = new \Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension($managerRegistry);

$formBuilder = \Symfony\Component\Form\FormFactoryBuilder::createFormFactoryBuilder();
$formBuilder->addExtension($extension);

$formFactory = $formBuilder->getFormFactory();

$form = $formFactory->create(new \UserType, $data, $options);

The above is intended for demonstration purposes only! While it does function, it is considered best practice to avoid using Doctrine Entities inside of Forms. Use a DTO (Data Transfer Object) instead.

ENTITIES SHOULD ALWAYS BE VALID

INVALID STATE SHOULD BE IN A DIFFERENT OBJECT
(You may need a DTO)
(Also applies to Temporary State)

AVOID SETTERS

AVOID COUPLING WITH THE APPLICATION LAYER

FORM COMPONENTS BREAK ENTITY VALIDITY
BOTH SYMFONY\FORM AND ZEND\FORM ARE TERRIBLE
(For this use-case)
Use a DTO instead

Doctrine 2.5+ "NEW" Operator Syntax

class CustomerDTO
{
    public function __construct($name, $email, $city, $value = null)
    {
        // Bind values to the object properties.
    }
}
$query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a');
$users = $query->getResult(); // array of CustomerDTO
like image 20
Will B. Avatar answered Nov 02 '22 02:11

Will B.


The easiest way to solve your issue is by registering the DoctrineOrmExtension from the Doctrine bridge which makes sure that the entity type is registered with the needed dependencies.

So basically, the process of bootstrapping the Form component would look like this:

// a Doctrine ManagerRegistry instance (you will probably already build this somewhere else)
$managerRegistry = ...;

$doctrineOrmExtension = new DoctrineOrmExtension($managerRegistry);

// the list of form extensions
$extensions = array();

// register other extensions
// ...

// add the DoctrineOrmExtension
$extensions[] = $doctrineOrmExtension;

// a ResolvedFormTypeFactoryInterface instance
$resolvedTypeFactory = ...;

$formRegistry = new FormRegistry($extensions, $resolvedTypeFactory);
like image 26
xabbuh Avatar answered Nov 02 '22 03:11

xabbuh