Hi all I'm using zend framework (but I think this is irrelevant) and php5 and I just want to modify an object of an object
public function saveSite($post) {
$form = new Diff_Form_Download();
$subform = new Diff_Form_DownloadSubform();
$form = $this->view->form;
$newSite = 0;
$sitesRecord = new Diff_Model_Sites();
$debugString = null;
if (is_array($post)) {
$subform = $this->getSubformByPost($post);
$debugString = $subform->getContent();
echo $debugString;
//new site instertion
if (is_null($subform)) {
$subform = $form->getSubForm('NewSite');
$newSite = 1;
}
$values = reset($subform->getValues());
$sitesRecord = Doctrine_Core::getTable('Diff_Model_Sites')->findOneBy('idSite', $values['idSite']);
if ($sitesRecord) {
$sitesRecord->idSite = $values['idSite']; //useless but necessary to make Doctrine understand to use update?
} else { //if the record is not present instantiate a new object (otherwise save() will not work
$sitesRecord = new Diff_Model_Sites();
}
$sitesRecord->content = $subform->getContent(); //$values['content'];
$sitesRecord->newHtml = $subform->getContent();
$sitesRecord->url = $values['url'];
$sitesRecord->shortName = $values['shortName'];
$sitesRecord->lastChanged = date("Y-m-d H:i:s");
$sitesRecord->pollingHours = $values['pollingHours'];
$sitesRecord->minLengthChange = $values['minLenghtChange'];
if (Zend_Auth::getInstance()->hasIdentity()) { //save the owner
$sitesRecord->idOwner = Zend_Auth::getInstance()->getIdentity()->idOwner; //retrieve the owner
$sitesRecord->save();
} else {
throw new Exception("your session have expired: \n please login again");
}
}
}
/**
* return the calling subform
* #param type $post
* #return type
*/
public function getSubformByPost($post) {
$form = new Diff_Form_Download();
$form = $this->view->form;
$subform = new Diff_Form_DownloadSubform();
$subformName = "site" . $post['idSite'];
$subform = $form->getSubForm($subformName);
return $subform;
}
public function refreshOneDiff($post) {
$debugString;
if (is_array($post)) {
$form = new Diff_Form_Download();
$form = $this->view->form;
$subform = new Diff_Form_DownloadSubform();
$subform = $this->getSubformByPost($post);
if (!$subform)
$subform = $this->view->form->getSubformByPost('NewSite');
$url = $subform->getUrl();
$idSite = $subform->getIdSite();
$crawler = new Crawler();
$newpageContent = $crawler->get_web_page($url);
$siteRecord = new Diff_Model_Sites();
$siteRecord = $subform->getSiteRecord();
if ($siteRecord) //check if the record is not null
$oldpageContent = $siteRecord->get('content');
else
$oldpageContent = null;
$differences = $this->getDiff($oldpageContent, $newpageContent);
if (!is_null($differences)) {
$siteRecord->content = $newpageContent;
$subform->setContent($newpageContent);
$subform->setContentDiff($differences);
$subform->setSiteRecord($siteRecord);
$subform = $this->getSubformByPost($post);
$debugString = $subform->getContent();
}
//echo $debugString;
$sitePresence = Doctrine_Core::getTable('Diff_Model_Sites')->findOneBy('idSite', $idSite);
if ($sitePresence) {
//$this->saveSite($post);
$this->debugtry($post);
}
} else {
}
}
Basically what I'm doing here is:
1) grab a the form from the view
2) grab a subform from the form (let's call it SubformX)
3) grab an object "siteRecordX" from SubformX
4) change a value of "siteRecordX"
5) call a function saveRecord()
6) In this function regrab the same SubformX and echoing the value of the object siteRecordX
Surprisingly if i change a variable of SubformX it will stay that way, if I change a variable of an object of SubformX it will remain unchanged (if I retrieve SubformX).
It looks like, even if SubformX is passed by reference it is not the same for it's subobjects, wich are passed by value and so they disappear changing the context of the function.
Can you please help me?
Thanks
EDIT
I still cannot solve this issue nor understand it:
This is the constructor of the subform:
public function __construct($site = null, $options = null) {
if ($site instanceof Diff_Model_Sites) {
$this->_shortName = $site->get('shortName');
$this->_url = $site->get('url');
$this->_content = $site->get('content');
$this->_idSite = $site->get('idSite');
$this->_siteRecord = $site;
$this->_lastChanged = $site->get('lastChanged');
}parent::__construct($options);}
while this is the function of SubformX i use to set the value.
public function setContent($contentText) {
$this->_siteRecord->content = $contentText;
$this->_content = $contentText;
}
and this is the function of the subform that I call to get the value
public function getContent() {
if (isset($this->_siteRecord)) {
//return $this->_content;
return $this->_siteRecord->content;
}
}
with the commented line I'm able to retrieve the updated value, not with the second.
This is a real mystery to me because i set and get them exactly the same way at exactly the same point and i cannot understand the difference.
Your _siteRecord property is an ORM object (Doctrine, it appears). So its data may have some behaviors that are not standard for a reference-type object. It surely has some override of __get and __set. It also employs caching. These things are necessary to keep the database-model interaction working properly, but they can confuse what should be a reference and value types. (See: http://www.codinghorror.com/blog/2006/06/object-relational-mapping-is-the-vietnam-of-computer-science.html)
P.S.: in your constructor you use:
$this->_content = $site->get('content');
$this->_siteRecord = $site;
but in your getContent() you use:
$this->_siteRecord->content;
That difference might be part of the problem.
Thank you all guys. It was Doctrine caching.
I have not investigated further the problem, but after getting rid of Doctrine everything works fine.
I have lost one entire day after this issue.
Moreover today I have lost another day for another curious problem related to Doctrine.
It seems that each time you gather data from your db Doctrine decode them for you (just like the php function utf8_decode($data).
Of course if you get the data and then put it back in the db again it will result in a total mayhem of coding and decoding.
This is enough. I still think that ORM are great programming tools but simply Doctrine is not.
I will not rest in peace since I'll have it eliminated from my program.
I will use Zend_Db library instead. Which I have mostly already done without regret and without facing the above-mentioned Doctrine's problems.
Again thanks to Seth Battin and Dave Random for the help and patience to understand my noob-coding.
Related
I'm trying to adapt a class of mine that handles tags for events stored in a JSON file. You can create tags, delete them, restore them, view them, etc. In the code below for this library you can see that I retrieve the array from the file during the constructor function so I use it and manipulate it throughout my classes' functions.
class tagHandler {
private $tagsFile = "/home/thomassm/public_html/functions/php/tags.json";
private $LstTags;
private $LstReturn;
function __construct() {
$this->LstTags = array();
if(!file_exists ($this->tagsFile)){
$fHND = fopen($this->tagsFile, "w");
$tmpArray = array(array("EID","EName","EColor", "EDel"));
fwrite($fHND, json_encode($tmpArray));
fclose($fHND);
}
$encodedInput = file ($this->tagsFile);
$this->LstTags = json_decode($encodedInput[0], true);
if(!$this->LstTags) $this->LstTags = array();
}
function __destruct(){
$this->update();
}
public function update(){
$this->LstTags = array_values($this->LstTags);
$fHND = fopen($this->tagsFile, "w");
fwrite($fHND, json_encode($this->LstTags));
fclose($fHND);
//empty memory region
$this->LstTags = array();
$encodedInput = file ($this->tagsFile);
$this->LstTags = json_decode($encodedInput[0], true);
}
//More functions that use the collected array here.
I am trying to adapt the class to deal with people signed up to my events. Each event has a record in my database that will store a field for an array of males who sign up and females who sign up. I wish for the constructor class to get the arrays(s) from the record so they can be manipulated like the previous class. The issue is to get the array I have to search the DB for a record with the Event ID (EID) and that will require a variable passed to the constructor function. To make things worse, this parameter has to be able to change in a loop. For example, the page listing all the events will have to use this class in a loop going through each record, so it can retrieve the array to manipulate it and then show it in a table / fullcalendar before repeating the process to get the next event. I have put the code I have so far below. Its not complete (some variables haven't been renamed to male and female, etc) and may be completely wrong, but it will give you a base to explain from.
class signupHandler {
private $LstMaleS;
private $LstFemaleS;
private $LstReturn;
function __construct($IntEID) {
$this->LstTags = array();
$StrQuery = "SELECT MaleS, FemaleS FROM tblEvents WHERE EID = ?";
if ($statement = TF_Core::$MySQLi->DB->prepare($StrQuery)) {
$statement->bind_param('s',$IntEID);
$statement->execute ();
$results = $statement->get_result ();
}
$this->LstTags = json_decode($encodedInput[0], true);
if(!$this->LstTags) $this->LstTags = array();
}
Thanks,
Tom
function decodeNames($StrNames){
$this->LstNames = array();
$this->LstNames = json_decode($StrNames, true);
if(!$this->LstNames) $this->LstNames = array();
$this->LstNames = array_values($this->LstNames);
}
function update(){
$this->LstNames = array_values($this->LstNames);
return json_encode($this->LstNames);
}
public function addSignUp($StrNames, $StrUsername, $StrStatus){
$this->decodeNames($StrNames);
$BlnPresent = false;
for($i = 0; $i < count($this->LstNames); $i++){
if($this->LstNames[$i][0] == $StrUsername){
$this->LstNames[$i][1] = $StrStatus;
$BlnPresent = true;
}
}
if($BlnPresent == false){
array_push($this->LstNames, array($StrUsername, $StrStatus, date("Y-m-d H:i:s")));
}
return $this->update();
}
I have decided to pass the encoded JSON array to the class each time I call a function from it. Before every function it is decoded and turned into an array and at the end it is then re-encoded and returned back to the file calling it. Now I no longer have any constructor or destruct functions.
I have a function I am using to add
I have an array $data that contains the user data I am trying to put into the db. Everything works except for the "makeUrlTag" function portion:
public function makeUrlTag() {
$url_tag = '';
if(isset($this->data['user']['first_name'])) {
$url_tag = $url_tag . $this->data['user']['first_name'];
}
if(isset($this->data['user']['last_name'])) {
$url_tag = $url_tag.$this->data['user']['last_name'];
}
$fan->url_tag = $url_tag;
}
public function createFan() {
$fan = new Fan;
$fan->fbid = isset($this->data['user']['id']) ? $this->data['user']['id'] : '';
$fan->email = isset($this->data['user']['email']) ? $this->data['user']['email'] : '';
$fan->first_name = isset($this->data['user']['first_name']) ? $this->data['user']['first_name'] : '';
$fan->last_name = isset($this->data['user']['last_name']) ? $this->data['user']['last_name'] : '';
$this->makeUrlTag();
$fan->save();
}
I call createFan with:
$this->createFan();
When I run this, I get the error:
Creating default object from empty value
in reference to the makeUrlTag(); portion. Particularly the line:
$fan->url_tag = $url_tag;
Any idea what's going on here? Again, taking out the makeUrlTag portion works fine. Thank you.
It's because your makeUrlTag() method doesn't know about the Fan which is in the $fan variable you created in the createFan() method and trying to use a non-existing object in the scope of makeUrlTag() method using this:
$fan->url_tag = $url_tag;
So, you need to make your $fan object available to makeUrlTag() and to do this you may add a protected property in your class:
class YourClass {
protected $fan = null;
public function makeUrlTag(){
$url_tag = '';
// ...
$this->fan->url_tag = $url_tag;
}
public function makeUrlTag(){
$this->fan = new Fan;
// rest of your code
// but use $this->fan instead of $fan
$this->fan->save();
}
}
So, now you can access the $fan object from any method of your class ussing $this->fan, that's it.
I'm building a multilanguage website with Laravel4.
In the database i have column named "content" that contains serialized values for multiple languages. For example:
a:3:{s:2:"gb";s:15:"This is English";s:2:"es";s:5:"Hola!";s:2:"si";s:19:"Slovenija je zakon!";}
The serialized array contains of:
Language abbreviation, taken from Session
Content that comes from the input field
Now when I add new language to the database it creates new serialized string. Great!
But when I want to unserialize that string and add a value into it, i get the following error:
unserialize() [<a href='function.unserialize'>function.unserialize</a>]: Error at offset 0 of 30 bytes
Any ideas what is going on? I understand the meaning of the error, but it just makes no sense, since I'm sure that value in the database is serialized string.
public function setContentAttribute($value)
{
$lang = (Session::has('my.locale') ? Session::get('my.locale') : Config::get('app.locale'));
/* Create new serialized string */
if(empty($this->content)) {
$data[$lang] = $value['content'];
$this->attributes['content'] = serialize($data);
/* Update values */
} else {
$data = $this->content;
$data = unserialize($data)
$data[$lang] = $value['content'];
$this->attributes['content'] = serialize($data);
}
}
P.S: I'm using mutators for adding values to database.
I hope it's clear enough. If there is anything unclear, please comment and I'll fix it.
Thanks!
Ok, I've managed to fix it. I was unserializing my code twice - once in the accessor and once in the mutator. Here is a working example:
public function getVsebinaAttribute($value)
{
$data = unserialize($value);
$lang = $this->getLang();
if (!empty($data[$lang])) {
return $data[$lang];
} else {
return '# Value has not yet been added';
}
}
public function setVsebinaAttribute($value)
{
if (isset($this->attributes['vsebina'])) {
$data = unserialize($this->attributes['vsebina']);
} else {
$data = array();
}
$lang = $this->getLang();
$data[$lang] = $value;
$this->attributes['vsebina'] = serialize($data);
}
protected function getLang()
{
return Session::has('my.locale') ? Session::get('my.locale') : Config::get('app.locale');
}
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.
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.