26

I've one page where i do a long polling i've to use at the begin of this page this

session_start();
session_write_close();

Because :

to prevent concurrent writes only one script may operate on a session at any time

So if i do not and the long polling is running the user will not be able to load another page.

So accessing to my data in session from this polling page is possible but at some point in my script i've to save my session back to the server because i made some change in it.

What's the way to do it?

That will be very nice it'll be a way to do something like

session_write_open();
//do stuff
session_write_close();

But the session_write_open() doesn't exist!

Thanks

Jerome Ansia
  • 6,854
  • 11
  • 53
  • 99
  • Belatedly, for future readers, I'm suggesting that you use `session_set_save_handler()` as more of a best-practice since it doesn't involve any work-arounds, but modifies the session as the PHP authors seem to have intended. I have posted an example of how to do this below. – Kevin Nelson Jan 16 '15 at 23:11

5 Answers5

22

Before you make some change to the session, call session_start again. Make the changes, and if you still do not want to exit call session_write_close once more. You can do this as many times as you like.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • why is it working for me without 'session_start'? I just make session_write_close at the very beginning of my script, containing a lot of logic including updates of session, and everything works fine, session updated correctly – Victor Bredihin Jan 19 '17 at 12:27
  • @VictorBredihin without looking at the actual code I have no idea what might be happening. Perhaps you could post a new question. – Jon Jan 26 '17 at 11:35
12

The previous solution will create a session ids and cookies... I wouldn't use it as is:

Session is created every time you call session_start(). If you want to avoid multiple cookie, write better code. Multiple session_start() especially for the same names in the same script seems like a really bad idea.

see here : https://bugs.php.net/bug.php?id=38104

I am looking for a solution right now too and I can't find one. I agree with those who say this is a "bug". You should be able to reopen a php session, but as you said session_write_open() does not exist...

I found a workaround in the above thread. It involves sending a header specifying manually the session id's cookie after processing the request. Luckily enough I am working with a home-brewed Front Controller that works so that no sub-controller will ever send data on its own. In a nutshell, it works perfectly in my case. To use this you might just have to use ob_start() and ob_get_clean(). Here's the magic line:

if (SID) header('Set-Cookie: '.SID.'; path=/', true);

EDIT : see CMCDragonkai's answer below, seems good!?

Armel Larcier
  • 15,747
  • 7
  • 68
  • 89
  • Ok i didn't know about the cookies, yes calling session_start(); doesn't make much sense but i have no other solution to fix the problem that i am aware of ;) Thanks for the info about the bug! – Jerome Ansia Dec 05 '12 at 15:07
9

All of the answers here seem to be saying to use the session methods in ways that they were clearly not intended to be used...namely calling session_start() more than once.

The PHP website offers an example SessionHandlerInterface implementation that will work just like existing sessions but without locking the file. Just implementing their example interface fixed my locking issue to allow for concurrent connections on the same session without limiting my ability to add vars to the session. To prevent some race conditions, since the app's session isn't fully stateless, I did have to make a way to save the session mid-request without closing it so that important changes could save immediately after change and less important session vars could just save at the end of the request. See the below example for usage:

Session::start();
echo("<pre>Vars Stored in Session Were:\n");print_r($_SESSION);echo("</pre>");

$_SESSION['one']    = 'one';
$_SESSION['two']    = 'two';
//save won't close session and subsequent request will show 'three'
Session::save(); 
$_SESSION['three']  = 'three';

If you replace that Session::start() with session_start() and Session::save() with session_write_close(), you'll notice that subsequent requests will never print out the third variable...it will be lost. However, using the SessionHandler (below), no data is lost.

The OOP implementation requires PHP 5.4+. However, you can provide individual callback methods in older versions of PHP. See docs.

namespace {
    class Session implements SessionHandlerInterface {
        /** @var Session */
        private static $_instance;
        private $savePath;

        public static function start() {
            if( empty(self::$_instance) ) {
                self::$_instance = new self();
                session_set_save_handler(self::$_instance,true);
                session_start();
            }
        }
        public static function save() {
            if( empty(self::$_instance) ) {
                throw new \Exception("You cannot save a session before starting the session");
            }
            self::$_instance->write(session_id(),session_encode());
        }
        public function open($savePath, $sessionName) {
            $this->savePath = $savePath;
            if (!is_dir($this->savePath)) {
                mkdir($this->savePath, 0777);
            }

            return true;
        }
        public function close() {
            return true;
        }
        public function read($id) {
            return (string)@file_get_contents("$this->savePath/sess_$id");
        }
        public function write($id, $data) {
            return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
        }
        public function destroy($id) {
            $file = "$this->savePath/sess_$id";
            if (file_exists($file)) {
                unlink($file);
            }

            return true;
        }
        public function gc($maxlifetime) {
            foreach (glob("$this->savePath/sess_*") as $file) {
                if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                    unlink($file);
                }
            }

            return true;
        }
    }
Kevin Nelson
  • 7,613
  • 4
  • 31
  • 42
8

The other answers here present pretty good solutions. As mentioned by @Jon, the trick is to call session_start() again before you want to make changes. Then, when you are done making changes, call session_write_close() again.

As mentioned by @Armel Larcier, the problem with this is that PHP attempts to generate new headers and will likely generate warnings (e.g. if you've already written non-header data to the client). Of course, you can simply prefix the session_start() with "@" (@session_start()), but there's a better approach.

Another Stack Overflow question, provided by @VolkerK reveals the best answer:

session_start(); // first session_start
...
session_write_close();
...

ini_set('session.use_only_cookies', false);
ini_set('session.use_cookies', false);
//ini_set('session.use_trans_sid', false); //May be necessary in some situations
ini_set('session.cache_limiter', null);
session_start(); // second session_start

This prevents PHP from attempting to send the headers again. You could even write a helper function to wrap the ini_set() functions to make this a bit more convenient:

function session_reopen() {
    ini_set('session.use_only_cookies', false);
    ini_set('session.use_cookies', false);
    //ini_set('session.use_trans_sid', false); //May be necessary in some situations
    ini_set('session.cache_limiter', null);
    session_start(); //Reopen the (previously closed) session for writing.
}

Original related SO question/answer: https://stackoverflow.com/a/12315542/114558

Community
  • 1
  • 1
rinogo
  • 8,491
  • 12
  • 61
  • 102
  • 1
    This is a good find. The *best* solution would be to simply never output content in chunks (always worked for me) in which case you do get the multiple headers but they are harmless. However that may not be possible if e.g. you have inherited some code, so this workaround has a valid use case. – Jon Jul 30 '13 at 09:02
  • This seems very convoluted when you can just use session_set_save_handler and avoid the problem altogether and have it do what you want. – Kevin Nelson Jan 16 '15 at 23:08
4

After testing out Armel Larcier's work around. Here's my proposed solution to this problem:

    ob_start();

    session_start();
    session_write_close();

    session_start();
    session_write_close();

    session_start();
    session_write_close();

    session_start();
    session_write_close();

    if(SID){

        $headers =  array_unique(headers_list());   

        $cookie_strings = array();

        foreach($headers as $header){
            if(preg_match('/^Set-Cookie: (.+)/', $header, $matches)){
                $cookie_strings[] = $matches[1];
            }
        }

        header_remove('Set-Cookie');

        foreach($cookie_strings as $cookie){
            header('Set-Cookie: ' . $cookie, false);
        }

    }

    ob_flush();

This will preserve any cookies that were created prior to working with sessions.

BTW, you may wish to register the above code as function for register_shutdown_function. Make sure to run ob_start() before the function, and ob_flush() inside the function.

CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98