I'm overriding my doSave() method to basically do the following: I have a sfWidgetFormPropelChoice field that the user can either choose from, or type a new option. How can I change the widget's value? Or maybe I am approaching this the wrong way. So here is how I overrode the doSave() method:
public function doSave($con = null)
{
// Save the manufacturer as either new or existing.
$manufacturer_obj = ManufacturerPeer::retrieveByName($this['manufacturer_id']->getValue());
if (!empty($manufacturer_obj))
{
$this->getObject()->setManufacturerId($manufacturer_obj->getId()); // NEED TO CHANGE THIS TO UPDATE WIDGET'S VALUE INSTEAD?
}
else
{
$new = new Manufacturer();
$new->setName($this['manufacturer_id']->getValue());
$new->save();
$this->getObject()->setManufacturerId($new->getId()); // NEED TO CHANGE THIS TO UPDATE WIDGET'S VALUE INSTEAD?
}
parent::doSave($con);
}
You should use setDefault or setDefaults and then it will autopopulate with the bound values.
(sfForm) setDefault ($name, $default)
(sfForm) setDefaults ($defaults)
usage
$form->setDefault('WidgetName', 'Value');
$form->setDefaults(array(
'WidgetName' => 'Value',
));
You could do it in the action :
$this->form->getObject()->setFooId($this->foo->getId()) /*Or get the manufacturer id or name from request here */
$this->form->save();
But I prefer to do the kind of work you are doing with your manufacturer directly in my Peer so my business logic is always at the same place.
What I put in my forms is mainly validation logic.
Example of what to put in the save method of the Peer :
public function save(PropelPDO $con= null)
{
if ($this->isNew() && !$this->getFooId())
{
$foo= new Foo();
$foo->setBar('bar');
$this->setFoo($foo);
}
}
Two assumption here: a) your form gets the name of the manufacturer and b) your model wants the ID of a manufacturer
public function doSave($con = null)
{
// retrieve the object from the DB or create it
$manufacturerName = $this->values['manufacturer_id'];
$manufacturer = ManufacturerPeer::retrieveByName($manufacturerName);
if(!$manufacturer instanceof Manufacturer)
{
$manufacturer = new Manufacturer();
$manufacturer->setName($manufacturerName);
$manufacturer->save();
}
// overwrite the field value and let the form do the real work
$this->values['manufacturer_id'] = $manufacturer->getId();
parent::doSave($con);
}
Related
Hi I have problem when i tried to save attribute of model to database. I write in OctoberCMS and i have this function:
public function findActualNewsletter()
{
$actualNewsletter = Newsletter::where('status_id', '=', NewsletterStatus::getSentNowStatus())->first();
if (!$actualNewsletter) {
$actualNewsletter = Newsletter::where('send_at', '<=', date('Y-m-d'))->where('status_id', NewsletterStatus::getUnsentStatus())->first();
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
dd($actualNewsletter);
}
return $actualNewsletter;
}
getSentNowStatus()=2;
getUnsentStatus()=1;
dd($actualNewsletter) in my if statement show that status_id = 2 But in database i still have 1. I used this function in afterSave() so i dont need:
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
$actualNewsletter->save();
becosue i have error then i use save in save.
Of course i filled table $fillable =['status_id']. And now i dont know why its not save in database when it go to my if. Maybe someone see my mistake?
If you are trying to modify the model based on some custom logic and then save it, the best place to put it is in the beforeSave() method of the model. To access the current model being saved, just use $this. Below is an example of the beforeSave() method being used to modify the attributes of a model before it gets saved to the database:
public function beforeSave() {
$user = BackendAuth::getUser();
$this->backend_user_id = $user->id;
// Handle archiving
if ($this->is_archived && !$this->archived_at) {
$this->archived_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle publishing
if ($this->is_published && !$this->published_at) {
$this->published_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle unarchiving
if ($this->archived_at && !$this->is_archived) {
$this->archived_at = null;
}
// Handle unpublishing, only allowed when no responses have been recorded against the form
if ($this->published_at && !$this->is_published) {
if (is_null($this->responses) || $this->responses->isEmpty()) {
$this->published_at = null;
}
}
}
You don't have to run $this->save() or anything like that. Simply modifying the model's attributes in the beforeSave() method will accomplish what you desire.
I've read through the tutorials/reference of the Form-Component in Zend-Framework 2 and maybe I missed it somehow, so I'm asking here.
I've got an object called Node and bound it to a form. I'm using the Zend\Stdlib\Hydrator\ArraySerializable-Standard-Hydrator. So my Node-object has got the two methods of exchangeArray() and getArrayCopy() like this:
class Node
{
public function exchangeArray($data)
{
// Standard-Felder
$this->node_id = (isset($data['node_id'])) ? $data['node_id'] : null;
$this->node_name = (isset($data['node_name'])) ? $data['node_name'] : null;
$this->node_body = (isset($data['node_body'])) ? $data['node_body'] : null;
$this->node_date = (isset($data['node_date'])) ? $data['node_date'] : null;
$this->node_image = (isset($data['node_image'])) ? $data['node_image'] : null;
$this->node_public = (isset($data['node_public'])) ? $data['node_public'] : null;
$this->node_type = (isset($data['node_type'])) ? $data['node_type']:null;
$this->node_route = (isset($data['node_route'])) ? $data['node_route']:null;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
}
In my Controller I've got an editAction(). There I want to modify the values of this Node-object. So I am using the bind-method of my form. My form has only fields to modify the node_name and the node_body-property. After validating the form and dumping the Node-object after submission of the form the node_name and node_body-properties now contain the values from the submitted form. However all other fields are empty now, even if they contained initial values before.
class AdminController extends AbstractActionController
{
public function editAction()
{
// ... more stuff here (getting Node, etc)
// Get Form
$form = $this->_getForm(); // return a \Zend\Form instance
$form->bind($node); // This is the Node-Object; It contains values for every property
if(true === $this->request->isPost())
{
$data = $this->request->getPost();
$form->setData($data);
// Check if form is valid
if(true === $form->isValid())
{
// Dumping here....
// Here the Node-object only contains values for node_name and node_body all other properties are empty
echo'<pre>';print_r($node);echo'</pre>';exit;
}
}
// View
return array(
'form' => $form,
'node' => $node,
'nodetype' => $nodetype
);
}
}
I want to only overwrite the values which are coming from the form (node_name and node_body) not the other ones. They should remain untouched.
I think a possible solution would be to give the other properties as hidden fields into the form, however I don't wanna do this.
Is there any possibility to not overwrite values which are not present within the form?
I rechecked the code of \Zend\Form and I gotta be honest I just guessed how I can fix my issue.
The only thing I changed is the Hydrator. It seems that the Zend\Stdlib\Hydrator\ArraySerializable is not intended for my case. Since my Node-Object is an object and not an Array I checked the other available hydrators. I've found the Zend\Stdlib\Hydrator\ObjectProperty-hydrator. It works perfectly. Only fields which are available within the form are populated within the bound object. This is exactly what I need. It seems like the ArraySerializable-hydrator resets the object-properties, because it calls the exchangeArray-method of the bound object (Node). And in this method I'm setting the non-given fields to null (see code in my question). Another way would propably be to change the exchangeArray-method, so that it only sets values if they are not available yet.
So the solution in the code is simple:
$form = $this->_getForm();
$form->setHydrator(new \Zend\Stdlib\Hydrator\ObjectProperty()); // Change default hydrator
There is a bug in the class form.php, the filters are not initialized in the bindvalues method just add the line $filter->setData($this->data);
it should look like this after including the line
public function bindValues(array $values = array())
{
if (!is_object($this->object)) {
return;
}
if (!$this->hasValidated() && !empty($values)) {
$this->setData($values);
if (!$this->isValid()) {
return;
}
} elseif (!$this->isValid) {
return;
}
$filter = $this->getInputFilter();
$filter->setData($this->data); //added to fix binding empty data
switch ($this->bindAs) {
case FormInterface::VALUES_RAW:
$data = $filter->getRawValues();
break;
case FormInterface::VALUES_NORMALIZED:
default:
$data = $filter->getValues();
break;
}
$data = $this->prepareBindData($data, $this->data);
// If there is a base fieldset, only hydrate beginning from the base fieldset
if ($this->baseFieldset !== null) {
$data = $data[$this->baseFieldset->getName()];
$this->object = $this->baseFieldset->bindValues($data);
} else {
$this->object = parent::bindValues($data);
}
}
to be precious it is line no 282 in my zf2.0.6 library
this would fix your problem, this happen only for binded object situation
I ran into the same problem, but the solution of Raj is not the right way. This is not a bug as for today the code remains still similar without the 'fix' of Raj, adding the line:
$filter->setData($this->data);
The main problem here is when you bind an object to the form, the inputfilter is not stored inside the Form object. But called every time from the binded object.
public function getInputFilter()
...
$this->object->getInputFilter();
...
}
My problem was that I created every time a new InputFilter object when the function getInputFilter was called. So I corrected this to be something like below:
protected $filter;
...
public function getInputFilter {
if (!isset($this->filter)) {
$this->filter = new InputFilter();
...
}
return $this->filter;
}
I ran into the same issue today but the fix Raj suggested did not work. I am using the latest version of ZF2 (as of this writing) so I am not totally surprised that it didn't work.
Changing to another Hydrator was not possible as my properties are held in an array. Both the ObjectProperty and ClassMethods hydrators rely on your properties actually being declared (ObjectProperty uses object_get_vars and ClassMethods uses property_exists). I didn't want to create my own Hydrator (lazy!).
Instead I stuck with the ArraySerializable hydrator and altered my exchangeArray() method slightly.
Originally I had:
public function exchangeArray(array $data)
{
$newData = [];
foreach($data as $property=>$value)
{
if($this->has($property))
{
$newData[$property] = $value;
}
}
$this->data = $newData;
}
This works fine most of the time, but as you can see it blows away any existing data in $this->data.
I tweaked it as follows:
public function exchangeArray(array $data)
{
$newData = [];
foreach($data as $property=>$value)
{
if($this->has($property))
{
$newData[$property] = $value;
}
}
//$this->data = $newData; I changed this line...
//to...
$this->data = array_merge($this->data, $newData);
}
This preserves any existing keys in $this->data if they are missing from the new data coming in. The only downside to this approach is I can no longer use exchangeArray() to overwrite everything held in $this->data. In my project this approach is a one-off so it is not a big problem. Besides, a new replaceAllData() or overwrite() method is probably preferred in any case, if for no other reason than being obvious what it does.
Lets see the action (form is based on model)
$this->form->bind ($request->getParameter('task'));
if ($this->form->isValid())
{
// cakk
}
This all works good, its not valid when its really not valid etc.
But I want to edit some fields, for example a date must be always set to now. Or a password must be encoded. How can I do this?
You can override the doSave() method in the form .. something like this :
public function doSave($con = null)
{
$this->values['form field'] = 'newvalue';
parent::doSave($con);
}
$this->values is an array containing the values on the form.
Update
You could use a post validator .. like this (again in the form class) :
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this, 'methodName')))
);
public function methodName($validator, $values)
{
// check / change what you need to
$values['fieldname'] = 'new value';
// return values
return $values;
}
I have a form and after submission, I can view the form values using: var_dump($this->form->getValues();. One of my form values (from a multi-select widget) is this:
["cat_list"]=>
array(1) {
[0]=>
string(1) "1"
}
I wish to append a value to this array before saving the form. How do I do this? I thought I can do this:
$values = $this->form->getValues();
array_push($values['cat_list'], '99'); // <--- 99 is the number I want to append
$this->form->setCatList($values['cat_list']);
$this->form->save();
But that doesn't work:
Call to undefined method FrontendOfferForm::setCatList.
Any clues?
In the action where you initialize the form before the validation (normally in either the create or the update function), just pass to the form an empty object with the value you want to add arbitrarily already set.
Let's call your form myModelForm and your model myModel.
In the action (before the processForm)
$obj = new myModel();
$obj->setCatList(array(99));
$this->form = new myModelForm($obj);
This should work
You should override doUpdateObject function in the form calss.
protected function doUpdateObject($values)
{
parent::doUpdateObject($values);
if (isset($values['cat_list']))
{
$carList = is_array($values['cat_list']) ? $values['cat_list'] : array($values['cat_list']);
array_push($catList, 99);
$this->getObject()->setCatList($catList)
}
}
And that's it. You should call only $this->form->save() in the action.
You should have an other method than the one from #1ed.
When you save the form, it returns the object. So, you have to save the form first and then, update the cat_list:
$values = $this->form->getValues();
$object = $this->form->save();
array_push($values['cat_list'], '99'); // <--- 99 is the number I want to append
$object->setCatList($values['cat_list']);
$object->save();
By the way, if you choose the solution from #1ed, you should use an external parameter to define your 99, if you want to be able to use something else than 99.
Like:
$this->form->catListExtraValue = 99;
$this->form->save();
Then:
public $catListExtraValue = null;
protected function doUpdateObject($values)
{
parent::doUpdateObject($values);
if (isset($values['cat_list']) && null !== $this->catListExtraValue)
{
$carList = is_array($values['cat_list']) ? $values['cat_list'] : array($values['cat_list']);
array_push($catList, $this->catListExtraValue);
$this->getObject()->setCatList($catList)
}
}
I've been looking at this event-listeners page http://www.doctrine-project.org/documentation/manual/1_1/pl/event-listeners and I'm not sure which is the listener I have to use to make a change after the doSave() method in the BaseModelForm.class.php.
// PlaceForm.class.php
protected function doSave ( $con = null )
{
...
parent::doSave($con);
....
// Only for new forms, insert place into the tree
if($this->object->level == null){
$parent = Place::getPlace($this->getValue('parent'), Language::getLang());
...
$node = $this->object->getNode();
$method = ($node->isValidNode() ? 'move' : 'insert') . 'AsFirstChildOf';
$node->$method($parent); //calls $this->object->save internally
}
return;
}
What I want to do is to make a custom slug with the ancestors' name of that new place. So if I inserting "San Francisco", the slug would be "usa-california-san-francisco"
public function postXXXXXX($event)
{
...
$event->getInvoker()->slug = $slug;
}
The problem is that I'm inserting a new object with no reference to its parent. After it's saved, I insert it to the tree. So I can't change the slug until then.
I think a Transaction listener could work, but I'm use there is a better way I'm not seeing right now.
thanks!
You are looking at the wrong piece of code. As stated by benlumley, you should manage your slug directly in the model, not in the form. To achieve what you want (a recursive slug) is quite easy using doctrine's Sluggable behavior. You need to implement a getUniqueSlug() into your model so that it gets called by the behavior (it's automatic) and handle your slug specifities in there:
public function getUniqueSlug()
{
$slug = '';
$parent = $this->getParent();
if ($parent->exists())
{
$slug = $this->getParent()->getUniqueSlug().'-';
}
return $slug.$this->getName();
}
What we do here is basically traverse all the ancestors of the current object and append the slugs on the go (replace the getParent() by whatever method you use to retrieve an object's parent.
Firstly, I'd put this into the model rather than the form - that way if the object is ever edited/updated the behaviour would still happen.
In the form though, I'd use updateObject:
function updateObject($values = array()) {
parent::updateObject($values);
// do your stuff
}
In the model (looks like you are using doctrine ...) I'd put this into the postSave() method. As I say, I think its better there than the form.
I had the same problems, and the Doctrine_Record::postInsert(Doctrine_Event $event) method did not work for me. Indeed the node aren't hydrated yet.
I had to overwrite the sfFormObject::doSave method like this:
protected function doSave($con = null)
{
$is_new = $this->isNew();
parent::doSave($con);
$this->doSaveNestedSet($con);
$service = $this->getObject();
if( $is_new and ! $service->getClientId() and $parent = $service->getParent())
{
$service->setClient($parent->getClient());
$service->save();
}
}