In Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
It works, and this is what profiler displays after form submit: http://i.imgur.com/54iXbZy.png
Actors' amount grown up and I wanted to load them with Ajax autocompleter on Select2. I changed form to ChoiceType:
->add('actors', ChoiceType::class, array(
'multiple' => true,
'attr' => array(
'class' => "select2-ajax",
'data-entity' => "actor",
),
))
//...
$builder->get('actors')
->addModelTransformer(new ActorToNumberModelTransformer($this->manager));
I made DataTransformer:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberModelTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
$actorsArray = $actors->toArray();
foreach($actorsArray as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return new ArrayCollection();
$actors = new ArrayCollection();
$actorIdArray = $actorIds->toArray();
foreach($actorIdArray as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors->add($actor);
}
return $actors;
}
}
And registered form:
common.form.type.movie:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
But seems like the reverseTransform() is never called. I even put die() at the beginning of it - nothing happened. This is, what profiler displays after form submit: http://i.imgur.com/qkjLLot.png
I tried to add also ViewTransformer (code here: pastebin -> 52LizvhF - I don't want to paste more and I can't post more than 2 links), with the same result, except that reverseTransform() is being called and returns what it should return.
I know that this is an old question, but I was having a very similar problem. It turned out that I had to explicitly set the compound option to false.
That is to say, for the third parameter to the add() method, you need to add 'compound => false'.
Related
I am facing a really weird situation a user with ROLE_ADMIN gets logged out as soon as I change the name and even if i dont change anything and press save button it gets logged out. if i change the user's role directly in database to ROLE_USER the same code works fine and user is not logged out.
Following is the controller that takes care of the profile update
/**
* #Route("/profile", name="profile")
*/
public function profileAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$userInfo = $this->getUser();
//create the form object
$profileForm = $this->createForm(UserType::class, $userInfo);
$profileForm->handleRequest($request);
//check data validity
if($profileForm->isValid()){
$em->persist($userInfo);
$em->flush();
$this->get('session')->getFlashBag()->add(
'success',
'Your profile information has been updated'
);
return $this->render('AppBundle:admin/user:admin-edit.html.twig',array(
'edit_form' => $profileForm->createView()
));
}
// render registration form
return $this->render('AppBundle:admin/user:admin-edit.html.twig',array(
'edit_form' => $profileForm->createView()
));
}
}
This is my security.yml
security:
encoders:
# Our user class and the algorithm we'll use to encode passwords
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
AppBundle\Entity\User: bcrypt
providers:
# Simple example of loading users via Doctrine
# To load users from somewhere else: http://symfony.com/doc/current/cookbook/security/custom_provider.html
database_users:
entity: { class: AppBundle:User, property: username }
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
http_basic: ~
anonymous: ~
logout: ~
guard:
authenticators:
- app.form_login_authenticator
- app.facebook_authenticator
# by default, use the start() function from FormLoginAuthenticator
entry_point: app.form_login_authenticator
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, roles: ROLE_ADMIN }
- { path: ^/user, roles: ROLE_USER }
UPDATE 1
This is my UserType
namespace AppBundle\Form;
use AppBundle\Form\EventListener\AddDepartmentDegreeCourseFieldSubscriber;
use AppBundle\Form\EventListener\AddProfileFieldSubscriber;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
private $addDepartmentDegreeCourseFieldSubscriber;
private $addProfileFieldSubscriver;
function __construct(AddDepartmentDegreeCourseFieldSubscriber $subscriber, AddProfileFieldSubscriber $fields)
{
$this->addDepartmentDegreeCourseFieldSubscriber = $subscriber;
$this->addProfileFieldSubscriver = $fields;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber($this->addProfileFieldSubscriver);
$builder->addEventSubscriber($this->addDepartmentDegreeCourseFieldSubscriber);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User'
));
}
}
This is my AddProfileFieldSubscriber
namespace AppBundle\Form\EventListener;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Validator\Constraints\NotBlank;
class AddProfileFieldSubscriber implements EventSubscriberInterface
{
protected $authorizationChecker;
function __construct(AuthorizationChecker $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$user = $event->getData();
$form = $event->getForm();
if($user){
$form->add('firstName', TextType::class);
$form->add('lastName', TextType::class);
$form->add('password', PasswordType::class, array(
'mapped' => false
));
$form->add('profileImage', FileType::class, array(
'data_class' => null
));
if (in_array("ROLE_USER", $user->getRoles())) {
$form->add('contactNumber', TextType::class);
$form->add('gender', ChoiceType::class, array(
'choices' => array(
'Male' => 'm',
'Female' => 'f'
),
'placeholder' => 'provide_gender'
));
$form->add('college', EntityType::class, array(
'placeholder' => 'provide_college',
'class' => 'AppBundle\Entity\College')
);
$form->add('interest', EntityType::class, array(
'class' => 'AppBundle\Entity\Interest',
'multiple' => true,
'expanded' => false,
'by_reference' => false,
)
);
}
if($this->authorizationChecker->isGranted('ROLE_ADMIN') ) {
$form->add('isActive', ChoiceType::class, array(
'choices' => array(
'account_active' => '1',
'account_inactive' => '0'
),
'placeholder' => 'provide_status'
));
}
}
//if the selected user has role_user only then display the following fields in edit profile view
else {
$form->add('username', EmailType::class);
$form->add('password', PasswordType::class, array(
'constraints' => array(new NotBlank(array(
'message' => 'user.password.not_blank'
)
),),
));
}
}
}
This is AddDepartmentDegreeCourseFieldSubscriber
namespace AppBundle\Form\EventListener;
use AppBundle\Entity\Degree;
use AppBundle\Entity\Department;
use Doctrine\ORM\EntityManager;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormInterface;
class AddDepartmentDegreeCourseFieldSubscriber implements EventSubscriberInterface
{
protected $em;
function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(
FormEvents::PRE_SET_DATA => 'onPreSetData',
FormEvents::PRE_SUBMIT => 'onPreSubmit'
);
}
protected function addElements(FormInterface $form, Department $departments = null, Degree $degree = null)
{
// Add the department element
$form->add('department', EntityType::class, array(
'data' => $departments,
'placeholder' => 'provide_department',
'class' => 'AppBundle\Entity\Department')
);
// Degree are empty, unless we actually supplied a department
$degree = array();
if ($departments) {
// Fetch the courses from specified degree
$repo = $this->em->getRepository('AppBundle:Degree');
$degree = $repo->findByDepartment($departments, array('name' => 'asc'));
}
// Add the province element
$form->add('degree', EntityType::class, array(
'placeholder' => 'provide_degree',
'class' => 'AppBundle\Entity\Degree',
'choices' => $degree)
);
// Cities are empty, unless we actually supplied a province
$courses = array();
if ($degree) {
// Fetch the cities from specified province
$repo = $this->em->getRepository('AppBundle:Course');
$courses = $repo->findByDegree($degree, array('name' => 'asc'));
}
// Add the Course element
$form->add('course', EntityType::class, array(
'class' => 'AppBundle\Entity\Course',
'choices' => $courses,
));
}
function onPreSubmit(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if (isset($data['degree'])) {
// Note that the data is not yet hydrated into the entity.
$degree = $this->em->getRepository('AppBundle:Degree')->find($data['degree']);
$department = $this->em->getRepository('AppBundle:Department')->find($data['department']);
$this->addElements($form, $department, $degree);
}
}
function onPreSetData(FormEvent $event) {
//echo "before submit";die;
$user = $event->getData();
$form = $event->getForm();
if($user){
//if the selected user has role_user only then display the following fields in edit profile view
if (in_array("ROLE_USER", $user->getRoles())) {
$degree = ( !empty($user) && !empty($user->getCourse())) ? $user->getCourse()->getDegree() : null;
$departments = ( !empty($user) && !empty($user->getCourse())) ? $user->getCourse()->getDegree()->getDepartment() : null;
$this->addElements($form, $departments, $degree);
}
}
}
}
At this point I am really clueless what could be causing this, I will really appreciate any help here...
There's quite a few things wrong in what you've provided. Elements of which may contribute to the behaviour you're seeing.
Security rules
In security.yml, you have this line:
- { path: ^/admin/, roles: ROLE_ADMIN }
Which means that any use without ROLE_ADMIN is going to get shot back to the login screen if they access this pattern.
Meanwhile, in your controller, you're always directing the user back to an Admin based route:
/*
* #Route("admin/profile", name="admin_profile")
*/
Which means, no matter what they do, they're always sent to an admin pattern. This means that if they change their role, they'll be kicked by the firewall.
Controller
In your controller you're binding the user entity to a formtype, fine. But then the way you're coding implies that you don't understand how this works.
When you call handleRequest, Symfony pulls the data from the form (if its been submitted), and 'merges' it with the entity that you passed to it. This means that you don't have to call any setters on the object, as all that's already been done for you.
User entity
What you should have on your User entity, is a PlainPassword field, and a Password field. The form only maps to the PlainPassword field.. then, in your controller, take the PlainPassword value, encode it, set it to the entity's Password field, and make sure you clear the PlainPassword value (you dont want to store that). If you've implemented UserInterface (which you should have) on your custom user entity, you should have a method called eraseCredentials, this is what this is for. Here is an example.
The upshot of this is that when you're checking for something, like say, a new password, you only have to do this:
if($profileForm->isValid()){
// $userInfo is populated by handleRequest
if ($userInfo->getPlainPassword()) {
// do your encoding/erasing credentials here
}
// ...
}
What I would also suggest, is that you write a proper UserManager class to handle most of those things. It centralises things and makes it easier to debug. Heres an example
If you use something such as as I've written in the example, it also means that when you want to update a users info, you only have to call $userManager->updateUser($user) and it'll do all the donkey work for you.
Situation
I have an entity Item that can has a collection of Items (preconditions) with the same class Item. I used this documentation for making a form with the collection type. I want to make almost the same like in the documentation. But my situation is:
My collecting items are self-referenced to the main item
I want to add already existing items from an existing list, not creating new ones
With prototyping, i'm adding a new choosed collection item with JS with the same HTML structure like the default repopulation from symfony's form does. But naturally with the replaced choosed relation ID.
Persisting the relations is working, but only with a manual hack, and with missing datas into the $form object.
Problems
When i add an relation, with js prototyping and post the data, a new entity with the correct Item class is added in the ArrayCollection. This new entity returns with the posted ID as key as ArrayCollection item, but an empty entity itself as value. (New one is with key 1 below)
#collection: ArrayCollection {#829 ▼
-elements: array:2 [▼
5 => Item {#834 ▼
#id: 5
-title: "foo"
+preconditions: PersistentCollection {#836 ▶}
}
1 => Item {#890 ▼
#id: null
-title: null
+preconditions: ArrayCollection {#886 ▶}
}
]
}
Problem 1: This results on persisting that an unknown new entity want to be inserted (Because entity with ID null doesn't exists in the DB), and then with invalid null datas. It must not be inserted because its actually already existing.
Problem 2: Before persist i must modify the ArrayCollection property by hand, invalid entities i have to refill. So the entries are valid for relation persisting.
Problem 3: When i modify the ArrayCollection this way, i need this datas to be accessable also in twig. And an update into $form will result an Exception that i can’t modify the $form after it was submitted. Then i saw here the suggestion of an event listener, but even with an event listener i cant’ update the $form because either $form is not prepared or is too late to update.
Questions
Is following the right way to perform my goal, or does exist a better variant?
How can the new entities in the ArrayCollection be already be filled correctly after a post?
Code
Controller:
public function newesiAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
// For repopulating an existing entity
$item = 0 < $id ? $em->getRepository('AppBundle:Item')->find($id) : new Item();
$form = $this->createForm(new ItemType($this->container), $item);
$form->add('submit', 'submit', array('label' => 'speichern'));
$form->handleRequest($request);
/* After handleRequest, the posted (also new) precondition entities are given. But not as expected. So we update the array collection.
* Not as expected meant: New relation entities becomes new entities with NULL as id. So entities with NULL as id wantet to be INSERTED, what is not our goal.
*/
foreach($item->preconditions as $precondition_entity_id => $precondition_entity) {
$precondition_entity_inner_id = $precondition_entity->getId();
if(null === $precondition_entity_inner_id) {
$item->preconditions[$precondition_entity_id] = $em->getRepository('AppBundle:Item')->find($precondition_entity_id);
}
}
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($item);
$em->flush();
}
return $this->render('AppBundle:Item:edit.html.twig', array(
'form' => $form->createView(),
));
}
ItemType:
<?php
// src/AppBundle/Form/Type/ItemType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class ItemType extends AbstractType
{
public function __construct($container) {
$this->container = $container;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
$builder->add('preconditions', 'collection', array(
'type' => new PreconditionType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
// $em = $this->container->get('doctrine')->getManager();
// $form = $event->getForm();
// // Replace the new related entity that have NULL in its ID, with an fully loaded entity
// $entity = $event->getData();
// foreach($entity->preconditions as $precondition_entity_id_posted => $possible_empty_entity) {
// $precondition_entity_id_loaded = $possible_empty_entity->getId();
// if(null === $precondition_entity_id_loaded) {
// $entity->preconditions[$precondition_entity_id_posted] = $em->getRepository('AppBundle:Item')->find($precondition_entity_id_posted);
// }
// }
// $form->get('preconditions')->setData($entity->preconditions);
});
$builder->add('available_items', 'entity', array(
'class' => 'AppBundle\Entity\Item',
'multiple' => true,
'expanded' => false,
'mapped' => false
));
$builder->add('add_item_precondition', 'button', array(
'attr' => array(
'onclick' => "addPreconditionForm('" . $this->getName() . "_available_items')"
)
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Item',
));
}
public function getName()
{
return 'item';
}
}
?>
Precondition type:
<?php
// src/AppBundle/Form/Type/PreconditionType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PreconditionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('relation', 'hidden', array(
'mapped' => false,
'required' => false
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Item'
));
}
public function getName()
{
return 'precondition';
}
}
?>
I had the possibility to discuss this with an employee of sensiolabs. Its officially not possible to automate this with the built-in tools, you must loop the collection entries and re-get the incomplete entities.
So the answers to my questions:
Yes, it is the correct way go get the missing entities
No, its not possible to get those automatically
This is a wierd situation because magento is loading my backend model, its just not calling it when I load and save it. I know this because 1. I see it in my database, 2. when I rename my backend model, my test case fails. Here is my code
It saves my values just fine and completely ignores my afterload and beforesave methods.
TEST CASE
<?php
class Super_Base_Test_Controller_Test extends EcomDev_PHPUnit_Test_Case_Controller {
const DEFAULTSTORE = 1;
public function setUpMocks() {
$this->setCurrentStore(1);
$customer = Mage::getSingleton('customer/customer')
->load(1);
$customer->setCoinBalance(20)
->save();
}
public function setUp() {
$this->setUpMocks();
$data = array(
'customer_id'=>1,
'message'=>'this is a test message',
'income'=>20,
'created_at'=>'9/11/84',
'updated_at'=>'9/11/84',
'current'=>1
);
$this->getRequest()->setParams($data);
}
protected function getTearDownOperation() {
return PHPUnit_Extensions_Database_Operation_Factory::TRUNCATE();
}
}
backend model
<?php
/**
* Created by PhpStorm.
* User: numerical25
* Date: 3/8/14
* Time: 6:22 PM
*/
class Super_Coin_Model_Customer_Attribute_Coinbalance extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract {
protected function _afterLoad()
{
if (!is_array($this->getValue())) {
$value = $this->getValue();
$this->setValue(empty($value) ? false : unserialize($value));
}
}
protected function _beforeSave() {
if (is_array($this->getValue())) {
$this->setValue(serialize($this->getValue()));
}
}
public function setCoinAmount($amount) {
$this->setValue($amount);
}
}
installation file
$eavsetup->addAttribute('customer', 'coin_balance', array(
'input' => 'text',
'type' => 'decimal',
'label' => 'Customer Coin Balance',
'backend' => 'coin/customer_attribute_coinbalance',
'global' => 1,
'visible' => 1,
'required' => 0,
'user_defined' => 1, ));
When I set break points, the system completly ignores my methods.
Look at abstract class Mage_Eav_Model_Entity_Attribute_Backend_Abstract. It contains the following public methods: beforeSave() and afterLoad().
There are no _afterLoad() and _beforeSave() methods in that class
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 5 Entities:
Affiliation
Person
User
UserAffiliation
PersonAffiliation
My goal is to display a list of choice fields where I can choose all the UserAffiliations which are not in PersonAffiliations.
My idea is to create a public function in the UserAffiliationRepository which will return only those affiliations for a specific user which are not preset for a specific person.
For that, I am using:
class UserAffiliationRepository extends EntityRepository
{
public function getUnselectedAffiliations( $user_id = null, $person_id = null )
{
$commQB = $this->createQueryBuilder( 'ua' )
->select('ua');
$commQB->where("ua.user_id = {$user_id}");
$commQB->andWhere( "ua.affiliation_id not in ( select pa.affiliation_id FROM SciForumVersion2Bundle:PersonAffiliation pa where pa.person_id = 3077 )" );
return $commQB->getQuery()->getResult();
}
}
And this works fine.
Now, I would like to use this result in a FormBuilder. For that, In my controller, I am using:
$affiliations = $em->getRepository('SciForumVersion2Bundle:UserAffiliation')->getUnselectedAffiliations($user->getId(), $author->getId())
$enquiry = new PersonAffiliation();
$formType = new SubmissionAffiliationAddFormType($user, $affiliations);
$form = $this->createForm($formType, $enquiry);
And then in the Form class, I am using:
$builder->add('affiliation', 'entity', array(
'class' => 'SciForumVersion2Bundle:UserAffiliation',
'multiple' => true));
But here, I am getting all the affiliations for the specific user, not only thos ones which are not allready in the PersonAffiliations entity.
Any help? Thank you.
You have to migrate your getUnselectedAffiliations function directly into entity_type in the following way
$builder->add('affiliation', 'entity', array(
'class' => 'SciForumVersion2Bundle:UserAffiliation',
'multiple' => true,
'query_builder' = function(EntityRepository $repo) use ($yourParameters){
return $repo->createQueryBuilder(....);}));
if you want to pass $yourParameters, you have to do that into __construct function (implement it, if you don't have one) and when you create your form, you can pass them along the calls