• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Session serialized data... kinda? Dont understand the format

#11
sess_regenerate() is called in the constructor of the session library. So, any class you use in that method needs to be loaded before the session library (or loaded in that method). At the very least, both uses of Accounts_model should be wrapped in a class_exists() call, especially if you're not going to load it in (or before) MY_Session.
Reply

#12
(08-24-2015, 08:46 AM)mwhitney Wrote: sess_regenerate() is called in the constructor of the session library. So, any class you use in that method needs to be loaded before the session library (or loaded in that method). At the very least, both uses of Accounts_model should be wrapped in a class_exists() call, especially if you're not going to load it in (or before) MY_Session.

See thats what I thought, bit I tried it:
PHP Code:
<?php
defined
('BASEPATH') OR exit('No direct script access allowed');

class 
MY_Session_database_driver extends CI_Session_database_driver {

    public function 
write($session_id$session_data)
    {
        
$CI =& get_instance();

        if( ! 
in_array('Accounts_model'get_declared_classes()))
 
               {
 
                   $CI->load->model('Accounts_model');
 
               }

        
// If theres an account ID set in the session data...
        
if (isset($_SESSION['account_id']))
        {
            
// ... Associate the account ID to the session ID
            
Accounts_model::assoc_session($_SESSION['account_id'], $session_id);
         }    

        return 
parent::write($session_id$session_data);
    }


The error generated is: 

Quote:Severity: Error --> Call to a member function userdata() on a non-object /Users/..../Accounts_model.php 66

Im assuming this is because the Session library is loading the Accounts_model model, which is also using the Session library, if I put the same code in the Accounts_model to load the session library, the same error persists.
Heres the relevant code from the Accounts_model, the constructor and assoc_session:
PHP Code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class 
Accounts_model extends CI_Model
{
 
   private static $CI;
 
   
    private 
static $db;

 
   private static $account;
 
   
    
// ------------------------------------------------------------------------

 
   public function __construct()
 
   {
 
       parent::__construct();

 
       self::$CI       =& get_instance();
 
       self::$db       self::$CI->db;

 
       if( ! in_array('session'get_declared_classes()))
 
       {
 
           self::$CI->load->library'session' );
 
       }

 
       if( ! $account_id self::$CI->session->userdata'account_id' ) )
 
       {
 
           self::$account NULL;
 
       }
 
       else
        
{
 
           self::$account $this->get_account_details$account_id );
 
       }
 
   }

 
   public function assoc_session($account_id NULL$session_id NULL$old_session_id NULL)
 
   {
 
       if( ! $account_id self::$CI->account_lib->get_id($account_id))
 
       {
 
           log_message('error'sprintf(self::$CI->lang->line('log_function_failed_getting_account_id'), __METHOD__));

 
           return FALSE;
 
       }

 
       if( ! $session_id)
 
       {
 
           if( ! $session_id self::$CI->session->session_id)
 
           {
 
               Logger_lib::error"Cant associate account to session, no session ID found" );

 
               return FALSE;
 
           }
 
       }

 
       // Update the session table with the account ID
 
       self::$db
            
->where'id'$session_id )
 
           ->update('ci_sessions', ['account_id' => $account_id]);

 
       // And update the accounts table to set the current session ID for the account
 
       self::$db
            
->where'account_id'$account_id )
 
           ->update('accounts', ['ci_session_id' => $session_id]);

 
       // If this is from the session being re-generated, then change all the activity over
 
       if($old_session_id)
 
       {
 
           self::$db
                
->where('session_id'$old_session_id)
 
               ->update('account_activity', ['session_id' => $session_id]);
 
       }

 
       return TRUE;
 
   }


Thank you for any help! Im still learning a lot of CI
Reply

#13
The name of the class declared in the session library is CI_Session, so you can either remove the check for 'session' that wraps your call to load->library('session') or change it to check for 'CI_Session'. Also, you may want to use class_exists('whatever', false) instead of in_array('whatever', get_declared_classes()), because class_exists() is case-insensitive, but in_array is not (and get_declared_classes() is going to return the class names using the same case used when they were declared).

I'm not sure how well PHP can handle circular dependencies like this, but it's clearly something that should be avoided, anyway. I would probably avoid using the session in the model's constructor, but you'll probably have to make some significant changes in the model to ensure it's initialized properly.

On a somewhat unrelated note, CI_Model allows you to reference the CI_Controller singleton (returned by get_instance()) through $this, so, unless you're really fond of the static syntax, there's no need for the self::$CI =& get_instance() (you can also use $this->load->library(), $this->session->userdata(), $this->session->session_id, etc.) and what you've defined as self::$db is already available in the model as $this->db.
Reply

#14
(08-25-2015, 08:39 AM)mwhitney Wrote: The name of the class declared in the session library is CI_Session, so you can either remove the check for 'session' that wraps your call to load->library('session') or change it to check for 'CI_Session'. Also, you may want to use class_exists('whatever', false) instead of in_array('whatever', get_declared_classes()), because class_exists() is case-insensitive, but in_array is not (and get_declared_classes() is going to return the class names using the same case used when they were declared).

I'm not sure how well PHP can handle circular dependencies like this, but it's clearly something that should be avoided, anyway. I would probably avoid using the session in the model's constructor, but you'll probably have to make some significant changes in the model to ensure it's initialized properly.

On a somewhat unrelated note, CI_Model allows you to reference the CI_Controller singleton (returned by get_instance()) through $this, so, unless you're really fond of the static syntax, there's no need for the self::$CI =& get_instance() (you can also use $this->load->library(), $this->session->userdata(), $this->session->session_id, etc.) and what you've defined as self::$db is already available in the model as $this->db.

I probably should have updated this earlier, when I actually took out all the conditional statements and just loaded the models/libraries, and the results are the exact same.

Error:
Quote:ERROR - 2015-08-25 10:10:37 --> Severity: Error --> Call to a member function userdata() on a non-object /Users/...htdocs/application/models/Accounts_model.php 66

PHP Code:
public function create( array $settings$partition_id NULL )
{
 
   self::$CI->load->library'session' );

 
   $partition_id $this->partition_lib->get_id$partition_id );

 
   $partition $this->Partition_model->get_partitions$partition_id );

 
   $creator $this->session->userdata'account_id' ); // LINE 66


MY_Session:
PHP Code:
class MY_Session extends CI_Session
{
 
   private static $db;

 
   private static $CI;

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

 
   function __construct()
 
   {
 
       parent::__construct();

 
       self::$CI =& get_instance();
 
       self::$db self::$CI->db;

 
       //if( ! in_array('Accounts_model', get_declared_classes())){
 
           self::$CI->load->model('Accounts_model');
 
       //}
 
   }

 
   public function sess_regenerate($destroy FALSE)
 
   {
 
       $current_session session_id();

 
       parent::sess_regenerate($destroy);

 
       $new_session session_id();

 
       // Re-associate all old account activity to the new session
 
       Accounts_model::$instance->assoc_session(NULL$current_session$new_session);
 
       //self::$db->where('session_id', $current_session)->update('account_activity', ['session_id' => $new_session]);
 
   }

And MY_Session_database_driver
PHP Code:
class MY_Session_database_driver extends CI_Session_database_driver {

    public function 
write($session_id$session_data)
    {
        
$CI =& get_instance();

        
//if( ! in_array('Accounts_model', get_declared_classes())){
 
           $CI->load->model('Accounts_model');
 
       //}
 
       
        
// If theres an account ID set in the session data...
        
if (isset($_SESSION['account_id']))
        {
            
// ... Associate the account ID to the session ID
            
Accounts_model::$instance->assoc_session($_SESSION['account_id'], $session_id);
         }    

        return 
parent::write($session_id$session_data);
    }


Is it a problem that the session library is using the Accounts_model, while the Accounts_model is also relying on the session library? (Chicken or the egg...)

P.S. Thanks for the input on class_exists('whatever', false), very helpful
Reply

#15
If I cant do something like this, what do you think the best way to handle it would be? Setup a public static variable that shows if a session was re-generated, then when the accounts_model finally loads, check that variable? And if so, execute the association?
Reply

#16
I kinda just did what I thought of in my last post... heres the summarized code:
PHP Code:
class MY_Session extends CI_Session
{
 
   private static $db;

 
   private static $CI;

 
   public static $sess_info = array(
 
       'old' => NULL,
 
       'new' => NULL
    
);

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

 
   public function sess_regenerate($destroy FALSE)
 
   {
 
       self::$sess_info['old'] = session_id();

 
       parent::sess_regenerate($destroy);

 
       self::$sess_info['new'] = session_id();
 
   }

Then add_account_activity() is whats executed to log the information, and that checks for those static vars:
PHP Code:
public function add_account_activity()
{
 
   if( ! is_nullMY_Session::$sess_info['old'] ) AND ! is_nullMY_Session::$sess_info['new'] ) )
 
   {
 
       // Associate the old session activity with the new one
 
   }

 
   // Log something

Reply

#17
(08-25-2015, 10:15 AM)jLinux Wrote:
(08-25-2015, 08:39 AM)mwhitney Wrote: I'm not sure how well PHP can handle circular dependencies like this, but it's clearly something that should be avoided, anyway. I would probably avoid using the session in the model's constructor, but you'll probably have to make some significant changes in the model to ensure it's initialized properly.

Is it a problem that the session library is using the Accounts_model, while the Accounts_model is also relying on the session library? (Chicken or the egg...)

That's what I was talking about when I said "circular dependencies", especially with the requirements being in the constructors, which I'm pretty sure have to complete before the loader can return.

Anyway, it looks like you've found something that will work for you for the moment.
Reply

#18
(08-25-2015, 12:56 PM)mwhitney Wrote:
(08-25-2015, 10:15 AM)jLinux Wrote:
(08-25-2015, 08:39 AM)mwhitney Wrote: I'm not sure how well PHP can handle circular dependencies like this, but it's clearly something that should be avoided, anyway. I would probably avoid using the session in the model's constructor, but you'll probably have to make some significant changes in the model to ensure it's initialized properly.

Is it a problem that the session library is using the Accounts_model, while the Accounts_model is also relying on the session library? (Chicken or the egg...)

That's what I was talking about when I said "circular dependencies", especially with the requirements being in the constructors, which I'm pretty sure have to complete before the loader can return.

Anyway, it looks like you've found something that will work for you for the moment.

I think I found what the underlying issue is, when CI executes the sess_regenerate(), it creates a new session ID, but it doesnt yet write that session ID to the ci_sessions table. I have CI set to delete the old sessions when it regenerates the sessions, so when it deletes the old one from the ci_sessions table, I really need it to create the new row as well

Is there a way for me to force that to happen earlier? Should I call the write() or some other method to do that? 
Reply

#19
The only thing I can think of, which I really am kinda opposed to doing, (And I know Mr Narff would probably crucify me, lol), is in MY_Session:Confusedess_regenerate(), create a record in the ci_sessions table with the necessary data, and have the MY_Session_database_driver::write() first check to see if the record exists (Instead of just inserting it), if it does, then just update it, if not, then do insert it...

Is there a better way to have the record in ci_sessions created when the session is regenerated? Preferably right when its destroying the old session?

Edit: Or I could insert the row in the MY_Session:Confusedess_regenerate() method, and set the CI_Session_database_driver->_row_exists to TRUE... still not sure i like that approach though

Edit 2: That wont work, because this segment of code in the CI_Session_database_driver::write() at the top will set it back:
PHP Code:
if ($session_id !== $this->_session_id)
        {
            if ( ! 
$this->_release_lock() OR ! $this->_get_lock($session_id))
            {
                return 
FALSE;
            }

            
$this->_row_exists FALSE;
            
$this->_session_id $session_id;
        }
        elseif (
$this->_lock === FALSE)
        {
            return 
FALSE;
        } 
Reply

#20
It sounds like you've gotten to a point where things are more complicated than they should be.

If I understand this correctly, you want to know which sessions are open for a given account_id. When the session library calls sess_regenerate(), it is essentially saying that the old session is closed and a new session has been opened using the existing data. So, when sess_regenerate() is called, you basically just need to notify your Accounts_model of this situation, which might be something like this:

PHP Code:
public function sess_regenerate($destroy FALSE)
{
    
$oldId session_id();
    
parent::sess_regenerate($destroy);
    
$newId session_id();

    if (! 
class_exists('accounts_model'false)) {
        
$this->load->model('accounts_model');
    }
    
$this->accounts_model->sessionRegenerate($oldId$newId);


How you handle this in your Accounts_model depends on how you're using the relations in the database, but you shouldn't need to touch the session. If you need to keep the entry with the $oldId value until a write occurs, then I would recommend adding a nullable column to your table for the $newId value. Then, in Accounts_model->sessionRegenerate(), you would update the entry with $oldId to add the $newId value to the new column, and insert a new entry with the $newId in the normal session column. Then, when the write occurs, you would notify the Accounts_model and it could go back through and delete the row with the $newId in the new column. If you have the potential for regenerating the session multiple times before writing the session, you would simply have to go back through the chain, finding the rows with each of the previous session IDs and deleting all of them.

If the Accounts_model doesn't access the session in its constructor, this won't be a problem. Further, the only thing the model should really do in terms of accessing the session is store/retrieve the account_id in the session. Any time you access the account_id in the model, it should be through get/set methods which access the session only when the value hasn't been retrieved (on get) or when it changes (on set). The session ID shouldn't be needed in the Accounts_model except for those relationships, so the session ID can be passed to the model from the session library or retrieved from the reference table.
Reply


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


  Theme © 2014 iAndrew  
Powered By MyBB, © 2002-2020 MyBB Group.