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...
Related
I'm trying to do some stuff while submitting a form in a custom module. Some of that is done by calling a function from a controller. That's when i get:
Error: Class 'Drupal\ice_cream\Controller\OrderController' not found in Drupal\ice_cream\Form\OrderForm->submitForm() (line 77 of modules\custom\ice_cream\src\Form\OrderForm.php).
As far as I can tell the namespaces aren't wrong? Or is that not related to this error?
This is how my OrderForm.php and submitForm() looks like:
<?php
namespace Drupal\ice_cream\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\ice_cream\Controller\OrderController;
/**
* Implements the order form.
*/
class OrderForm extends FormBase {
... (omitted code for getFormid and buildForm)
public function submitForm(array &$form, FormStateInterface $form_state) {
//Check if the order is ice or waffles.
if($form_state->getValue('foodType') == 'ice'){
//Save order to the DB.
OrderController::saveOrder($form_state->getValue('foodType'), $form_state->getValue('taste'));
... (more code)
}
}
}
This is how the controller looks like:
<?php
namespace Drupal\ice_cream\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Database;
/**
* Order controller for interacting (insert, select,...) with the ice cream table in the DB.
*/
class OrderController extends ControllerBase {
/**
* Saves an order for either Ice or Waffles with their options (tast or toppings).
*/
public function saveOrder($foodType, $options) {
$connection = Database::getConnection();
//Check if ice or waffles (to only insert the right field with $options).
if($foodType == "ice"){
$result = $connection->insert('ice_cream')
->fields([
'foodType' => $foodType,
'taste' => $options,
'toppings' => "",
])
->execute();
return true;
}elseif($foodType == "waffles"){
$result = $connection->insert('ice_cream')
->fields([
'foodType' => $foodType,
'taste' => "",
'toppings' => $options,
])
->execute();
return true;
}
}
}
Try using the below code:
$obj= new OrderController;
$obj->saveOrder($form_state->getValue('foodType'), $form_state->getValue('taste'));
Solved:
Just solved it with my mentor. The code was more or less correct, still needed to make my functions static in the OrderController and also I made a stupid error of forgetting the .php extension in my filename when I created it with the 'touch' terminal command...
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" );
[...]
}
Is there a way to make a form type container aware?
As an example I have 3 entities Account, User and Event. Users have ManyToMany relationship in order to associate many users with many other users (called approvers), reason for this is so that an Event created by a User can have a list of Users who area able approve it. In the User edit form where I have an approvers multiple select field the list needs to be filtered by Account so I need my form type to be container aware in order to filter the list of available Users by Account ID.
Am I right in thinking that making the form type container aware is the right way to go? I'd like to use the entity manager to filter a list of Users by Account.
1 Inject the entity manager through the constructor
<?php
namespace Acme\YourBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
class YourType extends AbstractType
{
/**
* The entity manager
*
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//build your form here
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\YourBundle\Entity\YourEntity',
));
}
public function getName()
{
return 'name_of_your_form';
}
}
2 Declare it as a service
services:
# Form type
acme_your_bundle.name_of_your_form.form.type:
class: Acme\YourBundle\Form\Type\YourType
arguments:
entityManager: "#doctrine.orm.entity_manager"
Note:
If you're starting with Symfony, take this advice:
look very closely at the code of the FOSMessageBundle, it will give you exactly what you need to do anything in symfony, from form models, to form factories, to the creation of special services (like composer, authorizer, etc..). The more you study this bundle, the quicker you will learn symfony, I guarantee you that 100%. Finally, in your specific case, look at the FormFactory in this bundle
This solution allows you to inject the container into many forms without each of your form types being a service:
Create a new form type:
class ContainerAwareType extends AbstractType implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null) {
$this->container = $container;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'container' => $this->container
));
}
public function getName() {
return 'container_aware';
}
public function getParent() {
return 'form';
}
}
Declare as a service:
services:
app.container_aware_type:
class: Burgerfuel\CmsBundle\Form\Type\ContainerAwareType
calls:
- [setContainer, ['#service_container']]
tags:
- { name: form.type, alias: 'container_aware' }
This type is now available to be a 'parent' to any other form type - whether its a service or not. In this case the important part is that setDefaultOptions from this class will be used to help build the $options argument that will be passed into any 'child' form types.
In any of your form types you can do this:
class MyType extends AbstractType
{
public function getParent() {
return 'container_aware';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$container = $options['container'];
$builder->add( ...
This solution will be beneficial if you can't make your form type a service for some reason.
Or it can save you time if you are creating many types that require access to the container.
A simple way to do this, without doing any dependency injection / declaring a service.
In your FormType file, force the form to require an EntityManager
//..
use Doctrine\ORM\EntityManager;
class YourFormType extends AbstractType
{
//...
//...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\YourBundle\Entity\YourEntity',
));
$resolver->setRequired('entity_manager');
$resolver->setAllowedTypes('entity_manager', EntityManager::class);
}
}
Then you'll be able to (and forced - important for testing), pass the entity manager from the controller.
public function yourControllerAction(Request $request)
{
//..
$em = $this->getDoctrine()->getManager();
$form = $this->createForm('Acme\YourBundle\Form\YourEntityType', $yourEntityObject, array(
'entity_manager'=>$em,
));
$form->handleRequest($request);
//..
}
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.
I try to pass my variable to constraint in form validator, but can't.
i'm doing that:
$payForm = $this->createForm(new CableTVPayType(), null, array('balance' => $balance));
And in CableTVPayType:
public function getDefaultOptions(array $options)
{
$maxSumm = $options['balance'] - 100;
[...]
It works fine, my maxSumm is what i want, but Symfiony checks $options array. 'balance' isn't a default option, and complain about this:
The option "balance" does not exist
Is there another, more right way to pass custom variable to validation?
Use the constructor for stuff to be used by all instances of a type. For example, your type might need an entity manager for it to work. It will be reused across all the form instances.
For instance specific stuff use options. If you use the constructor for instance specific stuff, all the instances will get the value you pass to the constructor of the first instance.
/**
* #FormType
*/
class PayType extends AbstractType {
private $someService;
/**
* #InjectParams
*/
public function __construct(SomeService $someService)
{
$this->someService = $someService;
}
public function getDefaultOptions(array $options)
{
return array(
'balance' => 0
);
}
public function getName()
{
return 'pay';
}
}
$form = $this->createForm('pay', null, array('balance' => $balance));
Note that the #FormType annotation registers the type as a service. It allows you to use the type's name instead of creating an instance manually. It gets even more convenient when a type needs a service to be injected into it. You use just the name — pay in this case — instead of something like this:
$form = $this->createForm(new PayType($this->get('some_service')), null, array(
'balance' => $balance
));
Done with this!
Crate variable for a class, and passing value to it through construct method
class CableTVPayType extends AbstractType {
private $maxSumm;
public function __construct($maxSumm) {
$this->maxSumm = $maxSumm;
}
Create form with argument
$payForm = $this->createForm(new CableTVPayType($someValue));
Now i can use this variable as i want in my form.