How to get entity manager in form type - symfony-forms

How could I get entity manager when building forms?
I would like to search results from the database and build the choices for choicetype.
I know I could use entitytype instead but in this situation I want to record string in database than an object.
And also I need to add some more options as well.
Thank you.

In Symfony 3.2 (and possibly others, I'm not sure about 3.1, but it is probably the same), the $this->createForm() method needs a string as the first parameter, and cannot take a form object.
Add a configureOptions method to your form class:
class YourFormType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'entityManager' => null,
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Entity Manager is set in: $options['entityManager']
}
}
Then get the form in your controller like so, passing in the Entity Manager:
$form = $this->createForm(
YourFormType::class,
$yourEntity,
[
'entityManager' => $this->getDoctrine()->getManager(),
]
);

Related

ZF2 nested data validation

I'm trying make to work my validation. I have data posted to controller in the format like this:
[
'property' => 'value',
'nested_property' => [
'property' => 'value',
// ...
]
]
I have divided fields/filters and form into different classes and just gather it together in the Form's controller that looks like that:
public function __construct($name, $options)
{
// ...
$this->add(new SomeFieldset($name, $options));
$this->setInputFilter(new SomeInputFilter());
}
But it doesn't work properly, looks like it just ignores nested array (or ignores everything). What have I missed?
Thank you.
You need to set up your inputfilter like the way you've setup your forms including the fieldsets if you use the InputFilter class.
So when you've got a structure like:
MyForm
1.1 NestedFieldset
1.2 AnotherFieldset
Your inputfilters need to have the same structure:
MyFormInputFilter
1.1 NestedFielsetInputFilter
1.2 AnotherFieldsetInputFilter
Some example code:
class ExampleForm extends Form
{
public function __construct($name, $options)
{
// handle the dependencies
parent::__construct($name, $options);
$this->setInputFilter(new ExampleInputFilter());
}
public function init()
{
// some fields within your form
$this->add(new SomeFieldset('SomeFieldset'));
}
}
class SomeFieldset extends Fieldset
{
public function __construct($name = null, array $options = [])
{
parent::__construct($name, $options);
}
public function init()
{
// some fields
}
}
class ExampleInputFilter extends InputFilter
{
public function __construct()
{
// configure your validation for your form
$this->add(new SomeFieldsetInputFilter(), 'SomeFieldset');
}
}
class SomeFieldsetInputFilter extends InputFilter
{
public function __construct()
{
// configure your validation for your SomeFieldset
}
}
So the important part of configuring your inputFilter for these situations is that you need to reuse the name of your fieldset when using: $this->add($input, $name = null) within your InputFilter classes.

How to pass parameter to FormType constructor from controller

In Symfony2.7 i was able to pass parameter to Form Type constructor directly from controller while creating the form, however in Symfony3 i'm not able to do it!
Before in Symfony2.7
$postedBy = $this->getUser()->getFullname();
$form = $this->createForm(new NewsType($postedBy));
After in Symfony3
$form = $this->createForm(NewsType::class); // no idea how to pass parameter?
Update:
I also wanted to access it from:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
// how to access posted_by_name here which is sent from controller
}
Any help will be highly appreciated..
Thanks for your time! i resolved this myself:
I removed parameter from NewsType constructor and added data to postedBy form field using $options array, and passed data to $options array from controller, please check following:
NewsType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('postedBy', HiddenType::class, array(
'data' => $options['postedBy']
)
)
;
}
// WARNING: this is a MANDATORY block! Only options described here will be allowed to be passed.
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'postedBy' => null,
));
}
Controller
$form = $this->createForm(NewsType::class, $news, array(
'postedBy' => $this->getUser()->getFullname(),
);
UPDATE:
Please use below code if you want to access $options array from addEventListener:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$postedBy = $event->getForm()->getConfig()->getOptions()['postedBy'];
}
Hope it helps somebody!
You need to define your form as service.
namespace AppBundle\Form\Type;
use App\Utility\MyCustomService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class NewsType extends AbstractType
{
private $myCustomService;
private $myStringParameter;
public function __construct(MyCustomService $service, $stringParameter)
{
$this->myCustomService = $service;
$this->myStringParameter = $stringParameter;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Your code
}
}
Add to your service configuration:
#src/AppBundle/Resources/config/services.yml
services:
app.form.type.task:
class: AppBundle\Form\Type\NewsType
arguments:
- "#app.my_service"
- "posted_by_name"
tags:
- { name: form.type }
You are both right.
#Muzafar and #jkucharovic, the question is when to use which...
As Bernard Schussek shows in Symfony Forms 101:
1 Don't pass Dynamic Data to constructor..
2 ... but use Custom Options instead
3 Do pass Global Settings to constructor (or services)

Manual SQL Query from Form Type / Abstract Class

First of all, I am moving from Symfony 1.4 to Symfony 3. (Yes, I was kicking and screaming at first)
My Question: I am running a manual query from the following FormType class that is for a chunk of my Signup form. I am moving the Address part of the signup into its own class. I am calling a geographical table to get my states list and I have a Union... hence this is why I am not calling an entity class.
The problem is that I need to connect to the database but cannot because it's an Abstract class. If I run this in the Controller Class, no problem, but can't do it herein this Abstract class. I have a bunch of manual steps to go through before making any inserts, so I can just
How do you make the following work? I have not created any services, but if I put all of this into a controller class, then it works fine.
<?php
namespace LocationBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Doctrine\ORM\EntityManager;
class PartialAddressType extends AbstractType
{
private function getStatesList()
{
$sql = "
SELECT
'0' AS id
,'' AS name
,'' AS abbreviation
,'Select State' AS display
UNION
SELECT
id
,trim(name)
,trim(abbreviation)
,CONCAT(trim(abbreviation), ' - ', trim(name)) AS display
FROM
geo_state
WHERE
type = 'state'
ORDER BY abbreviation ASC; ";
$manager = $this->getDoctrine()->getManager('default');
$conn = $manager->getConnection();
$rs = $conn->query($sql)->fetchAll();
return $rs;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$rs = $this->getStatesList();
// This one is for a Select
$builder->add('locationState', ChoiceType::class, array(
'expanded' => FALSE,
'multiple' => FALSE,
'choices' => $rs(),
'choice_label' => $rs['display'],
'choice_attribute' => $rs['abbreviation'],
'preferred_choices' => array('TX'),
'choices_as_values' => FALSE,
'label' => 'State',
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array());
}
public function getName()
{
return 'location_bundle_partial_address_type';
}
}
I suggest you following steps:
1) Move the query in a separate repository class (I hope you map the table as an entity)
2) Use an EntityType Field with the Custom query as described here
3) Avoid the UNION using the placeholder
Last tips, You should implements the getBlockPrefix method instead of getName as described in the migration guide here.
Hope this help

how to inject class dependency in Yii2 configuration?

I am learning Yii2. Here is one situation I have not googled the answer.
I register a component called scraper in config/console.php's $config['components'] array,
this scraper class has a public property $_client which is a Goutte\Client class.
I tried to use the following way to set up scraper component, but it is not working, Yii2 did not instantiate $_client as a Goutte\Client object.
$config = [
'scraper' => [
'class' => 'app\models\Scraper',
'_pageSize' => 10,
'_client' => [ //not working. can not instantiate this property as an object
'class' => 'Goutte\Client'
],
],
//...
]
Question: What would be working way to inject the dependency in the configuration?
Yii2 will not instantiate objects beyond the first level in your config array. In other words, scraper will get instantiated as an object, but it's property _client will be instantiated as an array ['class' => 'Goutte\Client'].
You should implement this logic yourself:
class Service extends Component
{
private $_client = null;
public $clientClass;
public function getClient()
{
if (null !== $this->_client) {
return $this->_client;
}
$this->_client = new $clientClass;
return $this->_client;
}
}
Alternatively, you can register Goutte\Client as a separate component, then Yii will properly instantiate it.
UPDATE:
To clarify, instantiating objects from config is done with yii\base\Configurable interface which is implemented in yii\base\Object class. Eventually, this implementation executes Yii::configure:
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
As you see, all properties will be assigned their respective values, so _client will become an array, not an object.
Found another approach in the guide itself: The property targets of the class yii\log\Dispatcher can be initialized with a class names or an objects. To make it working as one expects the init method is overwritten:
/**
* {#inheritdoc}
*/
public function init()
{
parent::init();
foreach ($this->targets as $name => $target) {
if (!$target instanceof Target) {
$this->targets[$name] = Yii::createObject($target);
}
}
}
This allows configuration/initialization of the log component like this:
'log' => [
'class' => 'yii\log\Dispatcher',
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
Note: targets is an array here. But it can be done with a single class/object as well.
So in your case this should be a solution:
namespace app\models;
class Scraper extends ActiveRecord // or extends from anything that actually implements yii\base\Configurable
{
public $_client;
/**
* {#inheritdoc}
*/
public function init()
{
parent::init();
if (!$this->_client instanceof Goutte\Client) {
$this->_client = Yii::createObject($this->_client);
}
}
}
btw: usually underscore prefix in variable names is used for private properties.

When using Symfony2.1 forms, at what point should I bind custom data (an object) based on form values to my form's object?

Given the following scenario, where should I put logic to bind Department to Review:
Entities:
Dealership (has many departments)
Department (has one type)
DepartmentType
Review (has one dealership and one department)
On my ReviewForm I need the user to be able to select a Dealership and a DepartmentType, and then in some form of callback or pre/post bind, work out from them which Department to bind to the Review.
I also need this to happen prior to validation so that I can validate that the Department is child of the Dealership.
Note: Review relates to both Dealership and Department when it could just relate to Department to ease traversal and other logic I have going on.
There are two approaches I've tried so far but reached deadends / confusion.
DataTransformer on the DepartmentType on the form, not sure I understand this properly, my transform / reverseTransform methods were getting passed in the Review object, not the field object.
PRE_BIND, happens before validation but I only have raw data to work with, no objects
POST_BIND, happens after validation :(
For the final step of validation of the relationship I have a relatively simple validator that should do the job, but I'm not sure at what point I am meant to bind data to the object like this. Any pointers?
As validation is also done in a POST_BIND listener, you could simply add your POST_BIND listener with a higher priority than the validation listener (i.e. anything > 0).
If you're writing a listener:
$builder->addEventListener(FormEvents::POST_BIND, $myListener, 10);
and if you're writing a subscriber:
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_BIND => array('postBind', 10),
);
}
public function postBind(FormEvent $event)
{
...
}
I would go with a standard (ie: non-Doctrine) choice type containing a choice to represent each DepartmentType.
Then use a DataTransformer to turn the selected option into the relevant type, and vice versa.
Your custom FormType should end up looking something like this:
class Department extends AbstractType
{
private $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new DepartmentToTypeTransformer($this->em);
$builder->addViewTransformer($transformer, true);
$builder->getParent()->addEventListener(FormEvents::PRE_BIND, function($event) use ($transformer) {
$data = (object) $event->getData();
$transformer->setDealership($data->dealership);
});
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'department';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$choices = array();
foreach ($this->em->getRepository('AcmeDemoBundle:DepartmentType')->findAll() as $type) {
$choices[$type->getId()] = (string) $type;
}
$resolver->setDefaults(array(
'choices' => $choices,
'expanded' => true
));
}
}
Note the passing of the Dealership into the DataTransformer for use in the transformation.
And DataTransformer something like this:
class DepartmentToTypeTransformer implements DataTransformerInterface
{
private $em;
private $dealership;
public function __construct($em)
{
$this->em = $em;
}
public function transform($department)
{
if (null === $department) {
return $department;
}
return $department->getType()->getId();
}
public function reverseTransform($type)
{
if (null === $type) {
return $type;
}
return $this->em->getRepository('AcmeDemoBundle:Department')->findOneBy(array(
'dealership' => $this->getDealership(),
'type' => $type
));
}
public function getDealership() {
return $this->dealership;
}
public function setDealership($dealership) {
$this->dealership = $dealership;
return $this;
}
}
Your confusion regarding what is being passed to your transformer is most likely caused by the transformer you're binding being appended to pre-existing behaviour, try adding true as the second parameter to addViewTransformer:
$transformer = new DepartmentToTypeTransformer($this->em);
$builder->addViewTransformer($transformer, true);
From the docs:
FormBuilder::addViewTransformer(
DataTransformerInterface $viewTransformer,
Boolean $forcePrepend = false
)
Appends / prepends a transformer to the view transformer chain.

Resources