Welcome Guest, Not a member yet? Register   Sign In
[BUG] [SOLVED] fixing the duplication of the DB updates in SESSION library
#1

[eluser]DeaD SouL[/eluser]
Hi there,

I posted a topic about this bug in: [bug] set_flashdata() duplicates the sql queries -> http://ellislab.com/forums/viewthread/189739/

And since there was no help. I checked it myself, and tried to fix it..

and it WORKED Big Grin

So, I just wanted to share it with you guys Smile

copy the following code, and save it in application/libraries/MY_Session.php

This is OLD, go to Reply #1

Session doesn't work with redirect() ? see: Reply #9



Code:
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Fixing the duplication of updates
*
* Please note, that the $this->db->query_count won't count the session db
* update query. It is because of the way of the __destruct() method.
*
* If you want to show the update query in profiler, first change the name of
* the __destruct() to anything else to avoid the autocall from php, then use
* the hooks and tweak it to do the job automatically. Otherwise, you can call
* it manually in the end of your each controller's method (before showing the
* views).
*
* @package        CodeIgniter
* @author        Mubarak Alrashidi (DeaDSouL)
* @copyright    Copyright (c) 2011, Mubarak Alrashidi.
* @license        http://ellislab.com/codeigniter/user-guide/license.html
* @link        http://codeigniter.com
* @since        Version 2.0.2
* @filesource
*/

// -------------------------------------------------------------------------

class MY_Session extends CI_Session {

    private
        $old_userdata = array();
    
    // -------------------------------------------------------------------------
    
    public function __construct()
    {
        parent::__construct();
        // getting a copy of the userdata
        $this->old_userdata = $this->userdata;
    }
    
    // -------------------------------------------------------------------------
    
    /**
     * Write the session data
     *
     * @access    public
     * @return    void
     */
    public function __destruct()
    {
        // Are we saving custom data to the DB?  If not, all we do is update the cookie
        if ($this->sess_use_database === FALSE)
        {
            $this->_set_cookie();
            return;
        }

        // set the custom userdata, the session data we will set in a second
        $custom_userdata = $this->userdata;
        $cookie_userdata = array();

        // Before continuing, we need to determine if there is any custom data to deal with.
        // Let's determine this by removing the default indexes to see if there's anything left in the array
        // and set the session data while we're at it
        foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
        {
            unset($custom_userdata[$val]);
            $cookie_userdata[$val] = $this->userdata[$val];
            // remove $val from $this->old_userdata too
            unset($this->old_userdata[$val]);
        }
        
        // counting the custom_userdata & old_userdata
        $c_custom_userdata    = count( $custom_userdata );
        $c_old_userdata        = count( $this->old_userdata );

        // comparing the custom_userdata with the old_userdata
        $need_update = FALSE; // default value
        if( $c_old_userdata <> $c_custom_userdata )
        {
            $need_update = TRUE;
        }

        if( $need_update === FALSE
            AND $c_custom_userdata > 0 )
        {
            foreach( $custom_userdata as $k => $v )
            {
                if( ! isset( $this->old_userdata[$k] )
                    OR $this->old_userdata[$k] <> $v )
                {
                    $need_update = TRUE;
                    break;
                }
            }
        }

        // Did we find any custom data?  If not, we turn the empty array into a string
        // since there's no reason to serialize and store an empty array in the DB
        if ( $c_custom_userdata === 0 )
        {
            $custom_userdata = '';
        }
        else
        {
            // Serialize the custom data array so we can store it
            $custom_userdata = $this->_serialize($custom_userdata);
        }

        // Do we need to update the database
        if( $need_update === TRUE )
        {
            // Run the update query
            $this->CI->db->where('session_id', $this->userdata['session_id']);
            $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
        }

        // Write the cookie.  Notice that we manually pass the cookie data array to the
        // _set_cookie() function. Normally that function will store $this->userdata, but
        // in this case that array contains custom data, which we do not want in the cookie.
        $this->_set_cookie($cookie_userdata);
    }

    // -------------------------------------------------------------------------

    /**
     * ignore $this->session->sess_write() if its being called
     *
     * @access    public
     * @return    void
     */
    public function sess_write()
    {
        return;
    }

}
// END MY_Session Class

/* End of file MY_Session.php */
/* Location: ./application/libraries/MY_Session.php */

By the way, I tested it on CI 2.0.2
I hope anyone tries it, tell us whether it worked for him or not, and what was his CI version

Regards !
#2

[eluser]DeaD SouL[/eluser]
Hi again,

I strongly recommend to forget about the first code that was mentioned in my previous post, as it will conflict with MY_ libs & MY_ cores specially with system/core/Exceptions.php if you are using the __autoload() function.

the new application/libraries/MY_Session.php

Code:
&lt;?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Fixing the duplication of updates
*
* Install:
*         1) save this code in application/libraries/MY_Session.php
*
*         2) setup the hook
*             1) update "application/config/config.php"
*                 $config['enable_hooks'] = TRUE; // FALSE;
*
*             2) update "application/config/hooks.php"
*                 $hook['post_controller'] = array(
*                    'class'    => '',
*                    'function' => 'update_session_db',
*                    'filename' => 'sess_update_fix.php',
*                    'filepath' => 'hooks',
*                    'params'   => array()
*                );
*
*             3) create "application/hooks/sess_update_fix.php"
*                 function update_session_db()
*                {
*                    $CI =& get_instance();
*                    $CI->session->sess_write( TRUE );
*                }
*
* If you don't wish to use the hooks, then you'll have to save the session
* manually like this: $this->session->sess_write(TRUE);
*
* @package        CodeIgniter
* @author        Mubarak Alrashidi (DeaDSouL)
* @copyright    Copyright (c) 2011, Mubarak Alrashidi.
* @license        http://ellislab.com/codeigniter/user-guide/license.html
* @link        http://codeigniter.com
* @since        Version 2.0.2
* @filesource
*/

// -------------------------------------------------------------------------

class MY_Session extends CI_Session {

    private
        $old_userdata = array();
    
    // -------------------------------------------------------------------------
    
    public function __construct()
    {
        parent::__construct();
        // getting a copy of the userdata
        $this->old_userdata = $this->userdata;
    }
    
    // -------------------------------------------------------------------------

    /**
     * Write the session data
     *
     * ignore $this->session->sess_write() if its being called with any
     * parameter that doesn't equal to TRUE.
     *
     * @access    public
     * @param    bool (default: FALSE)
     * @return    void
     */
    function sess_write( $real_write = FALSE )
    {
        if( $real_write !== TRUE )
        {
            return;
        }
        
        // Are we saving custom data to the DB?  If not, all we do is update the cookie
        if ($this->sess_use_database === FALSE)
        {
            $this->_set_cookie();
            return;
        }

        // set the custom userdata, the session data we will set in a second
        $custom_userdata = $this->userdata;
        $cookie_userdata = array();

        // Before continuing, we need to determine if there is any custom data to deal with.
        // Let's determine this by removing the default indexes to see if there's anything left in the array
        // and set the session data while we're at it
        foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
        {
            unset($custom_userdata[$val]);
            $cookie_userdata[$val] = $this->userdata[$val];
            // remove $val from $this->old_userdata too
            unset($this->old_userdata[$val]);
        }
        
        // counting the custom_userdata & old_userdata
        $c_custom_userdata    = count( $custom_userdata );
        $c_old_userdata        = count( $this->old_userdata );

        // comparing the custom_userdata with the old_userdata
        $need_update = FALSE; // default value
        if( $c_old_userdata <> $c_custom_userdata )
        {
            $need_update = TRUE;
        }

        if( $need_update === FALSE
            AND $c_custom_userdata > 0 )
        {
            foreach( $custom_userdata as $k => $v )
            {
                if( ! isset( $this->old_userdata[$k] )
                    OR $this->old_userdata[$k] <> $v )
                {
                    $need_update = TRUE;
                    break;
                }
            }
        }

        // Did we find any custom data?  If not, we turn the empty array into a string
        // since there's no reason to serialize and store an empty array in the DB
        if ( $c_custom_userdata === 0 )
        {
            $custom_userdata = '';
        }
        else
        {
            // Serialize the custom data array so we can store it
            $custom_userdata = $this->_serialize($custom_userdata);
        }

        // Do we need to update the database
        if( $need_update === TRUE )
        {
            // Run the update query
            $this->CI->db->where('session_id', $this->userdata['session_id']);
            $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
        }

        // Write the cookie.  Notice that we manually pass the cookie data array to the
        // _set_cookie() function. Normally that function will store $this->userdata, but
        // in this case that array contains custom data, which we do not want in the cookie.
        $this->_set_cookie($cookie_userdata);
    }

}
// END MY_Session Class

/* End of file MY_Session.php */
/* Location: ./application/libraries/MY_Session.php */


Enjoy Smile
#3

[eluser]DeaD SouL[/eluser]
If you want to use the Hooks, continue reading please:

1) update "application/config/config.php"
Code:
$config['enable_hooks'] = TRUE; // FALSE;


2) update "application/config/hooks.php"
Code:
$hook['post_controller'] = array(
    'class'    => '',
    'function' => 'update_session_db',
    'filename' => 'sess_update_fix.php',
    'filepath' => 'hooks',
    'params'   => array()
);


3) create "application/hooks/sess_update_fix.php"
Code:
function update_session_db()
{
    $CI =& get_instance();
    $CI->session->sess_write( TRUE );
}
#4

[eluser]DeaD SouL[/eluser]
The word: "Thanks" <-- would make me feel happy, as if I did something :p

And the discussing would exchange our experiences .. !
#5

[eluser]Drew J[/eluser]
Hi DeaD SouL,

Thanks for debugging and working on this! I would recommend filing a bug report over on BitBucket. You'll likely get a lot more feedback and testing that way.

Also, a little more information on what the offending code is that's causing the double database call would be very helpful. Personally (for me), I won't use code that is posted as "this solves the problem" without explaining what it's changing or what it fixed. Smile
#6

[eluser]DeaD SouL[/eluser]
Hi Drew J,

Right, I didn't mention that Smile

aw, if you call any of these methods:
Code:
$this->session->set_userdata();
$this->session->unset_userdata();
$this->session->set_flashdata();
$this->session->keep_flashdata();

The session library will update the database, because
Code:
set_userdata()
and
Code:
unset_userdata()
will call
Code:
$this->session->sess_write()
. while the
Code:
set_flashdata()
and
Code:
keep_flashdata()
will call
Code:
set_userdata()
which will call
Code:
$this->session->sess_write()

So, it means that the library will update the database every time you call any of the previous methods.

What I did: overwriting the
Code:
sess_write()
by adding a new parameter $real_write with a default value as (bool) FALSE. So, when ever its being called by the CI_Session library, it does nothing. unless that parameter is (bool) TRUE. then, it would compare the old userdata which is already in the database with the new one, and if its matched, then we don't need to update the database, otherwise, we do update it only one time.

Regards ^^
#7

[eluser]arvid.janson[/eluser]
Ok, so this is exactly what I asked for a couple of hours ago, so first off - thanks!

I did have a couple of problems though: did not get the session to save when using hooks (using the manual call DID work), and flashdata did not remove itself after reloading the controller. But, that was just my first impressions, so I will have to do some more testing. But a great start!
#8

[eluser]WanWizard[/eluser]
I find the copyright notice in this code interesting, I'm quite sure I saw this exact solution mentioned before in these forums...
#9

[eluser]DeaD SouL[/eluser]
WanWizard, I'm not that stupid to copy someone's code and claim its mine, specially if it was posted in the same website !! I'd rather just use it. Without sharing it Wink

Regards
#10

[eluser]DeaD SouL[/eluser]
If you're going to use redirect() the session won't be saved. to fix that:

Solution #1:
You'll have to call
Code:
$this->session->sess_write( TRUE );
manually before calling redirect();



Solution #2:
Overwriting the redirect() function:

Create a new helper and name it "overwrite_helper.php" (or any name)

Code:
application/helpers/overwrite_helper.php

then add this
Code:
if ( ! function_exists('redirect'))
{
    function redirect($uri = '', $method = 'location', $http_response_code = 302)
    {
        // forcing session to be saved
        $CI =& get_instance();
        $CI->session->sess_write( TRUE );
        
        // codeigniter code
        if ( ! preg_match('#^https?://#i', $uri))
        {
            $uri = site_url($uri);
        }

        switch($method)
        {
            case 'refresh'    : header("Refresh:0;url=".$uri);
                break;
            default            : header("Location: ".$uri, TRUE, $http_response_code);
                break;
        }
        exit;
    }
}

finally, you'll have to load the "overwrite_helper" before the "url_helper". in order to overwrite the original redirect() function in url_helper.php

Regards




Theme © iAndrew 2016 - Forum software by © MyBB