Статьи

Использование SQL Azure для хранения данных сеанса PHP

В моем последнем посте я рассмотрел функциональность обработки сеансов, встроенную в Windows Azure SDK для PHP , которая использует таблицы Azure или BLOB-объекты Azure для хранения данных сеансов. Когда я писал эту статью, я удивлялся, насколько легко будет использовать SQL Azure для хранения данных сеанса, тем более что использование базы данных для хранения данных сеанса является обычной и привычной практикой при создании распределенных приложений PHP. Как я выяснил, использование SQL Azure для хранения данных сеанса было относительно простым (как я покажу в этом посте), но я столкнулся с парой небольших препятствий, которые, возможно, стоит обратить внимание.

Примечание . Поскольку я буду использовать драйверы SQL Server для PHP для подключения к SQL Azure, вы можете рассмотреть этот пост также как «Использование SQL Server для хранения данных сеанса PHP». Драйверы SQL Server для PHP подключаются к SQL Azure или SQL Server путем простого изменения строки подключения.

Короткий рассказ здесь заключается в том, что я просто использовал функцию session_set_save_handler для сопоставления функциональности сеанса с пользовательскими функциями. Самым большим препятствием, с которым я столкнулся, было то, что мне пришлось учесть это предупреждение в документации к session_set_save_handler : «Начиная с PHP 5.0.5, обработчики записи и закрытия вызываются после уничтожения объекта и поэтому не могут использовать объекты или генерировать исключения. Однако деструкторы объекта могут использовать сеансы. Можно решить session_write_close () из деструктора, чтобы решить эту проблему с курицей и яйцом ». Я справился с этим, поместив мои сессионные функции в класс и включив метод __destruct, который вызывал session_write_close (), Меньшим препятствием было то, что мне нужно было написать хранимую процедуру, которая вставила бы новую строку, если строка еще не существовала, но обновила ее, если она существует.

Полная история следует. Я предполагаю, что у вас уже есть подписка на Windows Azure (если у вас ее нет, вы можете получить бесплатную пробную подписку здесь: http://www.microsoft.com/windowsazure/free-trial/ ). Имейте в виду, что этот код является «доказательством концепции» кода — он нуждается в некоторой доработке, чтобы быть готовым к производству.

1. Создайте базу данных, таблицу и хранимую процедуру (хранимая процедура описана выше). Для простоты моего PHP-кода предполагается, что вы создали базу данных с именем SessionsDB с таблицей, называемой сеансами, и хранимой процедурой, называемой UpdateOrInsertSession . (Элемент TODO должен добавить создание таблицы и хранимой процедуры в код PHP, но создание базы данных должно быть выполнено отдельно.) Чтобы создать эти объекты, выполните приведенный ниже код, используя портал SQL Azure или SQL Server Management Studio (подробности в этой статье — Обзор инструментов для использования с SQL Azure ):

Создать таблицу:

    CREATE TABLE sessions
    (
        id NVARCHAR(32) NOT NULL,
        start_time INT NOT NULL,
        data NVARCHAR(4000) NOT NULL,
        CONSTRAINT [PK_sessions] PRIMARY KEY CLUSTERED 
        (
            [id]
        )
    )

Создать хранимую процедуру:

    CREATE PROCEDURE UpdateOrInsertSession
    ( 
        @id AS NVARCHAR(32), 
        @start_time AS INT, 
        @data AS NVARCHAR(4000)
    )
    AS
    BEGIN    
        IF EXISTS (SELECT id FROM sessions WHERE id = @id)    
            BEGIN       
                UPDATE  sessions        
                SET  data = @data        
                WHERE id = @id      
            END    
        ELSE    
            BEGIN             
                INSERT INTO sessions (id, start_time, data)       
                VALUES ( @id, @start_time, @data )    
            END
    END

Об этой таблице следует отметить одну вещь: столбец данных будет содержать все данные сеанса в сериализованной форме. Это обеспечивает большую гибкость данных, которые вы храните.

2. Добавьте класс SqlAzureSessionHandler в свой проект . Полный класс прилагается к этому сообщению, но я здесь назову несколько вещей…

Конструктор принимает идентификатор вашего сервера, имя пользователя и пароль. Форматирование параметров подключения позаботится, но потребуется изменить, если вы используете SQL Server. (т. е. имя пользователя не требует суффикса «@serverId», а имя вашего сервера не требует префикса «tcp» и суффикса «.database.windows.net».)

Также обратите внимание, что session_set_save_handler вызывается в конструкторе.

    public function __construct($serverId, $username, $password)
    {    
        $connOptions = array("UID"=>$username."@".$serverId, "PWD"=>$password, "Database"=>"SessionsDB");
        $this->_conn = sqlsrv_connect("tcp:".$serverId.".database.windows.net", $connOptions);
        if(!$this->_conn)
        {
            die(print_r(sqlsrv_errors()));
        }
     
        session_set_save_handler(
                                 array($this, 'open'),
                                 array($this, 'close'),
                                 array($this, 'read'),
                                 array($this, 'write'),
                                 array($this, 'destroy'),
                                 array($this, 'gc')
                                 );
    }

Метод write сериализует, а base64 кодирует все данные сеанса перед записью в SQL Azure. Обратите внимание, что здесь используется хранимая процедура InsertOrUpdateSession, так что новые данные сеанса вставляются, а существующие данные сеанса обновляются:

    public function write($id, $data)
    {
        $serializedData = base64_encode(serialize($data));
        $start_time = time();
        $params = array($id, $start_time, $serializedData);
        $sql = "{call UpdateOrInsertSession(?,?,?)}";
        
        $stmt = sqlsrv_query($this->_conn, $sql, $params);
        if($stmt === false)
        {
            die(print_r(sqlsrv_errors()));
        }
        return $stmt;
    }

Конечно, когда данные сеанса читаются, они должны быть декодированы в base64 и не сериализированы:

    public function read($id)
    {
        // Read data
        $sql = "SELECT data
                FROM sessions
                WHERE id = ?";
     
        $stmt = sqlsrv_query($this->_conn, $sql, array($id));
        if($stmt === false)
        {
            die(print_r(sqlsrv_errors()));
        }
        
        if (sqlsrv_has_rows($stmt))
        {
            $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
            return unserialize(base64_decode($row['data']));
        }
        
        return '';
    }

В методах close , destroy и gc нет сюрпризов . Однако обратите внимание, что этот метод __destruct должен быть включен:

    function __destruct()
    {
        session_write_close(); // IMPORTANT!
    }

3. Создайте экземпляр SqlAzureSessionHandler перед вызовом функций сеанса, как обычно. После создания нового объекта SqlAzureSessionHandler вы можете обрабатывать сеансы как обычно (но данные будут храниться в SQL Azure):

    require_once "SqlAzureSessionHandler.php";
    $sessionHandler = new SqlAzureSessionHandler("serverId", "username", "password");
     
    session_start();
     
    if(isset($_POST['username']))
    {
        $username = $_POST['username'];
        $password = $_POST['password'];
     
        $_SESSION['username'] = $username;
        $_SESSION['time'] = time();
        $_SESSION['otherdata'] = "some other session data";
        header("Location: welcome.php");
    }

Вот и все. Надеюсь, что это информативно, если не полезно.