I want to create a form via form factory through a service in Symfony 2.3, but I couldn't be able to reproduce it.
My services.yml looks like this:
user.form.myservice:
factory_method: createNamed
factory_service: form.factory
class: Symfony\Component\Form\Form
arguments: [my_form_type_name]
user.form.myservice.type:
class: XxX\MyBundle\Form\Type\myEntityType
arguments: [null]
tags:
- { name: form.type, alias: my_form_type_name }
This is my form type definition (myEntityType)
class MyEntityType extends AbstractType
{
private $class;
public function __construct($class = null)
{
$this->class = (isset($class)) ? $class : 'XxX\MyBundle\Entity\myEntity';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('first_name', 'text')
->add('last_name', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => $this->class
));
}
public function getName()
{
return 'my_form_type_name';
}
}
In my controller, when I try to create the form:
$formFactory = $this->container->get('user.form.myservice');
$myForm = $formFactory->createForm();
It says:
FatalErrorException: Error: Call to undefined method Symfony\Component\Form\Form::createForm() in ...\proj1\src\XxX\MyBundle\Controller\ProfileController.php line 134
Any help please??
Thanks!
First of all you're not passing any arguments to the constructor of MyEntityType. You also used createForm method in a wrong way. It expects followigng arguments: $type, $data = null, array $options = array()`.
To get this thing work, you should specify type:
$form = $this->createForm($this->get('user.form.myservice'), new YourEntity());
Your factory service user.form.myservice returned the FormInterface instance (Symfony\Component\Form\FormFactory::createNamed) and your can call the method createForm from Form instance.
And your have a bad logic for creating form.
The factory method must be return an finished object for next working, but you want expected factory.
Factory for your example:
public function createForm()
{
$form = $this->formFactory->createForm('....');
return $form;
}
Well, I finally resolved the problem creating the form this via:
$myForm = $this->container->get('form.factory')->create(new myEntityType(), new myEntity());
Thanks for your help!
Related
Use case
I' m trying to reuse sonata_type_model_list in the front admin of my website by defining this in my buildForm method of my Entity FormType :
[...]
->add('position', 'sonata_type_model_list', array(
'model_manager' => $categoryManager,
))
[...]
However I can't use
$categoryAdmin = $this
->getConfigurationPool()
->getAdminByClass("\\Application\\Sonata\\ClassificationBundle\\Entity\\Category");
As I need to be in an AdminClass to use getConfigurationPool().
If anyone knows how to use getConfigurationPool() outside of an AdminClass or do you know how to declare sonata_type_model_list in order to use it outside of an admin class ?
You need to inject the admin pool in your form type.
Here is an example:
#services.yml
blast_base_entities.form.type.my:
class: Blast\BaseEntitiesBundle\Form\Type\MyformType
tags:
- { name: form.type, alias: blast_search_index_autocomplete }
arguments: [#sonata.admin.pool]
And in the form type:
use Sonata\AdminBundle\Admin\Pool
Class MyFormType
{
private $adminPool;
public function construct(Pool $adminPool)
{
$this->adminPool = $adminPool;
}
}
Then you can retrieve admins
$this->adminPool->getAdminByClass('Foo');
As writing in the doc, maybe you can use the sonata.admin.pool service.
$configurationPool = $this->container->get('sonata.admin.pool');
Solution
Thanks to #pbenard & #Mawcel for their answers, I combined them to achieve my goal as follow:
In my controller I create my form and inject $this->container->get('sonata.admin.pool') to it like so :
$form = $this->createForm(
new FormType($this->container->get('sonata.admin.pool')),
$entity,
$options
);
In my formType, I added the property $adminPool && the __construct method:
private $adminPool;
public function __construct(Pool $adminPool) {
$this->adminPool = $adminPool;
}
So I can now access the adminPool from the buildForm method:
public function buildForm(FormBuilderInterface $builder, array $options)
{
[...]
$categoryAdmin = $this
->getConfigurationPool()
->getAdminByClass("\\Application\\Sonata\\ClassificationBundle\\Entity\\Category" );
[...]
}
Hi I want to render a form in symFony2 with dynamic fields added to it.
So I have wrote code in controller as:
public function getDataAction(){
$handler = new AnotherClass();
$object = $handler->getForm($getAdditionalData);
}
and "AnotherClass" defined as following:
class AnotherClass extends Controller implements requiredInterface{
public function getForm($formData){
//Here i want to write Logic to render a dynamic form with dynamic fields
$form = $this->createFormBuilder($formData)
->setAction($this->generateUrl('me_route_go'))
// Set form field of additional data
foreach ($formData as $k => $v) {
$form->add($k, 'text');
}
//Create form and submit button
$form = $form->add('submit', 'submit')->add('Cancel', 'reset')->getForm();
$form = $form->getForm();
}
}
}
But here I am getting following error:
Error: Call to a member function get() on a non-object.
return $this->container->get('form.factory')->createBuilder($type, $data, $options);
Please suggest what could be the issue.
thanks in advance..
Your controller AnotherClass requires the Dependency Injection Container since you are extending the base Controller class, you have to set it after you instantiate it :
public function getDataAction(){
$handler = new AnotherClass();
$handler->setContainer($this->container);
$object = $handler->getForm($getAdditionalData);
}
You can also create it as a service :
services.yml
name.of.your.service:
class: Path\To\AnotherClass
calls:
- [setContainer, [ "#service_container" ]]
And then :
public function getDataAction(){
$handler = $this->get('name.of.your.service');
$object = $handler->getForm($getAdditionalData);
}
I have a controller that renders a form that is suppose to have a dropdown with titles mapped against a client_user entity. Below is code I use in my controller to create the form:
$builder = $this->get(form.factory);
$em = $this->get('doctrine.entity_manager');
$form = $builder->createBuilder(new ClientUserType($em), new ClientUser())->getForm();
Below is my ClientUserType class with a constructor that I pass the entity manager on:
<?php
namespace Application\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class ClientUserType extends AbstractType
{
protected $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', EntityType::class, array(
'class' => '\\Application\\Model\\Entity\\Title',
'em' => $this->entityManager
))
->add('name')
->add('surname')
->add('contact')
->add('email');
}
public function getName()
{
return 'client_user_form';
}
}
I keep on getting this catchable fatal error below and have no idea what I need to do in order to get a dropdown with titles from a database with doctrine.
Catchable fatal 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 D:\web\playground-solutions\vendor\symfony\form\FormRegistry.php on line 90 and defined in D:\web\playground-solutions\vendor\symfony\doctrine-bridge\Form\Type\DoctrineType.php on line 111
Reading from that error I have no idea where I need to create a new instance of ManagerRegistry registry as it appears that the entity manager does not work. I am also thinking perhaps I need to get the ManagerRegistry straight from the entity manager itself.
Can someone please help explain the simplest way to get this to work? What could I be missing?
Seems that doctrine-bridge form component is not configured.
Add class
namespace Your\Namespace;
use Doctrine\Common\Persistence\AbstractManagerRegistry;
use Silex\Application;
class ManagerRegistry extends AbstractManagerRegistry
{
protected $container;
protected function getService($name)
{
return $this->container[$name];
}
protected function resetService($name)
{
unset($this->container[$name]);
}
public function getAliasNamespace($alias)
{
throw new \BadMethodCallException('Namespace aliases not supported.');
}
public function setContainer(Application $container)
{
$this->container = $container;
}
}
and configure doctrine-bridge form component
$application->register(new Silex\Provider\FormServiceProvider(), []);
$application->extend('form.extensions', function($extensions, $application) {
if (isset($application['form.doctrine.bridge.included'])) return $extensions;
$application['form.doctrine.bridge.included'] = 1;
$mr = new Your\Namespace\ManagerRegistry(
null, array(), array('em'), null, null, '\\Doctrine\\ORM\\Proxy\\Proxy'
);
$mr->setContainer($application);
$extensions[] = new \Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension($mr);
return $extensions;
});
array('em') - em is key for entity manager in $application
For others that may find this: If you want to use the EntityType and you're not using a framework at all, you need to add the DoctrineOrmExtension to your FormFactoryBuilder like so:
$managerRegistry = new myManagerRegistry(
'myManager',
array('connection'),
array('em'),
'connection',
'em',
\Doctrine\ORM\Proxy\Proxy::class
);
// Setup your Manager Registry or whatever...
$doctrineOrmExtension = new DoctrineOrmExtension($managerRegistry);
$builder->addExtension($doctrineOrmExtension);
When you use EntityType, myManagerRegistry#getService($name) will be called. $name is the name of the service it needs ('em' or 'connection') and it needs to return the Doctrine entity manager or the Doctrine database connection, respectively.
In your controller, try to call the service like that:
$em = $this->get('doctrine.orm.entity_manager');
Hope it will help you.
Edit:
Sorry, I thought you was on Symfony... I have too quickly read...
I am learning to use Symfony2 and in the documentation I have read, all entities being used with Symfony forms have empty constructors, or none at all. (examples)
http://symfony.com/doc/current/book/index.html Chapter 12
http://symfony.com/doc/current/cookbook/doctrine/registration_form.html
I have parametrized constructors in order to require certain information at time of creation. It seems that Symfony's approach is to leave that enforcement to the validation process, essentially relying on metadata assertions and database constraints to ensure that the object is properly initialized, forgoing constructor constraints to ensure state.
Consider:
Class Employee {
private $id;
private $first;
private $last;
public function __construct($first, $last)
{ .... }
}
...
class DefaultController extends Controller
{
public function newAction(Request $request)
{
$employee = new Employee(); // Obviously not going to work, KABOOM!
$form = $this->createFormBuilder($employee)
->add('last', 'text')
->add('first', 'text')
->add('save', 'submit')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Should I not be using constructor arguments to do this?
Thanks
EDIT : Answered Below
Found a solution:
Looking into the API for the Controllers "createForm()" method I found something that is not obvious from the examples. It seems that the second argument is not necessarily an object:
**Parameters**
string|FormTypeInterface $type The built type of the form
mixed $data The initial data for the form
array $options Options for the form
So rather than pass in an instance of the Entity, you can simply pass in an Array with the appropriate field values:
$data = array(
'first' => 'John',
'last' => 'Doe',
);
$form = $this->createFormBuilder($data)
->add('first','text')
->add('last', 'text')
->getForm();
Another option (which may be better), is to create an empty data set as a default option in your Form Class.
Explanations here and here
class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('first');
$builder->add('last');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => new Employee('John', 'Doe'),
));
}
//......
}
class EmployeeFormController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createForm(new EmployeeType());
}
//.........
}
Hope this saves others the head scratching.
If I am inside a controller, I can easily read the config parameters using:
$this->container->getParameter('profession');
But when I am in some other class, say a Form type, how can I get hold of the config parameters?
$container = new Container();
$container->getParameter('profession');
The above code shouldn't and doesn't work.
Another similar solution is make your form type a service and inject the needed parameters. Then all your controller needs to do is to grab the service. Surround the parameter name with percent signs.
In services.xml
<service
id = "zayso_area.account.create.formtype"
class = "Zayso\AreaBundle\Component\FormType\Account\AccountCreateFormType"
public = "true">
<argument type="service" id="doctrine.orm.accounts_entity_manager" />
<argument type="string">%zayso_core.user.new%</argument>
</service>
And if you really wanted to then you could inject the complete container though that is discouraged.
Now you can use ContainerAwareInterface:
class ContactType extends AbstractType implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('choice_field', ChoiceType::class, [
'choices' => $this->container->get('yourservice')->getChoices()
]);
}
}
in services.yml:
app.contact_type:
class: AppBundle\Form\ContactType
calls:
- [setContainer, ['#service_container']]
tags:
- { name: form.type, alias: 'container_aware' }
One easy solution is to give your Type a new variable where you store the value of your config parameter. You can either make it public (not recommended), add a constructor parameter or use a setter:
class MyType extends AbstractType{
private $profession;
public function __construct($profession){
$this->profession = $profession;
}
// ...
}
You would use this in your controller like this:
$myType = new MyType($this->container->getParameter('profession'));
// use mytype with form
After all, the form should not know about the container at all as you would tie them together making it hard to test or exchange the container. This would be against the whole idea of the container.
On the other hand, using a constructor/setter to inject parameters is rather nice, as you don't need to know where they come from when testing, can change their source anytime you want and, as said, don't have a dependency to the container.
in Symfony 4 you should first define your form as a Service then in config/services.yaml pass your proper parameter to it
parameters:
locale: 'en'
upload_dir: '%kernel.project_dir%/public/uploads/avatars'
services:
App\Form\FilemanagerType:
arguments: ['%upload_dir%']
tags: [form.type]
and inside your form class get parameter (here upload dir) like this
class FilemanagerType extends AbstractType
{
private $upload_dir;
function __construct(string $upload_dir)
{
$this->upload_dir= $upload_dir;
}
}
I hope it helps
In Symfony 4.1, you just need to add ParameterBagInterface to the Form constructor:
public function __construct(ParameterBagInterface $parameterBag)
{
$this->parameterBag = $parameterBag;
}
Then to get your parameter:
$profession = $this->parameterBag->get('profession');
You can also use a Setter Injection. From http://symfony.com/doc/current/book/service_container.html#optional-dependencies-setter-injection :
If you have optional dependencies for a class, then "setter injection" may be a better option. This means injecting the dependency using a method call rather than through the constructor. The class would look like this:
namespace AppBundle\Newsletter;
use AppBundle\Mailer;
class NewsletterManager
{
protected $mailer;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
Injecting the dependency by the setter method just needs a change of syntax:
# app/config/services.yml
services:
app.mailer:
# ...
app.newsletter_manager:
class: AppBundle\Newsletter\NewsletterManager
calls:
- [setMailer, ['#app.mailer']]
In Symfony3, It can be done like this -
At Controller
$form = $this->createForm(FormType::class, $abc, array('firstargument' => $firstargumentvalue, 'second' => $secondvalue));
At FormType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => abc::class, 'firstargument' => null, 'second' => null));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$first = $options['firstargument'];
$second = $options['second'];
}
You can use the above values in the form
In Symfony 4.1
services:
# ...
_defaults:
bind:
$projectDir: '%kernel.project_dir%'
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class MessageGenerator
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function someMethod()
{
$parameterValue = $this->params->get('parameter_name');
// ...
}
}
Please refer this https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service