От сессии до сессии. Сессии CI и Oracle
Что-то последнее время чаще и чаще появляются негативные отзывы о 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.

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