I'm tryin to learn few things about Zend Framework and I got stucked on so simple operation like 'Edit' DB entry.
I've got list of contacts in MySQL db and my intention is to fill form with information from one row, edit it and save it back to db (update statement). I tried almost everthing that came into my mind, checked out google and book about ZF, but there is some problem all the time. At this moment, when I want to do update, zf and mysql will create new db entry with new id and edited information filled in, but that is not what i want to do obviously because instead of one updated entry in DB I've got two - old one and new one with updated information.
Here are the importat parts of my code...please have a look at it, I can't figure out what I'm missing here.
part of indexcontroller:
public function createcontactAction()
{
$createContactForm = $this->_helper->_formLoader('addContact');
$this->view->addContactForm = $createContactForm;
}
public function editcontactAction()
{
$id = $this->getRequest()->getParam('id');
$contactModel = new Application_Model_Contacts();
$contactRow = $contactModel->find($id)->current();
$addContactForm = $this->_helper->formLoader('addContact');
if ($this->getRequest()->isPost() && $this->getRequest()->getPost('send', false) !== false) {
if ($addContactForm->isValid($this->getRequest()->getPost())) {
$contactRow->setFromArray($addContactForm->getValues());
$contactRow->save();
$this->_redirect('/index/editcontact/id/' . $contactRow->id);
}
} else {
$addContactForm->populate($contactRow->toArray());
}
$this->view->addContactForm = $addContactForm;
}
public function savecontactAction()
{
$form = $this->_helper->formLoader('addContact');
if ($this->getRequest()->isPost() && $this->getRequest()->getPost('send', false) !== false) {
if ($form->isValid($this->getRequest()->getPost())) {
$contactModel = new Application_Model_Contacts();
$contactRow = $contactModel->createRow($form->getValues());
$contactRow->save();
$this->_redirect('/index/editcontact/id/' . $contactRow->id);
}
}
$this->view->form = $form;
}
form - parts that matters:
class Application_Form_AddContact extends Zend_Form
{
public function init()
{
$this->setAction('/index/savecontact');
$this->setMethod(Zend_Form::METHOD_POST);
$this->setAttrib('id', 'index_savecontact');
$contactFirstName = new Zend_Form_Element_Text('first_name', array('size'=>32, 'maxlength'=>64, 'label'=>'Křestní', 'required'=>false));
$contactLastName = new Zend_Form_Element_Text('last_name', array('size'=>32, 'maxlength'=>64, 'label'=>'Přímění', 'required'=>true));
.
.
.
$contactNotes = new Zend_Form_Element_Textarea('notes', array('cols'=>32, 'rows'=>1, 'label'=>'Poznámky', 'required'=>false));
$contactSend = new Zend_Form_Element_Submit('send', array('label'=>'Odeslat'));
$this->addElements(array ($contactFirstName, $contactLastName, $contactStreet, $contactHouseNumber, $contactCity, $contactZipCode, $contactCountry,
$contactPhoneNumber, $contactMobileNumber, $contactEmail, $contactWebPage, $contactCrn, $contactVat, $contactNotes, $contactSend));
Thank you very much!
(If theres anything more you could need to help me with this just ask for it)
EDIT:
heres model for contacts:
class Application_Model_Contacts extends Zend_Db_Table_Abstract
{
protected $_name = 'contacts';
protected $_primary = 'id';
}
I'm a bit rusty with regards to Zend_Db_Table and Zend_Db_Table_Row (I'm assuming that is what your model uses), but my bet would be that you are missing the Primary Key (PK) in your $contactRow - I'm guessing you probably don't supply it via the form as I see you get it through GET. So just set the id to $id in your $contactRow and you should be fine.
In editcontactAction(), before $contactRow->save();, add : $contactRow->id = $id. If your row doesn't have a id specified, save() can't update.
You're trying to get an update without providing the id of the row you want to update. The data used for the query is $form->getValues() but the form doesn't seem to contain the id of the contact. Add the id as a hidden field (with the id as the value) to your form or set it separately with $contactRow->id = $id and it should update instead of insert.
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 want to write a function which will take a series of fields and input different values into a different databases. Right now it is only two separate database entries but I hope to implement more later. I want to input a new Saint into one table and then, if the user fills in the 'ofRegion' field, I want that to be stored in a different table. My problem comes about when the model tries to input the information for 'ofRegion.' I get a MySQL error ( 1054 ) stating there is an unknown column. I can see by the MySQL error that it is trying to input all the information from the previous entry as well as the new information. How do I clear the old information? Can I even do this or will I need multiple models for each table I want to enter information into?
Model Functions
public function input_saint($newSaintID)
{
//grab values from post stream
$this->saintID = $newSaintID;
$this->active = 1;
$this->nameFirst = $this->input->post('nameFirst');
$this->nameLast = $this->input->post('nameLast');
$this->gender = $this->input->post('gender');
$this->martyr = $this->input->post('martyr');
$this->nationality = $this->input->post('nationality');
$this->feastMonth = $this->input->post('feastMonth');
$this->feastDay = $this->input->post('feastDay');
$this->about = $this->input->post('about');
//insert information into the saint table
$this->db->insert('saint_table', $this);
}
public function set_of_region($newSaintID)
{
$this->saintID = $newSaintID;
$this->ofRegion = $this->input->post('ofRegion');
$this->db->insert('saint_of_region', $this);
}
Controller Function
public function saint_input()
{
//Check if user is logged in, if they are not, send them to the login screen
if($this->session->userdata('logged_in') == FALSE)
{
redirect('base/');
}
$this->load->library('form_validation');
//load Saint model and get the nation list
$this->load->model('saint_model');
//Load the nation list
$data['nationList'] = $this->saint_model->get_nations();
if($this->form_validation->run('saint_input')==FALSE)
{
$this->load->view('std/top');
$this->load->view('forms/saint_input', $data);
$this->load->view('std/bottom');
}
else
{
//generate saintID
$newSaintID = $this->saint_model->get_largest_saintID();
$newSaintID++;
$this->saint_model->input_saint($newSaintID);
//if specified, record the ofRegion
if($this->input->post('ofRegion') != NULL)
{
$this->saint_model->set_of_region($newSaintID);
}
//Send the user to this saint's single view page for review
redirect('base/display_one_saint/'.$newSaintID);
}
}
Thank you very much for your time and work!
That's because you're using $this as an array to store data before inserting it. $this is a reference to the object as a whole and any properties that you set on it will persist until they are unset. One solution is to change to an array for the insert() function as below:
public function set_of_region($newSaintID)
{
$ins_arr['saintID'] = $newSaintID;
$ins_arr['ofRegion'] = $this->input->post('ofRegion');
$this->db->insert('saint_of_region', $ins_arr);
}
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();
}
}
Once you're OK with basic record form built after example from Tutorial, you realize you want more professionally designed Record Form. E.g. I don't want to duplicate record form for the same table in User and Admin areas.
1) Does anyone use some mechanism, possibly inheritance, to reduce duplication of almost similar admin and user forms? Is that burdensome or sometimes you better just do with copy-pasting?
2) Has anyone considered it to be a good idea to build some basic Record class
that can determine that among several record forms on this page, the current post is addressed specifically to this record form
that can distinguish between Edit or Delete buttons clicks in some organized fashion.
3) My current practice includes putting all form config code (decorators, validations, initial values) into constructor and form submit handling is put into a separate ProcessSubmit() method to free controller of needless code.
All the above addresses to some expected Record Form functionality and I wonder if there is any guideline, good sample app for such slightly more advanced record handling or people are still reinveting the wheel. Wondering how far you should go and where you should stop with such impovements...
Couple of suggestions:
First of all - Use the init() function instead of constructors to add your elements when you are subclassing the form. The init() function happens after the parameters you pass to the class are set.
Second - Instead of subclassing your form - you can just set an "option" to enable the admin stuff:
class My_Record_Form extends Zend_Form {
protected $_record = null;
public function setRecord($record) {
$this->_record = $record;
}
public function getRecord() {
if ($this->_record === null || (!$this->_record instanceOf My_Record)) {
throw new Exception("Record not set - or not the right type");
}
return $this->_record;
}
protected $_admin = false;
public function setAdmin($admin) {
$this->_admin = $admin;
}
public function getAdmin() { return $this->_admin; }
public function init() {
$record = $this->getRecord();
$this->addElement(......);
$this->addElement(......);
$this->addElement(......);
if ($this->getAdmin()) {
$this->addElement(.....);
}
$this->setDefaults($record->toArray());
}
public function process(array $data) {
if ($this->isValid($data)) {
$record = $this->getRecord();
if (isset($this->delete) && $this->delete->getValue()) {
// delete button was clicked
$record->delete();
return true;
}
$record->setFromArray($this->getValues());
$record->save();
return true;
}
}
}
Then in your controller you can do something like:
$form = new My_Record_Form(array(
'record'=>$record,
'admin'=>My_Auth::getInstance()->hasPermission($record, 'admin')
));
There is nothing "wrong" with making a My_Record_Admin_Form that handles the admin stuff as well - but I found this method keeps all the "record form" code in one single place, and a bit easier to maintain.
To answer section 2: The edit forms in my code are returned from a function of the model: $record->getEditForm() The controller code ends up looking a little like this:
protected $_domain = null;
protected function _getDomain($allowNew = false)
{
if ($this->_domain)
{
return $this->view->domain = $this->_domain;
} else {
$id = $this->_request->getParam('id');
if (($id == 'new' || $id=='') && $allowNew)
{
MW_Auth::getInstance()->requirePrivilege($this->_table, 'create');
$domain = $this->_table->createRow();
} else {
$domain = $this->_table->find($id)->current();
if (!$domain) throw new MW_Controller_404Exception('Domain not found');
}
return $this->view->domain = $this->_domain = $domain;
}
}
public function editAction()
{
$domain = $this->_getDomain(true);
MW_Auth::getInstance()->requirePrivilege($domain,'edit');
$form = $domain->getEditForm();
if ($this->_request->isPost() && $form->process($this->_request->getPost()))
{
if ($form->delete && $form->delete->getValue())
{
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'index',
), null, true));
} else {
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'view',
'id'=>$form->getDomain()->id,
), null, true));
}
}
$this->view->form = $form;
}
So - the actual id of the record is passed in the URI /domain/edit/id/10 for instance. If you were to put multiple of these forms on a page - you should make sure to set the "action" attribute of the form to point to an action specific to that form.
I created a SimpleTable extends Zend_Db_Table and SimpleForm extends Zend_Db_Form classes. Both of these assume that your table has an auto-incrementing ID column.
SimpleTable has a saveForm(SimpleForm $form) function which uses the dynamic binding to match form element names to the columns of the record. I also included an overridable saveFormCustom($form) for any special handling.
The SimpleForm has an abstract setup() which must be overridden to setup the form. I use the init() to do the initial setup (such as adding the hidden ID field).
However, to be honest, I really don't like using the Zend_Form object, I feel like that should be handled in the View, not the Model or Controller.
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);
}