От сессии до сессии. Сессии CI и Oracle

CI Server RequirementsЧто-то последнее время чаще и чаще появляются негативные отзывы о Code Igniter. Ну пусть бы они и были эти отзывы, да лишь бы нас не касалось… Ан нет, коснулось. Понадобилось нашей доблестной команде писать сессии в базу. Да не в простую, типа MySQL, а прямо в Oracle. Ну любим мы его…

Сделали все по инструкции, как полагается. Создали табличку в базе, прописали настройки и полезли искать, где же наши сессии? А их то и нету. Что ему не так? Что не эдак? И хоть бы ошибочку какую показал. Хотя нет, ошибку показал, вся ругань есть в логах.

Собственно долго думать не пришлось, то, что в инструкции для MySQL зовется user_data text в Oracle — USER_DATA CLOB. А с клобами еще надо подружиться…

К сожаление CI не позволяет перекрывать функционал драйверов БД (здесь и далее имеются в виду библиотеки CI для работы с Oracle), поэтому придется вносить изменения в код родного драйвера. Который, видимо из-за того, что используется в веб-разработке не так широко как MySQL, дописан не до конца и периодически там попадаются заплатки типа:

function db_set_charset($charset, $collation)
{
	// @todo - add support if needed
	return TRUE;
}

Однако, я забежал немного вперед. Для начала нужно разобраться и понять почему не записываются данные в таблицу CI_SESSIONS. А не записывается именно custom_userdata для которых создан столбец типа CLOB (сама строчка в таблице появляется). Собственно, сделать это не сложно, открываем файл system/libraries/Session.php и находим методы, которые создают и изменяют записи с данными сессий. Это Session::sess_create(), Session::sess_update() и Session::sess_write().

Session::sess_create(), как видно из названия, создает запись в таблице и записывает туда только простые поля.

Session::sess_update(), понятное дело, обновляет запись. И снова только простые поля.

А вот Session::sess_write() — это как раз тот метод, который должен записывать custom_userdata. И его нужно научить работать с CLOB — полями. Изначально тут было примерно вот что (я оставил только то, что интересно):

function sess_write()
{
  ...
  // Run the update query
  $this->CI->db->where('session_id', $this->userdata['session_id']);
  $this->CI->db->update($this->sess_table_name, array('last_activity' => 
    $this->userdata['last_activity'], 'user_data' => $custom_userdata));
 
  // Write the cookie.  Notice that we manually pass the cookie data array to the
  // _set_cookie() function. Normally that function will store $this->userdata, but
  // in this case that array contains custom data, which we do not want in the cookie.
  $this->_set_cookie($cookie_userdata);
}

Понятное дело, что тут собирается строка с SQL запросом, в которую подставляются параметры и затем этот запрос выполняется. Но не тут то было. С CLOB-ами такие фокусы не пройдут тут нужна привязка параметра. oci_bind_by_name то, что надо, но… Драйвер этого делать не умеет, а сделать это прямо тут не позволяет чувство прекрасного.

Поэтому было предпринято два шага. Первый — реализация недостающего функционала для oci8 драйвера. Второй — создание библиотеки MY_Session, где переписан функционал метода sess_write.
Возможно, более правильным решеним было бы полностью доработать драйвер. Но предложенный метод прост, а active record в проекте практически не использовался, поэтому его переписывание было отложено до лучших времен.

В соответствии с первым пунктом нашего нехитрого плана родился небольшой метод для oci8_driver по фамилии execute_query, предназначенный для выполнения DML запросов и специально обученный работать с параметрами. Через некоторое время этот метод оброс и дополнительными возможностями типа записи ошибок в лог, бенчмакринга и пр. (что я заменю на …), но суть осталась проста — подставить в запрос параметры и попытаться выполнить его.

    function execute_query($sql, &$params = array())
    {
        /** подготовка запроса, настройка бенчмаркинга и отладчика скромно опущены **/
        $stmt = oci_parse($this->conn_id, $sql);
        if (!$stmt) {
            $e = oci_error($this->conn_id);
            return $e['message'];
        }
        // цепляем параметры к запросу
        if (count($params) > 0) {
          foreach ($params as $param_name => $the_parameter) {
              $bind_result = @oci_bind_by_name($stmt, $param_name, $params[$param_name]);
          }
        } // if
        $result = @oci_execute($stmt);
        if (!$result) {
            // обработка ошибок пусть останется... без нее будет совсем некрасиво
            $e = oci_error($stmt);
            log_message('error', 'Query execution error (' . $sql . '): ' . $e['message']);
            return $e['message'];
        }
        return true;
    }

Ну и по пункту два наследуемся от класса Session, переносим себе метод sess_write и заменяем в нем вызов запроса на изменение данных сессии.

function sess_write()
{
  ...
    // Run the update query
    $this->CI->db->execute_query(
      'UPDATE  ci_sessions  SET  last_activity = :last_activity ,  user_data = :user_data  
          WHERE  session_id  = :session_id', 
      array(':last_activity' => $this->userdata['last_activity'],
            ':user_data' => $custom_userdata,
            ':session_id' => $this->userdata['session_id'])
    );
  // Write the cookie.  Notice that we manually pass the cookie data array to the
  // _set_cookie() function. Normally that function will store $this->userdata, but
  // in this case that array contains custom data, which we do not want in the cookie.
  $this->_set_cookie($cookie_userdata);
}

Пожалуй что все. В итоге мы получили работающий механизм сессий с храниением информации в Oracle и полезный метод для работы с базой.

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

P.S.: Все-таки я добрался и навел ревизию в своих черновиках. Большинство из них уже морально устарели или просто мне не понравились и поэтому я от них избавился. Эта же статья имеет право на жизнь. На счет публикации oci8_driver пока не буду спешить, пожалуй подожду CI 2.0. Тем более, что он уже Baking.

Добавить комментарий


XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">