I have a symfony 1.4 project and I am adding a new column via a migration. The new column in schema.yml looks like this:
has_private_data: { type: boolean, notnull: true, default: false }
The migration that gets generated looks like this:
<?php
/**
* This class has been auto-generated by the Doctrine ORM Framework
*/
class Version26 extends Doctrine_Migration_Base
{
public function up()
{
$this->addColumn('device', 'has_private_data', 'boolean', '25', array(
'notnull' => '1',
'default' => '0',
));
$this->addColumn('device_history', 'has_private_data', 'boolean', '25', array(
'notnull' => '1',
'default' => '0',
));
}
public function down()
{
$this->removeColumn('device', 'has_private_data');
$this->removeColumn('device_history', 'has_private_data');
}
}
Why it the length of this boolean field being set to 25? (My backend database is MySql.)
You can be save by ignoring that. In doctines Table.php on line 1361 you can find that if the type is booelan the length will be fixed to 1.
case 'boolean':
$length = 1;
Related
I want to store who is the "creator" and "updater" of every record as a reference to the "User" table in my database. This applies to every table, including the "User" table itself. This is because it can happen that a record is created either by the user that is registering or by some other already existing user.
On the "User" table I have a DB trigger that fills the "creator" and "updater" fields to the same value as the newly generated "id" by default, hence I would like to use a NOT NULL constraint on my "creator" and "updater" column. Unfortunately, this results in the following error: "Circular relations detected: User -> User. To resolve this issue you need to set nullable: false somewhere in this dependency structure."
Is there a way for me to have a circular relation with a not null constraint using TypeORM?
I was actually able to make it work. I'm going to share my solution in case anyone is looking for an answer.
Before I was trying to auto generate the "created_by" column in the following way:
#ManyToOne(type => User, { onUpdate: 'CASCADE', onDelete: 'CASCADE', nullable: false })
#JoinColumn({ name: 'created_by' })
creator: User
All I had to do to make it work was to remove the nullable: false from there and move it to a new #Column field so that in total I would have:
#Column({ nullable: false })
created_by: number
#ManyToOne(type => User, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
#JoinColumn({ name: 'created_by' })
creator: User
I had a similar issue. I have a scenario where I have foreign key in the same table.
If I explain my scenario a bit more. I was working on storing conditional statements in an application.
condition#1 a > 4
condition#2 a + b < 0
Now I wanted to give user the ability to put a logical operator in b/w condition#1 and condition#2 . Let say user selected OR operator in b/w.
That would look like
(condition#1 OR condition#2)
Now comes the interesting part where I felt the need to have FK in the same. What if user wants to add another condition with OR operator before or after #1 and #2.
condition#3 OR (condition#1 OR condition#2)
So for storing these conditions I created relationships.
I stored 2 records for this scenario
1st for storing (condition#1 OR condition#2) and 2nd record for storing
condition#3 OR (condition#1 OR condition#2)
Now come to the point how I fixed it.
Previously it was something like :
export class ConditionLogicalOperator {
#PrimaryGeneratedColumn({
type: "int",
name: "ConditionLogicalOperatorID"
})
ConditionLogicalOperatorID: number;
#Column("int", {
nullable: false,
name: "LogicalOperatorID"
})
LogicalOperatorID: number;
#ManyToOne(() => ConditionLogicalOperator, (ConditionLogicalOperator: ConditionLogicalOperator) => ConditionLogicalOperator.conditionLogicalOperators)
#JoinColumn({ name: 'ConditionID1' })
conditionLogicalOperator1: ConditionLogicalOperator | null;
#ManyToOne(() => ConditionLogicalOperator, (ConditionLogicalOperator: ConditionLogicalOperator) => ConditionLogicalOperator.conditionLogicalOperators2)
#JoinColumn({ name: 'ConditionID2' })
conditionLogicalOperator2: ConditionLogicalOperator | null;
....
....
}
The trick was to add an empty object { } to Relationship. Please notice { } at the end of #ManyToOne line in each property. It worked for me.
export class ConditionLogicalOperator {
#PrimaryGeneratedColumn({
type: "int",
name: "ConditionLogicalOperatorID"
})
ConditionLogicalOperatorID: number;
#Column("int", {
nullable: false,
name: "LogicalOperatorID"
})
LogicalOperatorID: number;
#ManyToOne(() => ConditionLogicalOperator, (ConditionLogicalOperator: ConditionLogicalOperator) => ConditionLogicalOperator.conditionLogicalOperators, { })
#JoinColumn({ name: 'ConditionID1' })
conditionLogicalOperator1: ConditionLogicalOperator | null;
#ManyToOne(() => ConditionLogicalOperator, (ConditionLogicalOperator: ConditionLogicalOperator) => ConditionLogicalOperator.conditionLogicalOperators2, { })
#JoinColumn({ name: 'ConditionID2' })
conditionLogicalOperator2: ConditionLogicalOperator | null;
....
....
}
I try to create form with html select element using EntityType. I must get values by some condition and by this condition necessary default value not select from database. So i get all options values, without one, that must be a default value. So i try to find a way to put this value to select. What i tried...
Set value in form:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $growing, ['user_id' => $user_id]);
$tableForm->get('garden')->setData($garden);
$form = $tableForm->createView()
Then i tried to set data in entity:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$growing->setGarden($garden);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $growing, ['user_id' => $user_id]);
$form = $tableForm->createView()
Then i tried to set default select value in form_builder using 'data' attribute:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $grow, [
'user_id' => $user_id,
'selected_choice' => $garden
]);
$form = $tableForm->createView();
Form_builder code:
class GrowingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('garden', EntityType::class , [
'class' => 'FarmBundle\Entity\Garden',
'query_builder' => function (GardenRepository $gr) use ($options) {
return $gr->queryFreeGardens($options['user_id']);
},
'attr' => [
'data-type' => 'text',
'class' => 'table-select',
'disabled' => true
],
'required' => false,
'data' => $options['selected_choice']
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FarmBundle\Entity\Growing',
'selected_choice' => null,
'user_id' => null
));
}
}
And code of query for query builder:
class GardenRepository extends \Doctrine\ORM\EntityRepository
{
public function queryFreeGardens($user_id)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.growing', 'grow')
->where('grow.plantDate is NULL')
->orWhere('grow.endDate is not NULL')
->andWhere('g.user = :user_id')
->orderBy('g.name')
->setParameter('user_id', $user_id);
return $qb;
}
}
And all of this 3 methods not works. Result is one, if entity not get for query in query builder, i cant set this entity. If i will set entity as default value, that was in query builder all will works fine.
How can i solve this problem?
try this
in controller:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $grow, [
'user_id' => $user_id,
'additional_id' => 7
]);
$form = $tableForm->createView();
in form:
class GrowingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('garden', EntityType::class , [
'class' => 'FarmBundle\Entity\Garden',
'query_builder' => function (GardenRepository $gr) use ($options) {
return $gr->queryFreeGardens($options['user_id'], $options['additional_id');
},
'attr' => [
'data-type' => 'text',
'class' => 'table-select',
'disabled' => true
],
'required' => false
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FarmBundle\Entity\Growing',
'additional_id' => null,
'user_id' => null
));
}
}
in repository:
class GardenRepository extends \Doctrine\ORM\EntityRepository
{
public function queryFreeGardens($user_id, $additional_id)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.growing', 'grow')
->where('grow.plantDate is NULL')
->andWhere('g.id = :additional_id')
->orWhere('grow.endDate is not NULL')
->andWhere('g.user = :user_id')
->andWhere('g.id = :additional_id')
->orderBy('g.name')
->setParameter('user_id', $user_id)->setParameter('additional_id', $additional_id);
return $qb;
}
}
maybe you will need to adjust your repository method to retrieve values in right way. There are or clause, you should add this additional id to both branches of your or clause. The main idea is to retrieve you selected object too.
I use Annotation in a Doctrine Entity Class. Annotation for a field are :
/**
* #var integer
*
* #ORM\Column(name="duree", type="integer", nullable=true)
*
* #Form\Type("Zend\Form\Element\Number")
* #Form\Attributes({"required":false, "placeholder":"Durée", "min":"1", "max":"20"})
* #Form\Required(false)
* #Form\AllowEmpty()
* #Form\Options({"label":"Durée :"})
* #Form\Filter({"name": "Int"})
* #Form\Validator({"name":"IsInt"})
*/
private $duree;
So the DB column can be Empty (nullable), and in the form i wan't the same (ie user can leave input empty). I've both annotation Required(false) and allowEmpty, but the form never valid (always got isEmpty for this field).
If i set #Form\Type to "Text", it is working fine (form is valid event if input is empty). But with the class Number, it'is not the same.
I've the same pb with a Select element (correspondinf to a relationship). Annotations are :
/**
* #var \Application\Entity\CcCategorie
*
* #ORM\ManyToOne(targetEntity="Application\Entity\CcCategorie")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="categorie", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* })
*
* #Form\Type("DoctrineModule\Form\Element\ObjectSelect")
* #Form\Attributes({"type":"select", "required":false})
* #Form\Options({"label":"Catégorie :"})
* #Form\Required(false)
* #Form\AllowEmpty()
* #Form\Options({
* "label":"Catégorie :",
* "empty_option": "---",
* "target_class": "Application\Entity\CcCategorie",
* "property": "label"
* })
*/
private $categorie;
But, with this the field has error (isEmpty) if the Select is set to empty option when validating Form.
The only workaround i've found is to set the annotation
* #Form\Type("\Application\Form\Entity\CcRepriseFieldset")
at the top of the entity class. The class CcRepriseFieldset extend Fieldset, and implement InputFilterProviderInterface. Then i specify the function in this class :
public function getInputFilterSpecification()
{
return array(
array(
"name" => "duree",
'required' => false,
'allow_empty' => true,
),
array(
"name" => "categorie",
'required' => false,
'allow_empty' => true,
),
);
}
With this it works... But it's not annotations.
I don't understand why annotation not work
thanks
Ok i found what is the probleme.
Annotation Builder made a array with all fields spec, like this :
array (size=7)
'name' => string 'reprise' (length=7)
'attributes' => array (size=0)
'elements' =>
array (size=8)
1 =>array (size=2)
...
'fieldsets' => array (size=0) empty
'type' => string '\Application\Form\Entity\CcRepriseFieldset'
(length=42)
'input_filter' =>
array (size=8)
'name' =>
array (size=4)
'name' => string 'name' (length=4)
'required' => boolean true
'filters' =>
array (size=3)
...
'validators' =>
array (size=2)
...
But this array is the Array for the FieldSet attached to the Entity.
So the Zend\Form\Factory never parse this because 'input_filter' are only parse for ZendForm Element not for fieldset (of course because Fieldset doesn't have SetInputFilter method)...
Ok i've a workaround but i'm not very statisfied of it .
First i've create a new FormFactory :
namespace Application\Form;
use Zend\Form\FieldsetInterface;
Class EntityFormFactory extends \Zend\Form\Factory {
public function configureFieldset(FieldsetInterface $fieldset, $spec)
{
$fieldset=parent::configureFieldset($fieldset, $spec);
$fieldset->input_filter_factory= $spec['input_filter'];
return $fieldset;
}
}
So this factory add the "input_filter" to a input_filter_factory variable of the fieldset.
Then in the fieldset class is :
class CcRepriseFieldset extends Fieldset implements \Zend\InputFilter\InputFilterProviderInterface
{
public function getInputFilterSpecification()
{
if (isset($this->input_filter_factory)){
return $this->input_filter_factory;
}
}
}
And finally, when i use annotationbuilder i change the formfactory :
$builder = new AnnotationBuilder($this->_em);
$builder->setFormFactory(new EntityFormFactory());
With this all is working fine... I'm not sur it's the best way to do it.
I have managed to override the template and the form for the registration page but one thing I have noticed is that the initial fields are being injected.
Here is my buildform Code:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('firstname', 'text', array('label' => 'First Name'))
->add('lastname', 'text', array('label' => 'Last Name'))
->add('over_18', 'checkbox', array('label' => 'Yes I am over 18', 'mapped' => false))
->add('email', 'text', array('label' => 'Email'))
->add('phone', 'text', array('label' => 'What is your telephone number?'))
->add('password', 'password', array('label' => 'Choose a password'))
;
}
When I do a {{ dump(form) }} in the template I get this:
I understand I can do $builder->remove() on those fields, but if I am overwriting the form should I really need to do this?
Thanks
Yes. This is the only way. Because the buildForm() method is chained to parent buildForm(). That's how the form parent-child dependency works in symfony2:
/**
* #return string
*/
public function getParent()
{
return 'fos_user_registration';
}
Not sure if this is the info you are looking for, but when using FOSUser and Sonata User, you can simply extend parent::buildForm() and then add your custom fields like this:
// src/Application/Sonata/UserBundle/Form/Type/RegisterType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
// call to FOSUser buildForm, brings default fields
parent::buildForm($builder, $options);
$builder
->add('phone','text',array('label' => 'phone','required'=>true))
;
//do $builder->remove(..) if necessary
}
I would like to use a conditional statement when creating a form in Symfony.
I am using a choice widget in general case. If the user selects the option "Other", I would like to display an additional text box widget. I suppose this can be done in javascript, but how can I still persist the data from 2 widgets into the same property in my entity?
I have this so far:
$builder->add('menu', 'choice', array(
'choices' => array('Option 1' => 'Option 1', 'Other' => 'Other'),
'required' => false,
));
//How to add text box if choice == Other ????
I was planing to use a DataTransfomer, but on 2 widgets??
I recommend to build a custom type for that, for example ChoiceOrTextType. To this type you add both the choice (named "choice") and the text field (named "text").
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ChoiceOrTextType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('choice', 'choice', array(
'choices' => $options['choices'] + array('Other' => 'Other'),
'required' => false,
))
->add('text', 'text', array(
'required' => false,
))
->addModelTransformer(new ValueToChoiceOrTextTransformer($options['choices']))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array('choices'));
$resolver->setAllowedTypes(array('choices' => 'array'));
}
}
As you already guessed, you also need a data transformer, which can be quite simple:
use Symfony\Component\Form\DataTransformerInterface;
class ValueToChoiceOrTextTransformer implements DataTransformerInterface
{
private $choices;
public function __construct(array $choices)
{
$this->choices = $choices;
}
public function transform($data)
{
if (in_array($data, $this->choices, true)) {
return array('choice' => $data, 'text' => null);
}
return array('choice' => 'Other', 'text' => $data);
}
public function reverseTransform($data)
{
if ('Other' === $data['choice']) {
return $data['text'];
}
return $data['choice'];
}
}
Now only make the "menu" field a field of that type.
$builder->add('menu', new ChoiceOrTextType(), array(
'choices' => array('Option 1' => 'Option 1', 'Option 2' => 'Option 2'),
'required' => false,
));