/**
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="mKeyword")
* #ORM\Entity(repositoryClass="KeywordRepository")
*/
class Keyword {
/**
* #ORM\OneToOne(targetEntity="Image",mappedBy="keyword" ,cascade={"all"})
* #var Image
*/
private $logo;
}
/**
* #Vich\Uploadable
* #ORM\Entity
* #ORM\Table(name="mKeywordLogo")
*/
class Image {
}
form
class KeywordType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title','text')
->add('logo',new ImageType())
image form
class ImageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file','vich_image',array(
'label'=>'logo'
));
}
when save form
get
Expected value of type "KeywordsBundle\Entity\Image" for association field "Mea\KeywordsBundle\Entity\Keyword#$logo", got "array" instead.
i add array parser in Keyword
public function setLogo($logo)
{
if(is_array($logo))
$logo = reset($logo);
$this->logo = $logo;
}
so get error
Expected value of type "KeywordsBundle\Entity\Image" for association field "KeywordsBundle\Entity\Keyword#$logo", got "Symfony\Component\HttpFoundation\File\UploadedFile" instead.
I Fond error, Form ImageType don't havee defaults, when i add this. save work fine
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Mea\KeywordsBundle\Entity\Image'
));
}
Related
I'm having a problem with Symfony options resolver where I need to specify a list of defined variables that should be normalized.
The problem is: I don't want to define all these variables again in
$resolver->setDefined();
because I have a list of defined fields in $builder and the same fields are defined in the entity SlotRequest.
Is there a different way of assigning all fields/variables from entity to resolver?
First approach:
$resolver->setDefined([
'date_form','etc..' ]);
But, it pointless because in the real world I have to normalize 10+ variables + 20 fields)
Second approach would be to parse all annotations from the entity 'SlotRequest', and then fill up an array with that object.
$resolver->setDefined($anArrayOfParsedFieldsFromEntity);
Is there a better way of doing this?
An example of using:
In controller:
$form = $this->createForm(new SlotRequestType(), new SlotRequest());
SlotRequestType:
class SlotRequestType extends AbstractType
{
/**
* #var CCriteria
*/
protected $resolved = null;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date_from',null,['property_path'=>'dateFrom']);
//more fields
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$this->resolved = new \CCriteria();
$resolver->setDefaults(array(
'data_class' => SlotRequest::class,
'allow_extra_fields' => true,
'method' => 'GET'
));
$resolver->setDefined([....]);// the list of fields
$resolver->setNormalizer('date_from', function (Options $options, $value) {
$dateFrom = new \DateTime($value);
$this->resolved->setStartDate($dateFrom->getTimestamp());
return $value;
});
//more normalizers
}
/**
* #return null
*/
public function getName()
{
return null;
}
/**
* #return CCriteria
*/
public function getResolved()
{
return $this->resolved;
}
Entity SlotRequest
<?php
namespace Test/Entity;
class SlotRequest
{
/**
* #var string
* #Assert\NotBlank(message="Parameter [date_from] is missing.")
* #Assert\Type(
* type="string",
* message="The value {{ value }} is not a valid {{ type }}."
* )
* #Assert\Date()
*/
public $dateFrom;
//more fields
}
I would like to create a google categories matching(first field categorie from database and second field a user autocomplete field from google categories) form where i have an entity CategoriesConfig :
private $id;
/**
* #var string
*
* #ORM\Column(name="category_site", type="string", length=100)
*/
private $categorySite;
/**
* #var string
*
* #ORM\Column(name="category_google", type="string", length=100)
*/
private $categoryGoogle;
In my Controller i tried this
/**
* #Route("/adminDashboard/categoriesMatching", name="googleShopping_categories")
* #Security("has_role('ROLE_SUPER_ADMIN')")
*/
public function categoriesMatchingAction(Request $request)
{
// create a task and give it some dummy data for this example
$idSite = $this->get('session')->get('_defaultWebSite')->getId();
$categories = $this->getDoctrine()->getRepository('DataSiteBundle:SiteCategory')->findBy(array('IdSite' => $idSite));;
$categories_config = new CategoriesConfig();
//var_dump($categories);exit;
$form = $this->createForm(new CategoriesConfigType($categories), $categories_config);
return $this->render('GoogleShoppingBundle:Default:categoriesMatching.html.twig', array(
'form' => $form->createView()
));
}
And my form type : CategoriesConfigType:
class CategoriesConfigType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
private $site_categories;
public function __construct ($site_categories) {
$this->site_categories = $site_categories;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach($this->site_categories as $k => $categorie){
$builder
->add('categorySite')
->add('categoryGoogle');
}
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Sp\GoogleShoppingBundle\Entity\CategoriesConfig'
));
}
}
I would like to have as many categories rows as row fields(website itecategorie and google categorie)
The result is like that:
Thank you in advance!
Your loop on $this->categories is uneffective, because the elements you add have the same name each time (categorySite and categoryGoogle), so the FormBuilder replaces the form field each time, instead of adding another one.
However, if you want your form to handle a Collection of CategoryConfigs, you need to take a different approach.
1) Create a CategoriesConfigType (as you did), but who is responsible of only a single CategoriesConfig entity
class CategoriesConfigType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('categorySite')
->add('categoryGoogle');
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Sp\GoogleShoppingBundle\Entity\CategoriesConfig'
));
}
}
2) Then use CollectionType field to manipulate your form as a whole collection of CategoryConfigTypes:
class YourCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('categoriesConfigs', CollectionType::class, array(
'entry_type' => CategoriesConfigType::class,
'entry_options' => array('required' => false)
);
}
}
So I'm trying to create a registration form in Symfony 2 which contains my "Person" entity. The person entity has a one-to-many join, and I want the registration form to allow the user to select a single instance of this "Many" side of the join.
The structure is Users and Institutions. A user can have many institutions. I want a user to select a single institution at registration time (but the model allows for more later).
The basic structure is:
RegistrationType -> PersonType -> PersonInstitutionType
…with corresponding models:
Registration (simple model) -> Person (doctrine entity) -> PersonInstitution (doctrine entity, oneToMany relation from Person)
I tried to pre-populate an empty Person & PersonInstitution record in the RegistrationController but it gives me the error:
Expected argument of type "string or Symfony\Component\Form\FormTypeInterface", "TB\CtoBundle\Entity\PersonInstitution" given
(ok above has been fixed).
I've moved the code from my website to here below, trying to remove all the irrelevant bits.
src/TB/CtoBundle/Form/Model/Registration.php
namespace TB\CtoBundle\Form\Model;
use TB\CtoBundle\Entity\Person;
class Registration
{
/**
* #var Person
*/
private $person
private $termsAccepted;
}
src/TB/CtoBundle/Form/RegistrationType.php
namespace TB\CtoBundle\Form;
use TB\CtoBundle\Form\PersonType;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('person', new PersonType());
$builder->add('termsAccepted','checkbox');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TB\CtoBundle\Form\Model\Registration',
'cascade_validation' => true,
));
}
public function getName()
{
return 'registration';
}
}
src/TB/CtoBundle/Entity/Person.php
namespace TB\CtoBundle\Entity;
use TB\CtoBundle\Entity\PersonInstitution
/**
* #ORM\Entity()
*/
class Person
{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="PersonInstitution", mappedBy="person", cascade={"persist"})
*/
private $institutions;
}
src/TB/CtoBundle/Form/PersonType.php
namespace TB\CtoBundle\Form;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('institutions', 'collection', array('type' => new PersonInstitutionType()))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TB\CtoBundle\Entity\Person',
));
}
/**
* #return string
*/
public function getName()
{
return 'tb_ctobundle_person';
}
}
src/TB/CtoBundle/Entity/PersonInstitution.php
namespace TB\CtoBundle\Entity
/**
* PersonInstitution
*
* #ORM\Table()
* #ORM\Entity
*/
class PersonInstitution
{
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="institutions", cascade={"persist"})
*/
private $person;
/**
* #ORM\ManyToOne(targetEntity="Institution", inversedBy="members")
*/
private $institution;
/**
* #ORM\Column(type="boolean")
*/
private $approved;
}
src/TB/CtoBundle/Form/PersonInstititionType.php
namespace TB\CtoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PersonInstitutionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('approved')
->add('person')
->add('institution')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TB\CtoBundle\Entity\PersonInstitution'
));
}
/**
* #return string
*/
public function getName()
{
return 'tb_ctobundle_personinstitution';
}
}
src/TB/CtoBundle/Controller/Registration.php
namespace TB\CtoBundle\Controller;
class RegisterController extends Controller
{
/**
*
* #param Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function registerAction(Request $request)
{
$registration = new Registration;
$person = new Person();
$institution = new PersonInstitution();
$person->addInstitution($institution);
$registration->setPerson($person);
// this causes error:
// Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
// $institution->setPerson($person);
$form = $this->createForm(new RegistrationType(), $registration);
$form->handleRequest($request);
if($form->isValid()) {
$registration = $form->getData();
$person = $registration->getPerson();
// new registration - account status is "pending"
$person->setAccountStatus("P");
// I'd like to get rid of this if possible
// for each "PersonInstitution" record, set the 'person' value
foreach($person->getInstitutions() as $rec) {
$rec->setPerson($person);
}
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
}
return $this->render('TBCtoBundle:Register:register.html.twig', array('form' => $form->createView()));
}
}
Here is a detailed solution for adding an Collection field to Person entity and formType.
Your complex question with Registration entity can be solved with this.
I suggest you to use this 3 entity related connection if it is really needed. (only because of termsAccepted data!?)
If you won't change your opinion, then use this annotation:
Registration code:
use TB\CtoBundle\Entity\Person;
/**
* #ORM\OneToOne(targetEntity="Person")
* #var Person
*/
protected $person;
Person code:
use TB\CtoBundle\Entity\PersonInstitution;
/**
* #ORM\OneToMany(targetEntity="PersonInstitution", mappedBy = "person")
* #var ArrayCollection
*/
private $institutions;
/* I suggest you to define these functions:
setInstitutions(ArrayCollection $institutions),
getInstitutions()
addInstitution(PersonInstitution $institution)
removeInstitution(PersonInstitution $institution)
*/
PersonInstitution code:
use TB\CtoBundle\Entity\Person;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="institutions", cascade={"persist"}))
* #var Person
*/
private $person;
PersonType code:
use TB\CtoBundle\Form\PersonInstitutionType;
->add('institutions', 'collection', array(
'type' => new PersonInstitutionType(), // here is your mistake!
// Other options can be selected here.
//'allow_add' => TRUE,
//'allow_delete' => TRUE,
//'prototype' => TRUE,
//'by_reference' => FALSE,
));
PersonController code:
use TB\CtoBundle\Entity\Person;
use TB\CtoBundle\Entity\PersonInstitution;
/**
* ...
*/
public funtcion newAction()
{
$person = new Person;
$institution = new PersonInstitution;
$institution->setPerson($person);
$person->addInstitution($institution);
$form = $this->createForm(new PersonType($), $person); // you can use formFactory too.
// If institution field is required, then you have to check,
// that is there any institution able to chose in the form by the user.
// Might you can redirect to institution newAction in that case.
return array( '...' => $others, 'form' => $form);
}
If you need more help in twig code, then ask for it.
A continuation of this question:
I'm trying to embed a collection of forms as described in the official docs here. Unfortunately, I'm getting the following exception:
The form's view data is expected to be an instance of class Acme\SiteBundle\Entity\BlogPost, but is a(n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) array to an instance of Acme\SiteBundle\Entity\BlogPost.
Which is odd, as I believe I followed the official docs to a 't':
BlogPostType:
class BlogPostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// static text fields/attrs (title, body, etc)
$builder->add('comments', 'collection', array('type' => new CommentType()));
}
public function getName()
{
return 'blogpost';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Acme\SiteBundle\Entity\BlogPost'));
}
}
CommentType:
class CommentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('commentId', 'hidden');
$builder->add('commentBody','text',array('label' => 'Comment:','attr'=>array('size'=>80,'class'=>'form-item-input form-type-texfield')));
}
public function getName()
{
return 'comment';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Acme\SiteBundle\Entity\Comment'));
}
}
Relationship as defined in BlogPost:
/**
* #var \Acme\SiteBundle\Entity\Comment.php
*
* #ORM\OneToMany(targetEntity="Comment", mappedBy="blogpost",
cascade={"all"},orphanRemoval=true)
* #ORM\OrderBy({"commentId" = "ASC"})
*/
private $comments;
And in Comment:
/**
* #var BlogPost
*
* #ORM\ManyToOne(targetEntity="BlogPost", inversedBy="comments")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="blog_id", referencedColumnName="id")
* })
*/
private $blogPost;
So, I'm not sure where I've gone wrong. Any ideas?
Turns out I was an idiot (big surprise there) and was passing the form builder an array. Old code that used to work was breaking it now. Whee!
I need to display the min date from a group of entities in a relationship. Eg. I have User and Contracts -> the User has many Contracts, but I need to display the minimum date from contracts.
Here is the code:
class User implements UserInterface
{
/**
* #ORM\OneToMany(targetEntity="Comp\ContractBundle\Entity\Contract", mappedBy="user_id")
* #ORM\OrderBy({"id" = "DESC"})
*
*/
private $contracts;
}
class Contract
{
/**
* #var string $datastart
*
* #ORM\Column(name="datastart", type="datetime")
*/
private $datastart;
/**
* #var string $dataend
*
* #ORM\Column(name="dataend", type="datetime")
*/
private $dataend;
/**
* #var integer $user_id
*
* #ORM\ManyToOne(targetEntity="Comp\AuthBundle\Entity\User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user_id;
}
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options){
#... other data
$builder->add('contracts','collection', array(
'type' => new ContractType()
) );
}
public function getName()
{
return 'User';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Comp\AuthBundle\Entity\User',
);
}
}
And the ContractType:
class ContractType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options){
$builder->add('name','text');
$builder->add('datastart','datetime');
}
public function getName()
{
return 'Contract';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Comp\ContractBundle\Entity\Contract',
);
}
}
The problem is, I get each Entity related to the User Entity - I just need to fetch one. If you know any good example - it would be great.
Change contracts field type from collection to entity, and google for query_builder attribute of entity field type.
In that attribute you can pass a closure (returning QueryBuilder) or QueryBuilder itself, in which you can retrieve specific records from given relationship.