-
jLinux
The Linux Dude
-
Posts: 157
Threads: 35
Joined: Jun 2015
Reputation:
2
(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
-
mwhitney
Posting Freak
-
Posts: 1,101
Threads: 4
Joined: Nov 2014
Reputation:
95
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.
-
jLinux
The Linux Dude
-
Posts: 157
Threads: 35
Joined: Jun 2015
Reputation:
2
08-25-2015, 10:15 AM
(This post was last modified: 08-25-2015, 10:16 AM by jLinux.)
(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
-
jLinux
The Linux Dude
-
Posts: 157
Threads: 35
Joined: Jun 2015
Reputation:
2
08-25-2015, 10:40 AM
(This post was last modified: 08-25-2015, 10:43 AM by jLinux.)
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_null( MY_Session::$sess_info['old'] ) AND ! is_null( MY_Session::$sess_info['new'] ) ) { // Associate the old session activity with the new one }
// Log something }
-
mwhitney
Posting Freak
-
Posts: 1,101
Threads: 4
Joined: Nov 2014
Reputation:
95
(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.
-
jLinux
The Linux Dude
-
Posts: 157
Threads: 35
Joined: Jun 2015
Reputation:
2
08-25-2015, 01:38 PM
(This post was last modified: 08-25-2015, 01:43 PM by jLinux.)
(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?
-
jLinux
The Linux Dude
-
Posts: 157
Threads: 35
Joined: Jun 2015
Reputation:
2
08-25-2015, 09:54 PM
(This post was last modified: 08-25-2015, 10:13 PM by jLinux.)
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:ess_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:ess_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; }
-
mwhitney
Posting Freak
-
Posts: 1,101
Threads: 4
Joined: Nov 2014
Reputation:
95
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.
|