I'll try to explain myself (my English is not as good as I wish)
I'm very inexperienced with symfony2 and doctrine2.
I'm trying to create a twig template and all the logic to handle modifications in, for example, user entity.
I've made the UserType AbstractType class and can handle, modify and persist if I get just one record or, at least, if I show only one form.
I've tried to do the same thing showing to the user every "User" in my database, and allowing him to modify and save one of them each time by clicking in submit button.
What I have right now:
src/Dummy/Form/Type/UserType.php
namespace Dummy\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction('')
->setMethod('POST')
->add('Name')
->add('Adress')
->add('save')
;
}
}
src/Dummy/Test/Controller/TestController.php
namespace Dummy\TestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Dummy\TestBundle\Entity\Users;
use Dummy\Form\Type\UserType;
class ArticuloController extends Controller
{
public function indexAction(Request $request)
{
//some stuff
$users= $this->getDoctrine()
->getRepository('DummyTestBundle:Users')
->findAll();
$forms = array();
foreach($users as $user)
{
$forms[] = $this->createForm(UserType::class, $user); //*1
}
//... Don't know how to handle request with $forms[index]->submit(...
//... Check if is valid
//... set values
//... persist
$twigForms = array();
foreach($forms as $form)
{
$twigForms[] = $form->createView()
}
//... render twig template
}
}
Also I have the entity Users which works fine, made from yaml config file as described in the documentation
What I want
Handle the request and persist the object modifications.
The part that works
Until clicking in that submit button this works fine, it shows a template with a form for each user in database, every one of them with his submit button. After pressing any of them I'm completely lost.
If I force index to be 0, it works too (but only in the first form).
Because of that, I think that I need to know the index in $users or $forms variable (marked with *1 commented point above) in order to handle that request using something like:
$forms[index]->submit($request->request->get($forms[index]->getName()));
if ($forms[index]->isValid()) {
$users[index]->setName('Name from post');
$users[index]->setAdress('Adress from post');
$em = $this->getDoctrine()->getManager();
$em->persist($users[index]);
$em->flush();
}
But don't know if this is correct or how to do it.
I also read about collections, but don't know how to make this work with them.
Related
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;
}
}
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!
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
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);
I have prepared custom form type class CheckListFormType. I have many fields there.
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class CheckListType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('IMEI');
$builder->add('serialNumber');
$builder->add('visualCheck');
$builder->add('callCheck');
$builder->add('cameraCheck');
$builder->add('checkMend');
$builder->add('wifiCheck');
$builder->add('wipeDataCheck');
$builder->add('checkComment');
$builder->add('checkDate');
$builder->add('batch');
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'check_list';
}
}
Next, I have action in Controller
public function updateCheckList(Request $request)
{
$em = $this->getDoctrine()->getManager();
$checkListId = $request->get('checkListId');
$checkListRepository = $this->getDoctrine()->getRepository('AppBundle:CheckList');
$checkList = $checkListRepository->find($checkListId);
if(!$checkList){
return new JsonResponse(array(
'success' => false
));
}
$form = $this->createForm(new CheckListType(), $checkList);
$form->handleRequest($request);
$em->persist($checkList);
$em->flush();
return new JsonResponse(array(
'success' => true
));
}
Next I have many views with this form type. In some view user can only edit 'serialNumber' in other user can edit only 'checkMend' etc.
Now when I submit form from view, where is only one field, doctrine clears all other properties from CheckListEntity. How can I avoid of clearing other fields, when I submit only one input.
When you call $form->handleRequest() the form passes all its values to the entity. If no value has been submitted, it will reset it to the default value. If you want the value to be left as it is, you have to remove that field from the form.
You can either define the forms separately for each case containing just the fields that can be modified, or you use the remove method on your existing form to get rid of the fields you want to be left untouched in each case.
I'm trying to build a Symfony form (in Silex) by name. Using the configuration below, I believe I should be able to call $form = $app['form.factory']->createBuilder('address');, however the FormRegistry cannot find a form of this type.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormTypeExtensionInterface;
class AddressType extends AbstractType implements FormTypeExtensionInterface
{
public function getName()
{
return 'address';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('addressee', 'text');
// .. fields ..
$builder->add('country', 'text');
}
public function getExtendedType()
{
return 'form';
}
}
This is then added to the form registry using the form.type.extensions provider:
$app['form.type.extensions'] = $app->share($app->extend('form.type.extensions', function($extensions) use ($app) {
$extensions[] = new AddressType();
return $extensions;
}));
Is there something else I need to do or a different way of building the form in this way?
Why not use direct
$app['form.factory']->createBuilder('Namespace\\Form\\Types\\Form')
First, sorry for my poor english. :)
I think you should extend form.extensions, instead of form.type.extensions.
Something like this:
$app['form.extensions'] = $app->share($app->extend('form.extensions',
function($extensions) use ($app) {
$extensions[] = new MyTypesExtension();
return $extensions;
}));
Then your class MyTypesExtension should look like this:
use Symfony\Component\Form\AbstractExtension;
class MyTypesExtension extends AbstractExtension
{
protected function loadTypes()
{
return array(
new AddressType(),
//Others custom types...
);
}
}
Now, you can retrieve your custom type this way:
$app['form.factory']->createBuilder('address')->getForm();
Enjoy it!
I see, this question is quite old but:
What you do is creating a new Form Type not extending an existing one, so the correct way to register it to add it to the 'form.types'. (Remember: form type extension is adding something to the existing types so for the future all instance will have that new 'feature'. Here you are creating a custom form type.)
$app['form.types'] = $app->share($app->extend('form.types', function ($types) use ($app) {
$types[] = new AddressType();
return $types;
}));
I think when you are coming from Symfony to Silex form.type.extension can be misleading.
From Symfony How to Create a Form Type Extension:
You want to add a generic feature to several types (such as adding a "help" text to every field type);
You want to add a specific feature to a single type (such as adding a "download" feature to the "file" field type).
So as your code shows you want to add a FormType which exists in Symfony but you would use the FormServiceProvider in Silex without defining an AbstractType and just use the form.factory service as shown in this example:
In your app.php:
use Silex\Provider\FormServiceProvider;
$app->register(new FormServiceProvider());
In your controller/action:
$form = $app['form.factory']->createBuilder('form', $data)
->add('name')
->add('email')
->add('gender', 'choice', array(
'choices' => array(1 => 'male', 2 => 'female'),
'expanded' => true,
))
->getForm()
;
I need to set up a custom form type in Symfony that uses the choice type as a parent but doesn't actually require choices to be preloaded. As in I want to be able to populate the select with an ajax call and then submit with one of the options from the call without getting This value is not valid. errors, presumably because its not one of the preloaded options.
I don't need a custom data transformer as I am doing that through the bundle controller, I just need Symfony not to complain when I submit with an option that wasn't originally on the list. Here is what my custom form type looks like so far:
<?php
namespace ISFP\Index\IndexBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class NullEntityType extends AbstractType
{
public function getDefaultOptions(array $options)
{
$defaultOptions = array(
'em' => null,
'class' => null,
'property' => null,
);
$options = array_replace($defaultOptions, $options);
return $options;
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'null_entity';
}
}
Dude look at the EntityType it has a parent as a choice. But entire display was handle by ChoiceType. When I was doing similar things I've started from overload Both ChoiceType and EntityType. And then set in overloaded Entity the getParent() to mine overloaded choice.
Finally In my case I modify the new choice and put there my embedded form. It's tricky to do It. And it consumes lot's of time.
But with that approach i don't have any problem with Validation.
I need to create a form class,I'm following the symfony book in http://symfony.com/doc/current/book/forms.html
I am trying to create a form in src/Acme/TaskBundle/Form/Type/TaskType.php, but when I look at the folder structure on my project there is no "Form" folder.
I try to create the Form folder manually, in src/Acme/TaskBundle/, I get an error in the the Form Folder and in the TaskType.php files ( namespace Acme\TaskBundle\Form\Type Expected:Identifier).
Is there a way to create the Form folder in an automatic way? Or how can I create in manually?
The Form folder is just a convention — you can put your forms wherever you want. The convention extends to:
Form\Type for form types,
Form\Model for form models,
Form\Handler for form handlers,
etc.
Have you tried the command, might be what you're looking for
php app/console generate:doctrine:form
I just created it manually.
form: (my case: LL\NameBundle\Form)
FormName.php
<?php
namespace LL\NameBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class FormName extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('add')
->add('your')
->add('fields')
;
}
public function getName()
{
return 'form-name';
}
}
Controller:
use LL\NameBundle\Form\FormName;
public function()
{
$form = new FormName();
$form = $this->form( $form, $entity );
return array(
'form' => $form->createView()
);
}
This Works for me, if it doesn't work please post some code.