PHP: storing closures in session breaks session - session

Update: solved
I finally figured out my problem (I think). I'm pretty sure the issue is that closures cannot be serialized, which means they cannot be stored in the session. The next issue then is that PHP was not returning a very useful error, and was breaking in an unexpected way, instead of just telling me that I couldn't serialize a closure.
I store my session data in a mysql database. I've had that part of my application in place and working well for quite a while. Today I tried to store a closure (i.e. anonymous function) in the session and that broke my otherwise very well behaved sessions.
My session management is handled by an object, which automatically calls session_write_close() when PHP attempts to destroy the object. I did this so that because otherwise, by the time PHP tries to close the session, my database connection (a mysqli object) has already been destroyed.
I take over the session handling like this:
// set the session save handler
session_set_save_handler(
array( $this, '_open' ),
array( $this, '_close' ),
array( $this, '_read' ),
array( $this, '_write' ),
array( $this, '_destroy' ),
array( $this, '_clean' )
);
which is pretty standard. The part that handles session closing is this:
public function __destruct()
{
// this variable will only be destroyed when the script is closing
// at this point it is safe to close the session
// if we wait for php to close the session then we will
// have lost the database connection, so we do it now
session_write_close();
}
// write session data
public function _write( $sid, $data )
{
// run query to write to database
$now = NOW;
$stmt = $this->mysqli->prepare( "REPLACE INTO $this->table (sid,time,data) VALUES (?,?,?)" );
$stmt->bind_param( 'sis', $sid, $now, $data );
// execute
$success = $stmt->execute();
// close
$stmt->close();
// and return
return $success;
}
// close session store
public function _close()
{
// close the database connection
$this->mysqli->close();
return true;
}
A couple print functions reveal that normally this works just as you would think: the __destruct() function is called, which calls session_write_close(), which is immediately followed up by calls to _write() and _close(). However, the moment I store a closure to the session:
$test = function($name)
{
print "Hello $name";
};
$_SESSION['test'] = $test;
Everything breaks. __destruct() is called as before, but execution never reaches the _write() or _close() functions. Instead I get these messages:
Warning: session_write_close() [function.session-write-close]: Failed to write session data (user). Please verify that the current setting of session.save_path is correct (/var/lib/php/session) in /var/www/vhosts/ambida.com/httpdocs/includes/core/session_handler.php on line 48
Fatal error: Exception thrown without a stack frame in Unknown on line 0
Which really makes no sense. It looks like it has reverted back to the default session handler, which would of course fail because the tmp file was never opened (since my function took over opening the session). I don't understand why storing a closure in the session would cause such a revert to happen, or why this is breaking in general. Any help would be appreciated.

This can now be achieved by using the SuperClosurefrom Jeremy Lindblom. The Package can be found on his github : https://github.com/jeremeamia/super_closure

Related

Laravel Auth::User() performance

I have a question that whenever we call Auth::User() then its execute the query to fetch record or it have a saved instance?
Example 1
echo Auth::User()->name;
echo Auth::User()->email;
echo Auth::User()->phone;
Example 2
$userInfo=Auth::User();
echo $userInfo->name;
echo $userInfo->email;
echo $userInfo->phone;
Which one should be used performance wise?
Answer and example
Call to the database will be made only the first time you call Auth::user(), after that Laravel will store the user data and each call after that will get the stored instance rather then query the database again.
You can take a look at the vendor\laravel\framework\src\Illuminate\Auth\SessionGuard.php file under user() method. This is the code I copied from my current project which uses Laravel 7.x and this is the function called by Auth::user().
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
Debugbar options
Also as the comment before me pointed out, it is good to download Debugbar for Laravel https://github.com/barryvdh/laravel-debugbar. It will enable you to take a look into queries being executed, views being rendered, requests being sent, and much more.
Other option is Laravel's native solution https://laravel.com/docs/8.x/telescope. I have never personally used it and IMO the first one is simpler to use.
Notes on good practice
Although both examples will essentially do the same thing, I think it is much better to use the second example. Not because of the performance, but rather to make your code readable in future. If you define the user only one time and assign Auth::user() result to it, in future it will be more obvious what it is, and plus, you can easily change what are you assigning to the $userInfo variable (maybe you want to get user from another guard in future, etc.) without having to change it on N places throughout the code.

Possible ajax call to result in extreme load times after redirects?

I have the following senario
Page A is being loaded and fires up 3 ajax calls, they take some time because they are working on getting a lot of data, though nothing the server can't handle. Now the user is redirecting to page B before the ajax calls have finished. I know they will continue running on the background, but could this possible cause dramatic loading times? There is no sql overheat, server processor is only using around 10% of it's limits. Yet loading times can differ from 1-2 seconds all the way to 50+ seconds.
Is it possible that this is being caused by the previous, yet running ajax calls and that the browser is somehow still having a connection with these calls and awaits response before it will load the next page??
Based on your answer in the comment section i try to explain it and provide a solution for this - because i ran a ton of times in this issue - although you write that your ajax calls are not using the session library - check out if you start it anyway.
PHP writes session data to a file by default. If a request starts it starts the session. This session file is locked. What this means is that if your web page makes a ton of requests to PHP scripts, e.g. for loading content via Ajax, each request locks the session and prevents the other requests to complete.
What we did in order to prevent situations like that is the following:
in your /application/config/hooks.php
$hook['pre_controller'][] = array(
"class" => "AppSessionInterceptor",
"function" => "initialize",
"filename" => "AppSessionInterceptor.php",
"filepath" => "hooks"
);
and after that create a hook called AppSessionInterceptor
class AppSessionInterceptor
{
private $ci;
public function __construct()
{
$this->ci = &get_instance();
}
public function initialize()
{
//some doings
$this->closeSession();
}
private function closeSession()
{
$blnSessWriteClose = true;
$arrStopSessionFromWriteClose = array(
$this->ci->uri->segment(1) => array("logout","login"),
$this->ci->uri->segment(2) => array("logout","login"),
$this->ci->input->get("blnWriteSession") => array(1)
);
foreach($arrStopSessionFromWriteClose AS $key => $arrValue)
{
if (in_array($key, $arrValue))
{
$blnSessWriteClose = false;
break;
}
}
if ($blnSessWriteClose) session_write_close();
}
}
This closes the session before anything else is called.
Keep in mind that if you close a session - you are able to read the data but can't write it anymore.

fatfree sessions, different values in database and echo stmt

I have this in my beforeroute() of a controller
public function beforeroute()
{
new \DB\SQL\Session($this->db);
$mapper = new \DB\SQL\Mapper($this->db, 'users');
$auth = new \Auth($mapper, array(
'id' => 'username',
'pw' => 'password'
));
if (!$auth->login('validuser', '1234')) {
die('username or password wrong');
} else {
echo ($csrf = $this->db->exec('SELECT csrf FROM sessions')[0]['csrf']);
}
}
After I hit the page, I have different values for csrf in database and what's been echoed out on page. Why is that?
The csrf token is renewed on every request. You see different values on the page and in the database, because the value in the database was updated after your page has rendered.
To be more specific, the SQL Session handler replaces the default php session handler, and that's why the call to session_commit within the unload method https://github.com/bcosca/fatfree-core/blob/master/base.php#L1903 (is called when the framework shut down) will update your session database table with the new value.
To have a way to reuse that single csrf token for your purpose, just put it back into the session itself:
$s = new \DB\SQL\Session($f3->get('DB'));
// old value from last request
echo $f3->get('SESSION.csrf');
// remember current value for next request
$f3->set('SESSION.csrf',$s->csrf());
Maybe there`s an easier way, but I haven't figured it out yet.

How to "Refresh" the User object in Laravel?

In Laravel you can do this:
$user = Auth::user();
Problem is, if I do changes on items on that object, it will give me what was there before my changes. How do I refresh the object to get the latest values? I.e. To force it to get the latest values from the DB?
You can update the cache object like this.
Auth::setUser($user);
for Example
$user = User::find(Auth::user()->id);
$user->name = 'New Name';
$user->save();
Auth::setUser($user);
log::error(Auth::user()->name)); // Will be 'NEW Name'
[This answer is more appropriate for newer versions of Laravel (namely Laravel 5)]
On the first call of Auth::user(), it will fetch the results from the database and store it in a variable.
But on subsequent calls it will fetch the results from the variable.
This is seen from the following code in the framemwork:
public function user()
{
...
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
...
}
Now if we make changes on the model, the changes will automatically be reflected on the object. It will NOT contain the old values. Therefore there is usually no need to re-fetch the data from the database.
However, there are certain rare circumstances where re-fetching the data from the database would be useful (e.g. making sure the database applies it's default values, or if changes have been made to the model by another request). To do this run the fresh() method like so:
Auth::user()->fresh()
Laravel does do that for you, HOWEVER, you will not see that update reflected in Auth::user() during that same request. From /Illuminate/Auth/Guard.php (located just above the code that Antonio mentions in his answer):
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
So if you were trying to change the users name from 'Old Name' to 'New Name':
$user = User::find(Auth::user()->id);
$user->name = 'New Name';
$user->save();
And later in the same request you try getting the name by checking Auth::user()->name, its going to give you 'Old Name'
log::error(Auth::user()->name)); // Will be 'Old Name'
A little late to the party, but this worked for me:
Auth::user()->update(array('name' => 'NewName'));
Laravel already does that for you. Every time you do Auth::user(), Laravel does
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
It nulls the current user and if it is logged, retrieve it again using the logged id stored in the session.
If it's not working as it should, you have something else in your code, which we are not seeing here, caching that user for you.

Session data gone after redirect in CI

i need your help.
I used the session to record the user selected business type in CI. For example,$this->ci->session->set_userdata('biztype','food'). When user login,it works ok. However, once the user logout, session will be destroyed in the function logout().So i set the userdata again in the function logout().You can view the code below:
function logout()
{
$biztype = $this->ci->session->userdata('biztype');
$this->delete_autologin();
$this->ci->session->set_userdata(array('user_id' => '', 'username' => '', 'status' => ''));
$this->ci->session->sess_destroy();
$this->ci->session->set_userdata('biztype',$biztype);
//echo $this->ci->session->userdata('biztype'); //here, i can get biztype that i want
}
However,when i logout and redirect to homepage, i cant get the userdata('biztype') and my session_id have changed.
Thanks for the help.
This is straight from CodeIgniter User Guide:
Destroying a Session
To clear the current session:
$this->session->sess_destroy();
Note: This function should be the last one called, and even flash
variables will no longer be available. If you only want some items
destroyed and not all, use unset_userdata().
So no, you cannot destroy a session then add user_data to it, you need to reload / redirect then once the NEW session is established add data.
Try using cookies for peristance, or use the mentioned unset_userdata() fn.
$this->session->sess_destroy() ;
This function should be called only at the end of the execution. For unsetting data (as you're trying to do) it's better to use unset_userdata method. See how you should implement that:
$unset_items = array('user_id' => '', 'username' => '', 'status' => '') ;
$this->ci->session->unset_userdata( $unset_items ) ;
$email = "abc#gmail.com";
///set the session
use the set_userdata function and include the session library
$this->load->library('session');
$this->session->set_userdata('session name',Value);
i.e.
$this->session->set_userdata('email', $email);
//unset the session
$this->session->unset_userdata('session name');
i.e.
$this->session->unset_userdata('email');

Resources