PHP Сохранить сеанс при использовании session_write_close(); - программирование
Подтвердить что ты не робот

PHP Сохранить сеанс при использовании session_write_close();

У меня есть одна страница, где я делаю длинный опрос, который я должен использовать в начале этой страницы.

session_start();
session_write_close();

Потому что:

чтобы предотвратить одновременную запись, только один script может работать в сеансе в любое время

Итак, если я этого не делаю и длительный опрос работает, пользователь не сможет загрузить другую страницу.

Таким образом, доступ к моим данным в сеансе с этой страницы опроса возможен, но в какой-то момент моего script мне нужно сохранить сеанс на сервере, потому что я внес некоторые изменения в него.

Каким образом это сделать?

Это будет очень хорошо, это будет способ сделать что-то вроде

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

Но session_write_open() не существует!

Спасибо

4b9b3361

Ответ 1

Прежде чем внести какое-либо изменение в сеанс, вызовите session_start еще раз. Внесите изменения, и если вы все еще не хотите снова выходить из вызова session_write_close. Вы можете делать это столько раз, сколько захотите.

Ответ 2

Предыдущее решение создаст идентификаторы сеанса и файлы cookie... Я бы не использовал его как есть:

Сессия создается каждый раз, когда вы вызываете session_start(). Если ты хочешь чтобы избежать множественного cookie, напишите лучший код. Несколько session_start() особенно для тех же имен в том же script, кажется, действительно плохая идея.

см. здесь: https://bugs.php.net/bug.php?id=38104

Я ищу решение прямо сейчас, и я не могу его найти. Я согласен с теми, кто говорит, что это "ошибка". Вы должны иметь возможность повторно открыть сеанс php, но, как вы сказали, session_write_open() не существует...

Я нашел обходной путь в вышеупомянутом потоке. Он включает отправку заголовка, указывающего вручную куки файл сеанса после обработки запроса. К счастью, я работаю с домашним контроллером Front Controller, который работает так, что никакой субконтроллер никогда не будет отправлять данные самостоятельно. В двух словах, он отлично работает в моем случае. Чтобы использовать это, вам просто нужно использовать ob_start() и ob_get_clean(). Здесь волшебная линия:

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

EDIT: см. ниже CMCDragonkai, кажется хорошим!?

Ответ 3

Другие ответы здесь представляют довольно хорошие решения. Как упоминалось в @Jon, трюк состоит в том, чтобы снова вызвать session_start(), прежде чем вы захотите внести изменения. Затем, когда вы закончите внесение изменений, снова вызовите session_write_close().

Как упоминалось @Armel Larcier, проблема в том, что PHP пытается генерировать новые заголовки и, скорее всего, генерирует предупреждения (например, если вы уже написали данные без заголовка клиенту). Конечно, вы можете просто префикс session_start() с помощью "@" (@session_start()), но есть лучший подход.

Другой вопрос, предоставленный @VolkerK, показывает лучший ответ:

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

Это предотвращает попытку PHP снова отправлять заголовки. Вы могли бы даже написать вспомогательную функцию, чтобы обернуть функции ini_set(), чтобы сделать это немного более удобным:

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.
}

Оригинальный вопрос и ответ: fooobar.com/questions/295619/...

Ответ 4

После тестирования Armel Larcier обойдется. Здесь мое предлагаемое решение этой проблемы:

    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();

Это сохранит все файлы cookie, которые были созданы до работы с сеансами.

Кстати, вы можете зарегистрировать вышеуказанный код как функцию для register_shutdown_function. Обязательно запустите ob_start() перед функцией и ob_flush() внутри функции.

Ответ 5

Все ответы здесь, по-видимому, говорят о том, чтобы использовать методы сеанса таким образом, чтобы они явно не предназначались для использования... а именно для вызова session_start() более одного раза.

Веб-сайт PHP предлагает пример реализации SessionHandlerInterface, который будет работать так же, как и существующие сеансы, но без блокировки файла. Просто реализуя свой примерный интерфейс, я исправил проблему с блокировкой, чтобы разрешить одновременные соединения на одном сеансе, не ограничивая возможность добавления vars в сеанс. Чтобы предотвратить некоторые условия гонки, поскольку сеанс приложения не является полностью безгосударственным, мне нужно было сделать способ сохранить средний запрос сеанса, не закрывая его, чтобы важные изменения могли сэкономить сразу после изменения, а менее важные сеансовые вары могли просто сэкономить в конце запроса. Пример использования приведен ниже:

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';

Если вы замените Session::start() на session_start() на session_start() и Session::save() на session_write_close(), вы заметите, что последующие запросы никогда не распечатывают третью переменную... она будет потеряна. Однако, используя SessionHandler (ниже), данные не теряются.

Для реализации OOP требуется PHP 5.4+. Тем не менее, вы можете предоставить отдельные методы обратного вызова в старых версиях PHP. См. документы.

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;
        }
    }