Работа в Drupal с несколькими базами данных

Sep 18, 2008 04:29


Из блога http://shaman.asiadata.ru.
Оригинал сообщения находится здесь.
Ваши комментарии.



Если вам необходимо на друпал-сайте использовать данные из другой базы (может быть даже с другого хоста), то это достаточно просто сделать. Предоставленные для этого возможности не затрагивают кода ядра и могут быть использованы, как в виде вставок PHP-кода в материалы сайта, так и в собственных модулях и темах.

Находим в папке sites/default файл settings.php.
Редактируем его. Вместо строчки

$db_url = 'mysql://username:password@localhost/databasename';
ставим

$db_url = array(
  'default'=>'mysql://username:pass@localhost/databasename',
  'db1'=>'mysql://username1:pass@host1/databasename1',
  'db2'=>'mysql://username2:pass@host2/databasename2',
/*
**  Сколько угодно параметров для требуемого количества баз данных
**  Индексы кроме 'default' могут быть любыми.  
*/
);
Теперь если в сниппете, модуле, блоке или материале вам понадобятся данные из других баз, используем следующий код:

/*
** Делаем активной базу с параметрами под индексом 'db1'
*/
db_set_active('db1');
$result = db_query("Здесь нужный вам запрос к таблицам в db1")
/*
**  Здесь обрабатываем результат первого запроса
*/
// Делаем активной базу с параметрами под индексом 'db2'
db_set_active('db2');
$result = db_query("Здесь нужный вам запрос к таблицам в db2")
/*
**  Здесь обрабатываем результат второго запроса
*/

/*
**  В конце ОБЯЗАТЕЛЬНО делаем активной «родную» базу,
**  чтобы Drupal мог нормально завершить обработку страницы.
*/
db_set_active('default');
Переключаться между базами можно сколько угодно раз - после первого обращения Drupal кеширует ресурс соединения с БД в массиве $db_conns и повторного соединения не производится.

Если вам недоступно редактирование файла установок, то можно установить требуемое соединение сразу в PHP-коде:

/*
** Получаем глобальные переменные Drupal
*/
global $db_url;
/*
** Добавляем свою строку подключения к БД, а родную оставляем под индексом 'default'
*/
$db_url = array(
  "default"=>$db_url,
  "db1"=>"mysql://username1:pass@host1/databasename1"
);
/*
** Делаем активной базу с параметрами под индексом 'db1'
*/
db_set_active('db1');
$result = db_query("Здесь нужный вам запрос к таблицам в db1")
/*
**  Здесь обрабатываем результат запроса
*/

/*
**  В конце ОБЯЗАТЕЛЬНО делаем активной «родную» базу,
**  чтобы Drupal мог нормально завершить обработку страницы.
*/
db_set_active('default');

Указанные решения работают в D5 и D6.

Единственное ограничение - тип баз данных должен быть одним для всех соединений, т.е. следующий код вызовет ошибку:

$db_url = array(
  'default'=>'mysqli://username:pass@localhost/databasename',
  'db1'=>'pgsql://username1:pass@host1/databasename1',
);
Вы получите: Cannot redeclare db_status_report() (previously declared in /var/www/mysite/includes/database.mysqli.inc:23) in /var/www/mysite.ru/includes/database.pgsql.inc

Устранить данное ограничение можно только хаком ядра Drupal. Хак получается весьма объемистым. С указанной ошибкой все дело не в функции db_status_report(), а со способом которым Drupal подключает интерфейс требуемого типа баз данных. Дело в том, что названия всех функций (за исключением одной - db_check_setup) находящихся в файлах database.pgsql.inc, database.mysql.inc, database.mysqli.inc и database.mysql-common.inc совпадают, что приводит к конфликту в именах 41 функции. Поэтому использовать несколько подключений к базам разных типов можно только полностью переписав слой абстракции баз данных в Drupal.

Чтобы сделать это откроем все упомянутые файлы в редакторе.

Из файлов database.mysql.inc и database.mysqli.inc удаляем строчку

require_once './includes/database.mysql-common.inc';

Содержимое файла database.mysql-common.inc копируем в файлы database.mysql.inc и database.mysqli.inc, а сам файл делаем пустым.

Далее во всех трех файлах database.pgsql.inc, database.mysql.inc, database.mysqli.inc переименовываем все функции добавляя к их именам постфикс типа базы данных: в файле database.pgsql.inc - _pgsql; в файле database.mysql.inc - _mysql; в файле database.mysqli.inc - _mysqli. Переименовывать не надо только функцию db_check_setup - она уникальна для файла database.pgsql.inc.

Открываем файл database.inc и создаем там 41(!!!) функцию со следующими именами.

_db_create_field_sql()
_db_create_key_sql()
_db_create_keys_sql()
_db_process_field()
_db_process_field()
_db_query()
db_add_field()
db_add_index()
db_add_primary_key()
db_add_unique_key()
db_affected_rows()
db_change_field()
db_column_exists()
db_connect()
db_create_table_sql()
db_decode_blob()
db_distinct_field()
db_drop_field()
db_drop_index()
db_drop_primary_key()
db_drop_table()
db_drop_unique_key()
db_encode_blob()
db_error ()
db_escape_string()
db_fetch_array()
db_fetch_object()
db_field_set_default()
db_field_set_no_default()
db_last_insert_id()
db_lock_table()
db_query()
db_query_range()
db_query_temporary()
db_rename_table()
db_result()
db_status_report()
db_table_exists()
db_type_map()
db_unlock_tables()
db_version()

Все функции выглядят однотипно, различаясь только названием.

function имя_функции () {
  global $db_type;
  $args = func_get_args();
  return call_user_func_array("имя_функции_".$db_type, $args);
}

Несомненно, большой объем правок кода ядра может отпугнуть желающих использовать это. Есть еще один способ, требующий меньшего объема кода. Основная проблема в том, что в PHP обычно нет возможности удаления или переопределения (перегрузки) функций. Правда, одно из расширений PHP - Runkit позволяет сделать это. Если runkit подключен, то достаточно сделать небольшую вставку в код функции db_set_active(). Находим строчки:

$db_type = substr($connect_url, 0, strpos($connect_url, '://'));
    $handler = "./includes/database.$db_type.inc";
    if (is_file($handler)) {
      include_once $handler;
    }
    else {
      _db_error_page("The database type '". $db_type ."' is unsupported. Please use either
'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
    }

$db_conns[$name] = db_connect($connect_url);
  }
и добавляем свой код:

$db_type = substr($connect_url, 0, strpos($connect_url, '://'));
    $handler = "./includes/database.$db_type.inc";
    if (is_file($handler)) {
      $fnames = array(
        '_db_create_field_sql', '_db_create_key_sql', '_db_create_keys_sql',
        '_db_process_field', '_db_process_field', '_db_query',
        'db_add_field', 'db_add_index', 'db_add_primary_key',
        'db_add_unique_key', 'db_affected_rows', 'db_change_field',
        'db_column_exists', 'db_connect', 'db_create_table_sql',
        'db_decode_blob', 'db_distinct_field', 'db_drop_field',
        'db_drop_index', 'db_drop_primary_key', 'db_drop_table',
        'db_drop_unique_key', 'db_encode_blob', 'db_error',
        'db_escape_string', 'db_fetch_array', 'db_fetch_object',
        'db_field_set_default', 'db_field_set_no_default', 'db_last_insert_id',
        'db_lock_table', 'db_query', 'db_query_range', 'db_query_temporary',
        'db_rename_table', 'db_result', 'db_status_report',
        'db_table_exists', 'db_type_map', 'db_unlock_tables', 'db_version',
      );
      foreach ($fnames as $fname) {
        @runkit_function_remove($fname);
      }
      include $handler;
    }
    else {
      _db_error_page("The database type '". $db_type ."' is unsupported. Please use either
'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
    }

$db_conns[$name] = db_connect($connect_url);
  }
В этом случае определения всех функций баз данных будут удалены, а при подключении файла другого типа БД определены заново.
Источники:

db_set_active()
runkit_function_remove()
2 соединения с БД
Переключение между базами

drupal 5, Заметки начинающего друпаллурга, базы данных, drupal 6, хак, drupal

Previous post Next post
Up