Create using save() is causing database duplicates in Laravel - php

I have very similar code that is functioning without a hitch elsewhere in my Laravel app, but for some reason the below code is creating two $paypal_object database entries that are identical except for the payment_id field:
DonateController.php
public function mimic()
{
try {
//This block is the addOrder function from the pizza tutorial
$paypal_object = new Paypal();
//The user who is making the payment
$paypal_object->user()->associate(Auth::user());
$paypal_object->amount = 50.00;
$paypal_object->description = "new subscription";
$paypal_object->state = $payment->getState();
$paypal_object->payment_id = $payment->getId();
$paypal_object->save();
} catch (Exception $ex) {
$message = $ex->getMessage();
$messageType = "error";
}
exit;
}
Database Results (with test data)
I've condensed the above code from my controller a little. If you'd like to see more of my code, let me know and I'd be happy to provide it. My theory right now is that for some reason my mimic() method is getting run twice, but I'm not sure how to test to see if that's true beyond including this in the above code, but it's not giving me any results this time:
echo '<script>console.log(' . json_encode("Testing to see how many times this message appears.") . ');</script>';
Even if it is running twice, I'm not sure how that's happening or where to check. I'm guessing it could well be another problem entirely, but I don't know what.
Right now, I'm accessing this method by pinging its route:
Route::get('/paypal/mimic', 'DonateController#mimic');
but for every 1 ping I make, I get 2 database entries as shown in the above image.
Paypal model:
class Paypal extends Eloquent
{
/**
* Get the user that made the paypal payment.
*/
public function user()
{
# Defines an inverse one-to-many relationship
return $this->belongsTo('User');
}
}
User model:
public function paypal(){
# User has many paypal payments - although just one subscription
# Defines a one-to-many relationship
return $this->hasMany('Paypal');
}
Thanks in advance for any help.

Related

Magento - getCustomer() giving unexpected results

I am experiencing some odd results when using Magento's
Mage::getSingleton('customer/session')->getCustomer();
I have the below code to first check if the customer is logged in, and if so to get the firstname of the logged in customer:
if(!$this->helper('customer')->isLoggedIn())
{
$name = Mage::getSingleton('customer/session')->getCustomer()->getFirstname();
print 'Hello '.$name;
// ...
}
Most of the time this works fine and if Joe Bloggs is logged in then it outputs:
Hello Joe
But every now and then, and it seems to be when a high amount of customers are logged in I get unexpected outputs with other names
Hello Lucy
or
Hello John
Is Mage::getSingleton('customer/session')->getCustomer() a foolproof way of getting the customers details or is it possible it's getting another logged in customers details? Or have I got a problem with my sessions mixing up?
As per the core code in Mage_Customer_Model_Session::getCustomer:
/**
* Retrieve customer model object
*
* #return Mage_Customer_Model_Customer
*/
public function getCustomer()
{
if ($this->_customer instanceof Mage_Customer_Model_Customer) {
return $this->_customer;
}
$customer = Mage::getModel('customer/customer')
->setWebsiteId(Mage::app()->getStore()->getWebsiteId());
if ($this->getId()) {
$customer->load($this->getId());
}
$this->setCustomer($customer);
return $this->_customer;
}
...there's no way that another customer's data could be loaded (by core Magento).
It would firstly return the customer model if it's already loaded, if not then load it if there's an ID present already, and finally it would return an empty customer model if nothing else.
It's possible that your sessions are getting mixed up. I suggest you check your configuration - are you using databases, files, Redis etc for session storage?

Symfony 2 stop saving form process

I am trying to link my local users stored in the database with some external services, so they can login anywhere with the same credentials.
I have an EventListener waiting for some FOSUserEvents like FOSUserEvents::REGISTRATION_SUCCESS or FOSUserEvents::CHANGE_PASSWORD_SUCCESS, fired when the data are valid but not actually saved, to perform some call to the various external services and replicate the new user credentials.
If the services return a message saying that the data are replicated, everything work fine.
But if a service say that there is a problem, no mater what it is, I would like to interrupt the saving process of the form and adding an error message.
The objective is to prevent Symfony to save the data, even if they are valid, if an external service say no, and I don't know how to perform this kind of emergency stop.
Actually, it's only based on FOSUserBundle but if I find a working solution, I will have to execute something similar for other entities, that why I try to be as generic as possible.
Here a some of my code
class MyListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'createUser',
FOSUserEvents::CHANGE_PASSWORD_SUCCESS => 'editUser'
);
}
public function createUser(FormEvent $event)
{
$user = $event->getForm()->getData();
// Check if the user exists before trying to edit
$result = $this->_userExists($user);
if($result['value'] == false)
{
// Create the user
$result = $this->_createUser($user);
// Check if the user is successfully replicated
if($result['result'] != 'success')
{
/*
* Emergency stop (user not replicated)
*/
}
}else{
/*
* Emergency stop (user doesn't exists)
*/
}
}
. . .
}
Actually I didn't find any way to stop the saving workflow and returning to the form page to display an error message. So if somebody has an idea of how to perform this, feel free so write your idea, and thanks you all guys. Stackoverflow is really the best ;-)

Detecting changes in the model; php yii framework

I'm creating an audit trail module that i will put in a larger system; and i've created a table to store the trail entries , as an "auditor" what i want to see the currently logged on user, the page where he/she is in, what action he/she did, and what were the changes and when...
these are basically what i want to see; my audit trail table looks like:
User| Timestamp| Module Name| Action| Old Value| New Value| Description
i basically had no problem getting the user, by
Yii::app()->session['username'];
the page/module and action by getting the controller's :
$this->module->getName();
$this->action->id;
My problem lies with the changes old value to new value, the edits done by the user.
i could sort of "sniff" out what edits/ changes he/she did by literally copying the variables and passing it through my function where i create the log.. How do i do this dynamically?
i sort of want to detect if a certain model's properties or attributes has been changed and see what changes were made so that i could get a detail log...Thanks ! sorry, i'm really trying hard to explain this.
In each model that you want to observe you can write a afterFind() method, where you store the current DB attributes into some private variable, e.b. _dbValues. Then in beforeSave() you verify the current attributes with the ones in _dbValues and create an audit record if there was a change.
After you have this working, you can take it a step further and create a behavior from it. You'd put the private variable, the afterFind() and the beforeSave() method there. Then you can attach that behavior to many records.
Quick example:
class Book extends CActiveRecord
{
private $oldAttrs = array();
public static function model($className = __CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'book';
}
protected function afterSave()
{
// store history
if (!$this->isNewRecord) {
$newAttrs = $this->getAttributes();
$oldAttrs = $this->getOldAttributes();
// your code
}
return parent::afterSave();
}
protected function afterFind()
{
// Save old values
$this->setOldAttributes($this->getAttributes());
return parent::afterFind();
}
public function getOldAttributes()
{
return $this->oldAttrs;
}
public function setOldAttributes($attrs)
{
$this->oldAttrs = $attrs;
}
}
Your solution is good, but what if there're 2 threads that call ->save() at the same time?
Assume that:
the 1st thread find record, save the A status.
the 2nd thread find record, save the A status.
then 1st thread change record to B, call ->save(). System will log A->B
then 2nd thread change record to C, call ->save(). System will log A->C
summary, there're 2 log: A->B, A->C. If this is not problem for you, just ignore it and do the above solution.

How to put begin-commit transaction in controller: cakephp?

I'm working on a controller that will update a few tables. I am able to call my model from my controller and inside the model function I can make a begin and commit my query, it can rollback should an error happen.
Here is my sample:
Controller:
//update table when update button is clicked
if (!empty($this->data)) {
if ($this->Item->update($this->data)) {
$this->Item->create();
$this->redirect('/sample');
return;
} else {
$this->set('data', $this->data);
}
}
Model:
function update($data)
{
$this->begin($this);
if(!parent::save($data)) {
$this->rollback($this);
return false;
}
$this->commit();
return true;
}
Now this works fine. But what I need to do is to call another model in my controller like "$this->"ANOTHER MODEL HERE"->update()". I need to have rollback should a problem occur with either model transaction. What I'm thinking is to put a commit in my controller after both model call succeeds.
Much like this:
CONTROLLER PHP:
BEGIN TRANSACTION
->CALLS MODEL1
IF(MODEL1 == ERROR){
ROLLBACK
}
->CALLS MODEL2
IF(MODEL2 == ERROR){
ROLLBACK
}
COMMIT WHEN NO PROBLEM IS ENCOUNTERED
So is it possible to perform commit in controller? I am only able to do it in model. Thanks in advance!
So is it possible to perform commit in controller? I am only able to do it in model.
Yes, you can perform commit or rollback from within the controller. You need to get the datasource from one of your models first. In the controller code, simply reference one of the models you are using (assuming they are all in the same database):
$ds = $this->MyModelName->getdatasource();
Then you can begin, commit, and rollback to that datasource from within the controller.
$ds->begin();
// do stuff and save data to models
if($success)
{
$ds->commit();
}
else
{
$ds->rollback();
}
I actually have a rollback or commit in more than one place if I am bailing on the action and redirecting or finalizing in some step and redirecting. I just illustrate a simple case here.
Handling transactions in the controller makes the most sense to me since the controller action is where the transaction boundaries really reside conceptually. The idea of a transaction naturally spans updates to multiple models. I have been doing this using postgres as the back end database with Cake 2.2 and 2.3 and it works fine here. YMMV with other db engines though I suspect.
Trasactions are to be enhanced in futures versions of CakePHP, as you can see in this CakePHP Lighthouse ticket.
There are two possible solutions proposed there, and I am showing you a third one. You could create a custom method to save it, and manually commit the transactions:
public function saveAndUpdate($data) {
$ds = $this->getDataSource();
$ds->begin();
if ($this->save($data)) {
foreach(Array('Model1', 'Model2') as $model) {
if (!ClassRegistry::init($model)->update()) {
$db->rollback();
return false;
}
}
return $db->commit() !== false;
}
return false;
}
I wrote this code to illustrate how I though about your problem, although I didn't test.
More useful links:
Transactions at CakePHP Book
About CakePHP Behaviors
How to create Behaviors
I used commit within my if statements and rollback in my else statements. Since I was using two different models from within a controller, I created two different datasources
$transactiondatasource = $this->Transaction->getDataSource();
$creditcarddatasource = $this->Creditcard->getDataSource();
$transactiondatasource->begin();
$creditcarddatasource->begin();
if (CONDITION){
$creditcarddatasource->commit();
$transactiondatasource->commit();
CakeSession::delete('Cart');
} else {
$this->Session->setFlash(__('MESSAGE'));
$creditcarddatasource->rollback();
$transactiondatasource->rollback();
}

Magento - customer_save_after always fired twice

I am using the customer_save_after event in magento, and all is working fine apart from 1 annoying thing - it is always fired twice.
There are no other modules rewriting this and I can find no other reason for this happening. When I look through all of the events getting fired at this time and this event is definately getting fired twice.
Anyone explain this?
I am writing a web service that hooks into this and its turning out to be quite inefficient to duplicate things.
I've noticed this double-save behaviour too. The way to prevent issue with your observer is to set a flag in the request that can be checked e.g.
if(Mage::registry('customer_save_observer_executed')){
return $this; //this method has already been executed once in this request (see comment below)
}
...execute arbitrary code here....
/* Customer Addresses seem to call the before_save event twice,
* so we need to set a variable so we only process it once, otherwise we get duplicates
*/
Mage::register('customer_save_observer_executed',true);
I ran into this as well and did a stack trace in the observer for each method, and can tell you at least ONE reason why it fires twice (there may be others):
When a new user creates an account, createPostAction() runs when the form is submitted. This action does a save() on the customer.
THEN, after the customer has been created, setCustomerAsLoggedIn() is called by createPostAction(). This in turn calls setCustomer(), which has this little bit of code:
if ((!$customer->isConfirmationRequired()) && $customer->getConfirmation()) {
$customer->setConfirmation(null)->save(); // here is the second save
$customer->setIsJustConfirmed(true);
}
Those are the two save()s which dispatch the save event. I only know this for sure for account creation in Magento 1.5. I doubt if it gets fired twice when creating users in the Admin area, or when a user edit's their information... but I don't know for sure.
I hope this helps!
Be careful with Jonathans solution, 'customer_save_observer_executed' stays in the session, so event will not be fired again in the browser session. So it's generally a bad idea, because it will not allow to register two or more customers in a row(actually, it will, but events will not be fired)
I suggest the following solution:
public function customerRegister(Varien_Event_Observer $observer)
{
$customer = $observer->getEvent()->getCustomer();
if (!$customer->getId())
return $this;
if(Mage::registry('customer_save_observer_executed_'.$customer->getId()))
return $this;
//your code goes here
Mage::register('customer_save_observer_executed_'.$customer->getId(),true);
}
I used a static var:
private static $_handleCustomerFirstSearchCounter = 1;
public function Savest($observer) {
if (self::$_handleCustomerFirstSearchCounter > 1) {
return $this;
}
$customerData = Mage::getSingleton('customer/session')->getCustomer();
$model = Mage::getModel('customerst/customerst')
->setQueryText(Mage::app()->getRequest()->getParam('q'))
->setCustomerId($customerData->getId())
->setCustomerName($customerData->getName())
->save();
self::$_handleCustomerFirstSearchCounter++;
}
The difference between these 2 events is one of them can't get customer info, while the other can. So the solution is
public function email_CustomerRegister(Varien_Event_Observer $observer){
$customer = Mage::getSingleton('customer/session')->getCustomer();
$customer_email = $customer->getEmail();
if(empty($customer_email)){
return;
}
// do something
}

Categories