Prevent infinite loop on update function that updates itself? - php

I'm using Drupal 7 with Commerce, although that doesn't really matter I think.
There's a hook (function) hook_entity_update() that is called when an entity (my shopping cart) is updated.
I want to recalculate my shipping in this hook, the issue is that it saves the entity again once the shipping is recalculated and so it calls the hook again, getting me stuck in an infinite loop.
What would be the best way to solve something like this?
I was thinking of using a simple $_SESSION variable, like $_SESSION['updating'] and set it to TRUE at the start of the hook, then set it to FALSE again at the end of the hook and prevent the function from running with a return at the start if the variable is set to true? I'm not sure if this is a good solution?
Using a session variable because it's a form and the function seems to be called multiple times when submitting.
Not sure if a regular variable would work?
One issue I'm already running into is that the variable is somehow not reset at the end of the function and so the function is never called again ...

Using the session variable seems to work.
Stupid function is somehow called like 7 times when clicking one button ...
Added this at the start of the function:
// Check if entity is a commerce_order
if ($type != 'commerce_order') {
return;
}
// Only apply if order is still in cart
if ($entity->status != 'cart') {
return;
}
if (isset($_SESSION['bab_checkout']['updating']) &&
$_SESSION['bab_checkout']['updating'] === true) {
return;
}
$_SESSION['bab_checkout']['updating'] = true;
And then this at the end of the function:
// We're done updating
$_SESSION['bab_checkout']['updating'] = false;
Still wonder if there is no better solution?

Related

race conditions with php sessions and ajax request

I have a shopping cart I built myself that uses session variables to maintain the state of cart across requests. I have an increment and decrement button that allows users to increase or decrease the quantity of a product in their cart. this happens via an Ajax request. The cart class operates by restoring the cart out of the session when constructed, and saving the cart back to the session when destructed.
<?php
class Cart {
/**
* constructor
*/
public function __construct(){
//restore cart
$this->restore();
}
/**
* destructor
*/
public function __destruct(){
//save cart
$this->save();
}
/**
* restore
*/
public function restore(){
//retrieve session info
if(Session::has('cart')){
//get cart
$session = Session::get('cart');
//assign session info
$this->data = ($session['data']);
$this->rates = $session['rates'];
$this->lines = $session['lines'];
}
}
/**
* save
*/
public function save(){
Session::put('cart', $this->forSession());
}
the problem I'm running into is a race condition with multiple Ajax requests. the user can hit the button many times, sending multiple Ajax requests. each request is therefore pulling the current state of the session, doing the operation, and then saving it. the problem is the previous transaction is not necessarily completed and saved when it restores the cart. my first fix was to make any subsequent Ajax requests cancel the previous one, to both cut down on unnecessary (immediately overwritten) requests, and also to help avoid this race condition. while it seemed to help, it still acts quirky. So my next thought was to attack it at the source, namely the cart class itself. my idea was to implement some type of 'locking' to prevent the cart from being accessed until a previous operation was completed. The idea looked something like this.
<?php
/**
* is cart locked
*/
public function isCartLocked(){
if(Session::get('cartLock') === 1){
sleep(1);
$this->isCartLocked();
}
}
public function restore(){
Session::put('cartLock', 0);
//check if cart is locked
$this->isCartLocked();
//lock cart
Session::put('cartLock', 1);
...
}
public function save(){
//unlock the cart
Session::put('cartLock', 0);
...
}
now the first question is, SHOULD I be doing something like this, with the locking? And then, if so, is this a decent way to handle it?
After my first attempt at it, the problem I seem to be running into is the destructor is not necessarily called all the time, which is causing my cart to stay locked, and eventually causing a timeout error.
Thanks for any help!
I'd think that you would actually want to debounce the function call to the AJAX request so that you're not concerning yourself with locking/unlocking or cancelling previous requests - just make one once you're relatively sure they're done adjusting the quantity.
I would recommend this jQuery plugin for throttling or debouncing JavaScript function calls (though jQuery actually isn't required to use this, it's just accessible through the jQuery namespace if available), assuming that's how you're making your AJAX request.
I would have to say that if you are running into race conditions then you have some issues in regards to your application design, and these sorts of issues may haunt your application for a while since concurrent accesses of the session will be smashing your session store relatively frequently
The easiest way to do something like this in my opinion would be with a database table and instead of updating a field one would be adding a row for each user event ( so a increment action would insert , , , 1. And a decrement would insert a row of -1 )
This way you can simply do a sum operation to tally your counts, and when the users checkout process is complete you can clear up the entire order in the cart table .

PHP - Run function after while loop ends

This may not be the best solution to my problem, but I'm interested to know anyway.
Is it possible to defer a function or piece of code till after a while loop ends? I assumed this happened anyway if it was placed below, but that doesn't seem to be the case.
What I'm trying to accomplish is inside the while loop some session data is being saved (if it does not exist, which is the condition). When the loop finally ends, since the session data is set, the code below which relies on the session data can be executed.
This is in a CodeIgniter 3.0 system, so it is using the session driver:
// Uses a helper function session_isset (see below) to check if the session data is set, and if at least one of the supplied data variables is null then try to set it
while(!session_isset(array('instance_id','instance_prefix'))) {
$this->session->set_userdata('instance_id', $instance->instance_id);
$this->session->set_userdata('instance_prefix', $instance->instance_prefix);
}
// The get_managers() function, which ultimately calls a model function, relies on the set session data to get a table prefix which is prepended to all relevant database queries
// This should ideally only be allowed to run once the session data has been confirmed as set
if($manager = $this->manager->get_managers(array($manager_id))[0]) {
$manager->instance_id = $instance->instance_id;
$manager->instance_prefix = $instance->instance_prefix;
echo json_encode($manager);
}
function session_isset($data) {
$ci =& get_instance();
foreach ($data as $value) {
if(is_null($ci->session->userdata($value))) return FALSE;
}
return TRUE;
}
So just to reiterate, is there a way to defer code until a while loop has ended, similar to a callback?
Also, is there perhaps a better way to ensure session data is set before running functions that depend on it?

Get ID of the last product added to the cart in magento

I am trying to get the ID of the product that was most recently added to a user’s cart. A quick google search revealed this function
Mage::getSingleton('checkout/session')->getLastAddedProductId(true);
which is also used in Mage_Checkout_Block_Cart_Crosssell. When I try calling the function in my own controller however, it returns nothing.
I have tried to instantiate a core session via
Mage::getSingleton('core/session', array('name'=>'frontend'))
however this approach does not seem to work. I also tried to create the Crossell block and making the protected method that wraps around getLastAddedProductId function public however that returns null just like it does when I try calling it on its own.
Is there something I have to call or instantiate in order to use this function? Here’s my source listing for reference.
class Mymodule_Addcartaftermath_ItemaddedController extends Mage_Core_Controller_Front_Action {
public function generatemessageAction() {
$parameters = $this->getRequest()->getParams();
if (isset($parameters['ajax_call'])) {
$latest_item_id = Mage::getSingleton('checkout/session')->getLastAddedProductId(true);
$response = array('response' => $latest_item_id);
echo json_encode($response);
} else {
$this->_redirect('/');
}
}
}
I tried poking through the source code, particularly the checkout/model/session.php file in the core and I cannot seem to find the definition of the function. I also looked at it’s parent’s class definition but could not find it there either.
If this method is not available to me is there another way of retrieving the most recent item added? I know that the items are added sequentially and I could perhaps just get the last item of the list of items from the cart however this would not work in the case where the user adds the same item to the cart essentially increasing the quantity rather than actual item itself (e.g. the user adds a laptop the cart when there already is one)
The call to
Mage::getSingleton('checkout/session')->getLastAddedProductId(true);
Is actually clearing the session variable after it is read. Magento uses magic methods extensively. In this case you are using the __call magic method which in turn uses the getData() method. In Mage_Core_Model_Session_Abstract_Varien you will see that they override the default behaviour of getData() to expect the second parameter to be a boolean (The first parameter to getData is the key name for the value you are looking for). That boolean is a flag telling the session to clear the variable after reading.
You could always listen for the checkout_cart_product_add_after event and add the item to your own variable in the session. That event is actually fired on the line before setLastAddedProductId() is called.
try to grep the variable you are looking for. As they are coming from magic methods then its hard to find the exact function you are after so it's easier to see the places where data gets set than where it is used
grep '>setLastAddedProductId' app/code -rsn
to see where the product id gets set to that session variable
app/code/core/Mage/Checkout/Model/Cart.php:255: $this->getCheckoutSession()->setLastAddedProductId($product->getId());
and then you can ask this variable (if it is set else empty())
Mage::getSingleton('checkout/session')->getLastAddedProductId();
and you can see all the things that are in checkout/session and verify if the data is there.
var_dump(Mage::getSingleton('checkout/session'));
Haven't a clue why it works this way but it works for me in Magento 1.6...
<?php
require_once ( "app/Mage.php" );
umask(0);
Mage::app("default");
Mage::getSingleton('core/session', array('name'=>'frontend'));
$session = Mage::getSingleton('checkout/session');
$lastadded = $session->getData("last_added_product_id");
print_r($lastadded);
Apparently you have to instantiate the core/session and then the checkout/session. I've tried it every other way but this is the only way I've found it to work. Perhaps someone can explain why it works this way. Hope this helps you out!

Using the $_COOKIE var to keep important data

I'm creating a wordpress plugin. All the functions I'm writing are 'hooked' into certain wordpress events. This means I have a hard time creating variables that I need to use in several functions.
For example:
There's two functions that are hooked in somewhere:
Display_if_facebook_connected() {
if (Check_facebook_connected()) { return 'Yes, connected!' }
return '';
}
Display_if_facebook_connected() {
if (!Check_facebook_connected()) { return 'No, not connected!' }
return '';
}
And they both run a very heavy function:
Check_facebook_connected() { // some heavy facebook connect stuff, return bool }
I'm basically trying to avoid having the heavy function run twice, since it will have the same result.
In this case, would it be safe to do $_COOKIE['check_facebook_connected'] = true; and then read that variable in the Display_if_facebook_connected()?
By safe I mean that the user can't see or change the value. Since the cookie is never actually set, I think/hope it just disappears at the end of the php code.
I wouldn't be surprised if there is some better way, or better var, to do this with, but with my limited understanding of php I can't think of any.
UPDATE:
About sessions: I don't need the values to persist over multiple pages, just one page load. Since Wordpress doesn't use sessions I see no reason to change it.
I experimented a bit and the problem persists:
All of the following code is in the main file of my wordpress plugin. The way I understand it, the plugin file is 'included' at every request, so all code is run everytime I refresh my testpost.
Firstly I create the variable:
$myplugin_connected = false;
Then I hook my function in the right place:
add_shortcode( 'myplugin_notconnected', 'myplugin_notconnected_func' );
This basically hooks the myplugin_notconnected_func() function into the [myplugin_notconnected] shortcode. (A shortcode is text in a wordpress post, some id between [ ]-brackets. Wordpress loads the code associated with the shortcode whenever it appears.)
Here's the myplugin_notconnected_func():
function myplugin_notconnected_func( $atts, $content = null ) {
echo '<p>connected: ' . var_export($myplugin_connected, true) . '</p>';
return '$contents';
}
And here's the result:
connected: NULL
This is why I was trying to use $_COOKIE variables because at least they persist over the whole php instance. I apologize for lack of coherence, I'm learning as I go and I definitely appreciate the help!
Display_if_facebook_connected() {
$result = Check_facebook_connected();
if (!$result) { return 'No, unconnected!' } else { return 'Yes, connected!' }
}
$connected = Display_if_facebook_connected();
Scope
Referring to the updated part of your question:
Defining
$myplugin_connected = false;
and getting NULL as result on a subsequent
var_export($myplugin_connected, true)
could mean, that you either defined $myplugin_connected outside global scope (e.g. in a function instead of main), or you have defined in global scope, but have some unset($myplugin_connected) somewhere before the var_export. In both cases the return value of var_export would be NULL.
In your case I believe the former is more probably. You could use:
$GLOBALS['myplugin_connected'] = false;
and
var_export($GLOBALS['myplugin_connected'], true)
to have the connection state (which already has been determined once by your "heavy" function before) available in your shortcode handler.
Cookie
To answer your origin question:
In this case, would it be safe to do
$_COOKIE['check_facebook_connected'] = true; and then read that
variable in the Display_if_facebook_connected()?
Well, $_COOKIE is a server-side superglobal, so yes, as long as you never actually send/set that cookie on response, the user wouldn't see, nor could change it.
Personally, using $_COOKIE to save a state which is only valid for a single page load, feels just plain wrong to me.
I'd recommend to use at least $GLOBALS over $_COOKIE - or maybe even better use a static variable instead of a superglobal in this case - e.g. something like this:
function isConnected() {
static $bConnected = null;
if ($bConnected === null)
$bConnected = Check_facebook_connected();
return $bConnected;
}
But that's always in the eye of the beholder^^
session_start();
Check_facebook_connected()
{
if(isset($_SESSION["is_facebook_connected"])) return ($_SESSION["is_facebook_connected"] === true);
// if we get here we haven't checked the facebook connection status, so do it
...
}

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