How can the Symfony2 form be transformed to JSON data structure? Looking for proper bundle gave me no results;
Example:
$builder
->add('name', 'text')
->add('password', 'password')
;
Would result in something like that:
{
fields: {
name: {
type: 'text'
},
password: {
type: 'password'
}
}
}
Iterating over each element in form after $form = $this->createForm(new FormType(), new Entity()) was not helpful, could not find some properties that could be defined in form builder.
I assume that you want to get this information in a controller once you have posted the form, in which case you can easily get the underlying entity from the form object, like so:
$entity = $form->getData();
At this point you can either manually pull out the fields you want into an array and json_encode() that, or... implement the JsonSerializable interface in your entity and then directly json_encode() the object itself.
For example:
<?php
namespace FooApp/BarBundle/Entity;
use JsonSerializable;
class Baz implements JsonSerializable
{
private $name;
private $password;
// ...
function jsonSerialize()
{
return [
'fields' => [
'name' => ['type' => $this->name],
'password' => ['type' => $this->password],
],
];
}
}
Then, in your controller:
$entity = $form->getData();
$json = json_encode($entity);
Calling json_encode() will automatically invoke Baz::jsonSerialize() and return the array structure you defined, which in turn is JSON-encoded.
Update 2016-06-23
I happened across this question again by chance - and... I realise that I didn't answer your actual question.
You didn't want to convert the form's underlying entity to JSON - instead you want to represent form structure as data. My apologies for misunderstanding - hopefully I can rectify that with an update.
This is a proof-of-concept that should work for a non-nested form (although it should be straightforward to create a recursive version or something for that case). But, assuming a scenario where you have instantiated a form, comprising of fields name and password, like so:
$form = $this->createForm(FooType::class, $foo);
It should then possible to iterate over the instance and derive a representation of the structure; e.g:
$fields = ['fields' => []];
foreach ($form->all() as $field) {
$name = $field->getName();
$type = $field->getConfig()->getType()->getBlockPrefix();
$fields['fields'][$name] = ['type' => $type];
}
echo json_encode($fields, JSON_PRETTY_PRINT);
Yields:
{
"fields": {
"name": {
"type": "text"
},
"password": {
"type": "password"
}
}
}
Hope this helps :)
If you need to get the JSON representation of the form object, you can get the entity object and encode it:
$jsonStr =json_encode($builder->getData());
Take a look on http://jmsyst.com/libs/serializer#installation
and fosrestbundle
$view = $this->view( $form->createView() );
return $this->handleView( $view );
Does what you are looking for.
Related
I know the title isn't that understanding but I will try to explain here my problem.
So I have a form that auto generates input fields by jquery and I'm trying to store that data in the db
the blade:
<div>
<input type="text" placeholder="ID" name="myproduct[]"/>
<input type="text" placeholder="Șansa" name="mychance[]"/>
Delete
</div>
My Controller:
class SomeController extends BaseController {
public function someMethod(Request $request) {
...
$items = '{"'.$request->myproduct[0].'":"'.$request->mychance[0].'", "'.$request->myproduct[1].'":"'.$request->mychance[1].'"}';
$case = Cases::create([
'items' => $items
]);
$case->save();
...
}
}
It is kinda working but I want to know how to get all data in $items without creating new variables like $variable[0], $variable[1],2 ,3 for every input I generate
If you can guarantee that you always have the same amount of products and choices you can use array_combine
$myproduct = [1,2,3];
$mychance = ['test1', 'test2', 'test3'];
$items = array_combine($myproduct, $mychance);
// result: [1 => "test1", 2 => "test2", 3 => "test3"]
// encode it to a string with json_encode
// result: "{"1":"test1","2":"test2","3":"test3"}"
$case = Cases::create(['items' => json_encode($items)]);
Additionaly you can cast your items column to an array
In your Cases model add
protected $casts = [
'items' => 'array',
];
Laravel will then automatically serialize it when storing and deserialize it when accessing giving you an array.
then you could just do
$myproduct = [1,2,3];
$mychance = ['test1', 'test2', 'test3'];
$case = Cases::create(['items' => array_combine($myproduct, $mychance)]);
Array & JSON Casting
The array cast type is particularly useful when working with columns
that are stored as serialized JSON. For example, if your database has
a JSON or TEXT field type that contains serialized JSON, adding the
array cast to that attribute will automatically deserialize the
attribute to a PHP array when you access it on your Eloquent model
You maybe want to do this:
class SomeController extends BaseController {
public function someMethod(Request $request) {
...
$items = [];
foreach($request->myproduct as $i => $myProductSingle) {
$items[$myProductSingle] = $request->mychance[$i];
}
$case = Cases::create([
'items' => json_encode($items)
]);
$case->save();
...
}
}
Hope it helps..
$items = [];
foreach($request->myproduct as $key => $myProduct) {
$items[$myProduct] = $request->mychance[$key];
}
I have a simple form with Sonata admin.
I would like the user could add a list of integers (as many as he wants). And after it would be store as an array in my object:
[1, 2, 3, 6, 9]
There any way of doing it without creating another class to instantiate the integers?
UPDATE:
The only way I know how to something close is using choice like:
->add('type', 'choice', [
"required" => true,
"expanded" => true,
"multiple" => false,
"choices" => Campanha::getTypes(),
])
But with that I have a limited number of choices, I would like that it would be free to the user to add the quantity of numbers and the values he wants
All you need to accomplish this is a Data Transformer. Look at an example:
namespace AppBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ArrayToStringTransformer implements DataTransformerInterface
{
public function transform($array)
{
if (null === $array) {
$array = array();
}
if (!is_array($array)) {
throw new TransformationFailedException('Expected an array.');
}
return implode(',', $array);
}
public function reverseTransform($string)
{
if (null === $string || '' === $string) {
return array();
}
if (!is_string($string)) {
throw new TransformationFailedException('Expected a string.');
}
return explode(',', $string);
}
}
Later, use it where there is an array field. For greater reusability let's create a custom field type which extends of TextType:
namespace AppBundle\Form\Type;
use AppBundle\Form\DataTransformer\ArrayToStringTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class ArrayTextType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new ArrayToStringTransformer());
}
public function getParent()
{
return TextType::class;
}
}
That's it! Now you can manage your array fields safely by using your ArrayTextType:
// in any controller context
public function fooAction()
{
$data = array(
'tags' => array(1, 2, 3, 4, 5),
);
$form = $this->createFormBuilder($data)
->add('tags', ArrayTextType::class)
->getForm();
return $this->render('default/index.html.twig', array('form' => $form->createView()));
}
Also we can use any Doctrine array mapped field (i.e. #ORM\Column(name="tags", type="array")).
Output result:
For better data entry I recommend using with this the Bootstrap Tags Input jQuery plugin. See examples here.
Try looking into sonata_type_native_collection:
From the Sonata Admin Docs:
This bundle handle the native Symfony collection form type by adding:
an add button if you set the allow_add option to true.
a delete button if you set the allow_delete option to true.
And the Symfony collection form type:
This field type is used to render a "collection" of some field or form. In the easiest sense, it could be an array of TextType fields that populate an array emails values.
So, for your case, maybe something like:
->add('type', 'sonata_type_native_collection', [
'required' => true,
'entry_type' => 'number',
'options' => [
// Any options you'd like the integer fields to have.
]
])
(This doesn't speak at all to the change's you'll need to make to the underlying model, of course.)
Edit: Changed the 'entry_options' array key to 'options', as per #Matheus Oliveira's comment.
I have a symfony 2 form that simply posts the comment. I want to remove html characters from the posted form before saving it to database. I read somewhere that you can't directly change the value of posted form and you need to create an event listener. I have created an event listener for this purpose to achieve this goal. So i created an event listener and below is the code.
<?php
// src/Adhl/FrontBundle/EventListener/StripHtmlSubscriber.php
namespace Adhl\FrontBundle\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class StripHtmlSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$event->getData()->setDetails(strip_tags($event->getData()->getDetails()));
$form->add('details', 'textarea', array(
'label' => false,
'attr' => array(
'cols' => '30',
'class' => 'text-input span8'
)
)
);
}
}
$event->getData()->setDetails() changes the value of posted field "details"
but $event->getData()->getDetails() does not returns anything.
I want to get the post field that has the name "details", strip html tags from it and save it back to same posted key.
In simple PHP I could do it as:
$_POST['details'] = strip_tags($_POST['details']);
Can anyone please tell me what am i doing wrong?
How does your FormType class look like?
do you bind it on a entity or array?
If you do the entity binding, then simply in the entity modify the setDetails method in your entity to
setDetails($details) {
$this->details = strip_tags($details);
}
if you have a array binding then in the action or subscriber the getData() will return an associatoive array not a entity
$data = $event->getData();
$data['details'] = strip_tags($data['details']);
$event->setData($data);
As mentioned here I'm building a custom hydration strategy to handle my related objects in a select box in a form.
My form looks like this:
$builder = new AnnotationBuilder($entityManager);
$form = $builder->createForm(new MyEntity());
$form->add(new MyFieldSet());
$hydrator = new ClassMethodsHydrator();
$hydrator->addStrategy('my_attribute', new MyHydrationStrategy());
$form->setHydrator($hydrator);
$form->get('my_attribute')->setValueOptions(
$entityManager->getRepository('SecEntity\Entity\SecEntity')->fetchAllAsArray()
);
When I add a new MyEntity via the addAction everything works great.
I wrote fetchAllAsArray() to populate my selectbox. It lives within my SecEntityRepository:
public function fetchAllAsArray() {
$objects = $this->createQueryBuilder('s')
->add('select', 's.id, s.name')
->add('orderBy', 's.name ASC')
->getQuery()
->getResult();
$list = array();
foreach($objects as $obj) {
$list[$obj['id']] = $obj['name'];
}
return $list;
}
But in the edit-case the extract() function doesn't work. I'm not at the point where I see something of hydrate() so I'll leave it out for now.
My hydrator strategy looks like this:
class MyHydrationStrategy extends DefaultStrategy
{
public function extract($value) {
print_r($value);
$result = array();
foreach ($value as $instance) {
print_r($instance);
$result[] = $instance->getId();
}
return $result;
}
public function hydrate($value) {
...
}
The problem is as follows:
Fatal error: Call to a member function getId() on a non-object
The print_r($value) returns loads of stuff beginning with
DoctrineORMModule\Proxy__CG__\SecEntity\Entity\SecEntity Object
following with something about BasicEntityPersister and somewhere in the mess are my referenced entities.
The print_r($instance) prints nothing. It's just empty. Therefore I guess is the error message legit... but why can't I iterate over these objects?
Any ideas?
Edit:
Regarding to #Sam:
My attribute in the entity:
/**
* #ORM\ManyToOne(targetEntity="Path/To/Entity", inversedBy="whatever")
* #ORM\JoinColumn(name="attribute_id", referencedColumnName="id")
* #Form\Attributes({"type":"hidden"})
*
*/
protected $attribute;
My new selectbox:
$form->add(array(
'name' => 'attribute',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'attributes' => array(
'required' => true
),
'options' => array(
'label' => 'MyLabel',
'object_manager' => $entityManager,
'target_class' => 'Path/To/Entity',
'property' => 'name'
)
));
My final hope is that I'm doing something wrong within the controller. Neither my selectbox is preselected nor the value is saved...
...
$obj= $this->getEntityManager()->find('Path/To/Entity', $id);
$builder = new \MyEnity\MyFormBuilder();
$form = $builder->newForm($this->getEntityManager());
$form->setBindOnValidate(false);
$form->bind($obj);
$form->setData($obj->getArrayCopy());
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$form->bindValues();
$this->getEntityManager()->flush();
return $this->redirect()->toRoute('entity');
}
}
I still haven't come around to write the tutorial for that :S
I don't know if this is working with the annotationbuilder though! As the DoctrineModule\Form\Element\ObjectSelect needs the EntityManager to work. The options for the ObjectSelect are as follows:
$this->add(array(
'name' => 'formElementName',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'attributes' => array(
'required' => true
),
'options' => array(
'label' => 'formElementLabel',
'empty_option' => '--- choose formElementName ---',
'object_manager' => $this->getEntityManager(),
'target_class' => 'Mynamespace\Entity\Entityname',
'property' => 'nameOfEntityPropertyAsSelect'
)
));
In this case i make use of $this->getEntityManager(). I set up this dependency when calling the form from the ServiceManager. Personally i always do this from FactoryClasses. My FormFactory looks like this:
public function createService(ServiceLocatorInterface $serviceLocator)
{
$em = $serviceLocator->get('Doctrine\ORM\EntityManager');
$form = new ErgebnishaushaltProduktForm('ergebnisform', array(
'entity_manager' => $em
));
$classMethodsHydrator = new ClassMethodsHydrator(false);
// Wir fügen zwei Strategien, um benutzerdefinierte Logik während Extrakt auszuführen
$classMethodsHydrator->addStrategy('produktBereich', new Strategy\ProduktbereichStrategy())
->addStrategy('produktGruppe', new Strategy\ProduktgruppeStrategy());
$hydrator = new DoctrineEntity($em, $classMethodsHydrator);
$form->setHydrator($hydrator)
->setObject(new ErgebnishaushaltProdukt())
->setInputFilter(new ErgebnishaushaltProduktFilter())
->setAttribute('method', 'post');
return $form;
}
And this is where all the magic is happening. Magic, that is also relevant to your other Thread here on SO. First, i grab the EntityManager. Then i create my form, and inject the dependency for the EntityManager. I do this using my own Form, you may write and use a Setter-Function to inject the EntityManager.
Next i create a ClassMethodsHydrator and add two HydrationStrategies to it. Personally i need to apply those strategies for each ObjectSelect-Element. You may not have to do this on your side. Try to see if it is working without it first!
After that, i create the DoctrineEntity-Hydrator, inject the EntityManager as well as my custom ClassMethodsHydrator. This way the Strategies will be added easily.
The rest should be quite self-explanatory (despite the german classnames :D)
Why the need for strategies
Imo, this is something missing from the DoctrineEntity currently, but things are still in an early stage. And once DoctrineModule-Issue#106 will be live, things will change again, probably making it more comfortable.
A Strategy looks like this:
<?php
namespace Haushaltportal\Stdlib\Hydrator\Strategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class ProduktbereichStrategy implements StrategyInterface
{
public function extract($value)
{
if (is_numeric($value) || $value === null) {
return $value;
}
return $value->getId();
}
public function hydrate($value)
{
return $value;
}
}
So whenever the $value is not numeric or null, meaning: it should be an Object, we will call the getId() function. Personally i think it's a good idea to give each Element it's own strategy, but if you are sure you won't be needing to change the strategy at a later point, you could create a global Strategy for several elements like DefaultGetIdStrategy or something.
All this is basically the good work of Michael Gallego aka Bakura! In case you drop by the IRC, just hug him once ;)
Edit An additional resource with a look into the future - updated hydrator-docs for a very likely, soon to be included, pull request
I have a form for my entity called Book and I have a type to display a form in my view. In this type I have some fields that are mapped to properties in my entity.
Now I want to add another field which is not mapped in my entity and supply some initial data for that field during form creation.
My Type looks like this
// BookBundle\Type\Book
public function buildForm(FormBuilderInterface $builder, array $options = null)
{
$builder->add('title');
$builder->add('another_field', null, array(
'mapped' => false
));
}
The form is created like this
$book = $repository->find(1);
$form = $this->createForm(new BookType(), $book);
How can I supply some initial data now during form creation? Or how do I have to change that creation of the form to add initial data to the another_field field?
I also have a form that has fields that mostly match a previously defined entity, but one of the form fields has mapped set to false.
To get around this in the controller, you can give it some initial data pretty easily like this:
$product = new Product(); // or load with Doctrine/Propel
$initialData = "John Doe, this field is not actually mapped to Product";
$form = $this->createForm(new ProductType(), $product);
$form->get('nonMappedField')->setData($initialData);
simple as that. Then when you're processing the form data to get ready to save it, you can access the non-mapped data with:
$form->get('nonMappedField')->getData();
One suggestion might be to add a constructor argument (or setter) on your BookType that includes the "another_field" data, and in the add arguments, set the 'data' parameter:
class BookType
{
private $anotherFieldValue;
public function __construct($anotherFieldValue)
{
$this->anotherFieldValue = $anotherFieldValue;
}
public function buildForm(FormBuilderInterface $builder, array $options = null)
{
$builder->add('another_field', 'hidden', array(
'property_path' => false,
'data' => $this->anotherFieldValue
));
}
}
Then construct:
$this->createForm(new BookType('blahblah'), $book);
You can change the request parameters like this to support the form with additional data:
$type = new BookType();
$data = $this->getRequest()->request->get($type->getName());
$data = array_merge($data, array(
'additional_field' => 'value'
));
$this->getRequest()->request->set($type->getName(), $data);
This way your form will fill in the correct values for your field at rendering. If you want to supply many fields this may be an option.