I want to create readonly hidden field. Now I have field that looks like this:
$builder
->add('question_category_id', HiddenType::class);
And entity has method:
public function getQuestionCategoryId() {
return $this->getQuestion()->getQuestionCategory()->getId();
}
After saving I got following error:
Neither the property "question_category_id" nor one of the methods "addQuestionCategoryId()"/"removeQuestionCategoryId()", "setQuestionCategoryId()", "questionCategoryId()", "__set()" or "__call()" exist and have public access in class "Entity\UnitQuestionAnswer".
I could add dummy method
public function setQuestionCategoryId($id) {
return $this;
}
but it is not right way.
How to create readonly hidden field, or avoid of writing back data from from into entity?
S2.8 has a read_only attribute which would do what you want but it has been removed in 3.0.
The disabled attribute should work. Just be aware that the value itself will not actually be submitted symfony.com/doc/current/reference/forms/types/… so if you are doing anything funky with the posted data then that could be a problem.
I suppose it's possible to fool around with the internals but that would be more trouble than it is worth.
Personally, given that my get method was added just for the form, I would just add a corresponding set method and move on.
Related
I have a question - is here a possibility to configure AssociationField to work with specific property. i.e:
I have a Subscription Entity with a many-to-one relation to User, User has a __toString() method, that returns username, and it is used across the application, so I can't change it. In the 'create Subscription' form, I have AssociationField::new('user'), where I'm able to find User by his name.
But this is inconvenient, since, when I need to create a Subscription, many users with same names pop up. Instead, I want to be able to search Users by ID, or email.
Is there a way to override default behaviour?
Your AssociationField is made using Symfony EntityType. If you look into the form type used by this field.
//AssociationField.php
public static function new(string $propertyName, $label = null): self
{
return (new self())
//...
->setFormType(EntityType::class)
//...
It means that you can use every options from it. See more here.
In your case, it's quite easy modifying your label by either defining another property or a callback.
Then you can use the ->setFormTypeOption() to modify the entity type option.
So if you want to use a callback function to define a custom label:
AssociationField::new('user')
->setFormTypeOption('choice_label', function ($user) {
return $user->getEmail();
});
Or using php 7.4 arrow function:
AssociationField::new('user')
->setFormTypeOption('choice_label', fn($user) => $user->getEmail());
You can also define the email property as label:
AssociationField::new('user')
->setFormTypeOption('choice_label', 'email');
I'm using ActiveForm with Yii2 and by default it seems to generate default id's for fields if you don't set one, in the format of:
{action-name}-{field-name}
So for example if I had a field with the name of foo_bar used in an action of actionSettings then the id of this field would be generated as:
settings-foo_bar
I would prefer this to just be foo_bar.
Is this possible to change on a form by form basis?
Based on the answer provided by #Bizley I was investigating how the method calculated the name and found out there is another way to achieve this as well.
You can simply override the formName method of your respective model to return a blank value, such as:
public function formName() {
return '';
}
Whilst this has less overheads as you don't need to create a new class, it will also affect other things within your form such as the field names and also should not be used for forms which contain multiple different models as explained here.
Lastly, because this question was about changing how Yii formats the id, #Bizleys answer is the correct one; my solution is just another option of possibly achieving it another way.
ActiveField id is by default created based on the form's model name and field's name.
If you want to change it for the whole form override the method that does it:
protected function getInputId()
{
return $this->_inputId ?: Html::getInputId($this->model, $this->attribute);
}
and use this modified class in your form.
I am using Symfony with propel to generate a form called BaseMeetingMeetingsForm.
In MeetingMeetingsForm.class.php I have the following configure method:
public function configure() {
$this->useFields(array('name', 'group_id', 'location', 'start', 'length'));
$this->widgetSchema['invited'] = new myWidgetFormTokenAutocompleter(array("url"=>"/user/json"));
}
In MeetingMeetings.php my save method is simply:
public function save(PropelPDO $con = null) {
$this->setOwnerId(Meeting::getUserId());
return parent::save($con);
}
However propel doesn't know about my custom field and as such doesn't do anything with it. Where and how to I put in a special section that can deal with this form field, please be aware it is not just a simple save to database, I need to deal with the input specially before it is input.
Thanks for your time and advice,
You have to define a validator (and/or create your own). The validator clean() method returns the value that needs to be persisted.
In Doctrine (I don't know Propel) the form then calls the doUpdateObject() on the form, which in turns calls the fromArray($arr) function on the model.
So if it's already a property on your model you'll only need to create the validator. If it's a more complex widget, you'll need to add some logic to the form.
First, let me say, that I find the sfFormPropel form's interface inconsistent.
There is bind(), which returns nothing, but triggers validation, save() which returns the saved object, and bindAndSave(), which returns boolean, actually the return value of isValid(). Now, I have a working application, but I don't feel the code is right, and I'm quite new to symfony, so perhaps I'm missing something.
The object I need to create needs some external properties, that are not presented in the form, are external to the model, and are handled by the application (for example, the userId of the user, that created the entity, an external-generated guid, etc.).
Right now the flow is as follows:
get values from request and bind them to form
check if form is valid
if it's valid, add additional values and bind them to form one more time
save the form and return the object
The obvious answer would to add application-specific values to the values, retrieved from request, but It does not make sense to bind the application-specific values if the form is not valid, since they can be potentially expensive operations, may create database records, etc. Additionally, it should not be possible to pass those values with the post request, they should come from application only.
Now, I though that I have to let the model do these things, but since the data is external to the model, action still need to pass it to the model. The problem is, if I call $form->getObject() after bind(), it still has the old data, and not the data submitted.
What is the correct way to implement this kind of post-processing?
Second bounty is started to award the other valuable answer
The correct way would be setting your default values on the object you are passing to the form constructor. For example if you want to set the logged in user id on an object you are creating:
$article = new Article();
$article->setUserId($this->getUser()->getId());
$form = new ArticleForm($article);
if ($request->isMethod('post')) {
$form->bind($request->getParameter('article'));
if ($form->isValid()) {
$form->save();
}
}
Likewise for existing object, you can load the record and change any properties before passing it to the form constructor.
EDIT:
If you want to modify the object after validating, you can use $form->updateObject() like Grad suggests in his response. If the generated values depend on the submitted values, you can override sfFormObject::processValues():
class UserForm {
public function processValues($values) {
$values['hash'] = sha1($values['id'] . $values['username']);
return parent::processValues($values);
}
}
In case you need something from the action, you can always pass it as an option to the form:
$form = new UserForm($user, array('foo' => $bar));
That way, you can use $this->getOption('foo') anywhere in your form code, eg. in processValues().
It kind of depends of who has "knowledge" about the extra attributes. If they're really request specific, thus need to be processed in the controller, I go for binding, testing if valid and then update the bound object. To get the updated object with the bound (and validated) fields use the updateObject function.
$form->bind(..)
if ($form->isValid()) {
$obj = $form->updateObject(); // Updates the values of the object with the cleaned up values. (returns object)
$obj->foo = 'bar';
$obj->save();
}
But since this normally is also behaviour that is form specific, I usually go for overriding the Form class. By overriding the doUpdateValues() function you can easily access submitted data, and append your own data. Of course you can also go higher in the chain, and override the save() function.
To set custom data for this form, you can also 'publish' public methods, which can then be used by the controller.
I would like to validate an embedded form field before it gets saved in the database. Currently it will save an empty value into the database if the form field is empty. I'm allowing empty fields, but I want nothing inserted if the form field is empty.
Also quick question, how to alter field values before validating/saving an embedded form field?
$this->form->getObject works in the action, but $this->embeddedForm->getObject says object not found
I found a really easy solution. The solution is to override your Form's model class save() method manually.
class SomeUserClass extends myUser {
public function save(Doctrine_Connection $conn = null)
{
$this->setFirstName(trim($this->getFirstName()));
if($this->getFirstName())
{
return parent::save();
}else
{
return null;
}
}
}
In this example, I'm checking if the firstname field is blank. If its not, then save the form. If its empty, then we don't call save on the form.
I haven't been able to get the validators to work properly, and this is an equally clean solution.
Symfony gives you a bunch of hooks into the form/object saving process.
You can overwrite the blank values with null pre/post validation using by overriding the the doSave() or processValues() functions of the form.
You can read more about it here: http://www.symfony-project.org/more-with-symfony/1_4/en/06-Advanced-Forms#chapter_06_saving_object_forms