Префикс таблиц базы данных

Префикс таблиц базы данных - это строка, которая присоединяется к названию каждой таблицы Joomla в базе данных. Префикс задается при установке Joomla. В старых версиях по умолчанию использовался префикс "jos_", однако это создавало потенциальную уязвимость сайта, т.к. хакеры знали название таблицы с паролями пользователей - "jos_users". Теперь префикс, предлагаемый при установке, генерируется случайным образом.

Использование префикса позволяет разместить в одной базе данных несколько установок Joomla.

Различают реальный и символический префиксы. Реальный префикс - это то конкретное сочетание символов, которое используется в названиях таблиц базы данных. Символический префикс - это сочетание "#__" (решетка и два знака подчеркивания), которое используется в запросах вместо реального префикса. При обработке запроса вместо символического префикса будет автоматически подставлен реальный. Например, при реальном префиксе "jos_" строка "#__mycomponent_mytable" превратится в "jos_mycomponent_mytable".

При разработке собственных расширений в SQL-запросах всегда указывается символический префикс, а не реальный, так как в других установках Joomla почти наверняка будут использоваться другие реальные префиксы.

Выполнение запроса к базе данных

Чтобы выполнить запрос к базе данных Joomla, необходимо осуществить пять операций:

  1. Получение ссылки на объект JDatabase.
  2. Формирование запроса.
  3. Задание запроса.
  4. Выполнение запроса.
  5. При необходимости - загрузка результата.

Получение ссылки на объект JDatabase

JDatabase - абстрактный класс, предоставляющий доступ к соединению с базой данных. Это соединение создается при инициализации приложения Joomla, а в коде своего расширения мы можем получить ссылку на него с помощью метода getDbo() статического класса JFactory:

$db = JFactory::getDbo();

Формирование SQL-запроса

В старых версиях Joomla запросы формулировались в виде строки:

$query = 'SELECT * FROM #__categories';

В Joomla 1.6 появился объект JDatabaseQuery, методы которого позволяют упростить создание сложных SQL-запросов. Названия этих методов практически совпадают с ключевыми словами языка SQL: select(), from(), where(), having(), join() и т.д. Использование объекта JDatabaseQuery иллюстрирует следующий пример:

$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('id, name');
$query->from('#__users');
$query->order('name');
$query->where('username LIKE \'a%\'');
$db->setQuery($query);
echo $query->__toString();

В данном примере мы получаем из таблицы #__users отсортированный по алфавиту список id и имен пользователей, чьи логины начинаются на букву "a". Данный код выведет на экран следующий SQL-запрос:

SELECT id, name 
FROM #__users 
WHERE username LIKE 'a%' 
ORDER BY name

Как известно, употребляющиеся в запросе названия полей и таблиц рекомендуется заключать в ограничители, чтобы избежать совпадений с зарезервированными словами. Кроме того, строковые значения в запросах также берутся в кавычки. Методы nameQuote() и Quote() заключают, соответственно, названия и значения в правильные ограничители. Для MySQL это обратные апострофы (``) для названий и обычные апострофы ('') для значений.

Рассмотрим пример использования этих методов:

$query = 'SELECT * FROM '.
               $db->nameQuote('#__users').
               ' WHERE '.$db->nameQuote('username').
                '='.$db->Quote('admin');

Для базы данных MySQL с префиксом таблиц jos_ переменная $query примет следующее значение:

SELECT * FROM `jos_users` WHERE `username`='admin';

Задание запроса

Чтобы задать SQL-запрос для последующего выполнения, используется метод:

JDatabase setQuery (
                     string $query, 
                    string $offset=0, 
                    string $limit=0
       )

где $query - это запрос, а $offset и $limit - соответственно смещение для начала выборки и количество выбираемых строк.

Например:

$db->setQuery($query, 0, 10);

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

Выполнение запроса

Без выборки данных

Для выполнения запроса, не требующего выборки данных (например, UPDATE или INSERT), используется метод mixed query().

При успешном выполнении запроса метод возвращает указатель на его результат, в противном случае - false. Например:

$db = JFactory::getDBO();
$query = "UPDATE #__users ".
   "SET block = 0 WHERE username LIKE 'a%'";
$db->setQuery($query);
$result = $db->query();

С выборкой данных

В классе JDatabase существуют методы для получения форматированного результата. Их можно разделить на следующие группы:

  1. Получение одного значения: loadResult().
  2. Получение одной строки таблицы: loadRow(), loadAssoc(), loadObject().
  3. Получение одного столбца таблицы: loadColumn().
  4. Получение нескольких строк и нескольких столбцов: loadRowList(), loadAssocList(), loadObjectList().

Рассмотрим каждый из этих методов на примере таблицы #__categories, использующейся Joomla (таблица 2.1).

Таблица 2.1. Таблица #__categories
idasset_idparent_idlftrgtlevelpathextensionlanguage
1 0 0 0 11 0   system *
2 27 1 1 2 1 uncategorised com_content *
3 28 1 3 4 1 uncategorised com_banners *
4 29 1 5 6 1 uncategorised com_contact *
5 30 1 7 8 1 uncategorised com_newsfeeds *
6 31 1 9 10 1 uncategorised com_weblinks *

mixed loadResult()

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

Например, получим значение поля name в записи под номером 2:

$db = JFactory::getDbo();
$query = 'SELECT '.$db->nameQuote('extension').
 ' FROM '.$db->nameQuote('#__categories').
 ' WHERE '.$db->nameQuote('id').'='.$db->Quote('2');
$db->setQuery($query);
echo $db->loadResult();

Результатом выполнения данного запроса будет значение "com_content".

array loadRow()

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

$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
$db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadRow());

Результатом запроса будет следующий список (будем называть списком массив, индексами которого являются числа 0, 1, 2 и т.д.):

Array(
    [0]=>1 
    [1]=>0 
    [2]=>0 
    [3]=>0 
    [4]=>11 
    [5]=>0 
    [6]=> 
    [7]=>system 
    [8]=>ROOT
    [9]=>root 
    [10]=> 
    [11]=> 
    [12]=>1 
    [13]=>0 
    [14]=>0000-00-00 00:00:00 
    [15]=>1 
    [16]=>{} 
    [17]=> 
    [18]=> 
    [19]=> 
    [20]=>0 
    [21]=>2009-10-18 16:07:09
    [22]=>0 
    [23]=>0000-00-00 00:00:00 
    [24]=>0 
    [25]=>*
)

array loadAssoc()

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

 
$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
  $db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadAssoc());

Результат запроса:

 
Array (
    [id]=>1 
    [asset_id]=>0 
    [parent_id]=>0 
    [lft]=>0 
    [rgt]=>11 
    [level]=>0 
    [path]=> 
    [extension]=>system 
    [title]=>ROOT 
    [alias]=>root 
    [note]=> 
    [description]=> 
    [published]=>1 
    [checked_out]=>0
    [checked_out_time]=>0000-00-00 00:00:00
    [access]=>1 
    [params]=>{} 
    [metadesc]=> 
    [metakey]=> 
    [metadata]=> 
    [created_user_id]=>0 
    [created_time]=>2009-10-18 16:07:09
    [modified_user_id]=>0
    [modified_time]=>0000-00-00 00:00:00 
    [hits]=>0 
    [language]=>*
)

object loadObject()

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

 
$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
  $db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadObject());

Результат запроса:

 
stdClass Object (
    [id]=>1 
    [asset_id]=>0 
    [parent_id]=>0
     [lft]=>0 
    [rgt]=>11 
    [level]=>0 
    [path]=> 
    [extension]=>system 
    [title]=>ROOT
    [alias]=>root
    [note]=> 
    [description]=> 
    [published]=>1
    [checked_out]=>0 
    [checked_out_time]=>0000-00-00 00:00:00 
    [access]=>1 
    [params]=>{} 
    [metadesc]=> 
    [metakey]=> 
    [metadata]=> 
    [created_user_id]=>0 
    [created_time]=>2009-10-18 16:07:09 
    [modified_user_id]=>0 
    [modified_time]=>0000-00-00 00:00:00 
    [hits]=>0 
    [language]=>*
)

array loadColumn(int numinarray=0)

Метод загружает массив значений из результирующей выборки, полученных из одного столбца. Параметр numinarray используется для указания того, какой столбец нужно вернуть.

 
$db = JFactory::getDbo();
$query = 'SELECT '.
   $db->nameQuote('extension').
 ' FROM '.$db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadColumn());

Результатом будет следующий список:

 
Array (
    [0]=>com_banners
    [1]=>com_contact 
    [2]=>com_content 
    [3]=>com_newsfeeds 
    [4]=>com_weblinks 
    [5]=>system
)

Данный метод позволяет перебирать в цикле столбцы таблицы:

$db = JFactory::getDbo();
$query = 'SELECT '.$db->nameQuote('path').','.
  $db->nameQuote('extension').','.
  $db->nameQuote('language').
' FROM '.$db->nameQuote('#__categories');
$db->setQuery($query);
for ($i = 0; $i <= 2; $i++)
{
   $column = $db->loadColumn($i);
   print_r($column);
   echo "<br>";
}

В результате на экран будет выведено:

Array (
    [0]=> 
    [1]=>uncategorised 
    [2]=>uncategorised 
    [3]=>uncategorised 
    [4]=>uncategorised 
    [5]=>uncategorised
)
Array ( 
    [0]=>system 
    [1]=>com_content 
    [2]=>com_banners 
    [3]=>com_contact 
    [4]=>com_newsfeeds
    [5]=>com_weblinks
)
Array (
    [0]=>* 
    [1]=>*
    [2]=>*
    [3]=>* 
    [4]=>* 
    [5]=>*
)

array loadRowList(int key)

Метод загружает список массивов или ассоциативный массив массивов. Если задан параметр key, то ключами возвращаемого массива будут значения поля, идущего в таблице под номером key, начиная с нуля.

$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
   $db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadRowList(7));

В данном примере из таблицы #__categories ядра Joomla извлекаются все записи, причем ключами полученного массива будут значения столбца №7, т.е. поля extension:

Array
(
 [system]=>Array([0]=>1 … [25]=>*)
 [com_content]=>Array([0]=>2 … [25]=>*) 
 [com_banners]=>Array([0]=>3 … [25]=>*) 
 [com_contact]=>Array([0]=>4 … [25]=>*) 
 [com_newsfeeds]=>Array([0]=>5 … [25]=>*) 
 [com_weblinks]=>Array([0]=>6 … [25]=>*)
)

Если не указать параметр key, то вместо ассоциативного массива мы получим список:

Array
(
 [0]=>Array([0]=>1 … [25]=>*)
 [1]=>Array([0]=>2 … [25]=>*) 
 [2]=>Array([0]=>3 … [25]=>*) 
 [3]=>Array([0]=>4 … [25]=>*) 
 [4]=>Array([0]=>5 … [25]=>*) 
 [5]=>Array([0]=>6 … [25]=>*)
)

array loadAssocList(string key='', string column='')

Метод загружает список ассоциативных массивов или ассоциативный массив ассоциативных массивов. Если задан параметр key, то ключами полученного массива будут значения столбца под названием key. Если задан параметр column, то в полученном массиве будет всего один столбец column.

 
$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
   $db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadAssocList('extension'));

Результат запроса:

Array
(
    [system]=>Array(
        [id]=>1 
        [asset_id]=>0 
        ... 
        [language]=>*
    )
     [com_content]=>Array(
        [id]=>2 
        [asset_id]=>27
         ... 
        [language]=>*
    ) 
    [com_banners]=>Array(
        [id]=>3 
        [asset_id]=>28
         ... 
        [language]=>*
    ) 
    [com_contact]=>Array(
        [id]=>4 
        [asset_id]=>29 
        ... 
        [language]=>*
    ) 
    [com_newsfeeds]=>Array(
        [id]=>5 
        [asset_id]=>30 
        ... 
        [language]=>*
    ) 
    [com_weblinks]=>Array(
        [id]=>6 
        [asset_id]=>31 
        ... 
        [language]=>*
    )
)

Как видим, ключи полученного массива - это значения поля extension, заданного параметром в метод loadAssocList().

Зададим значение второго параметра, чтобы получить только значение id для каждой строки таблицы:

$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
   $db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadAssocList('extension','id'));

Результат запроса:

Array(
    [system]=>1 
    [com_content]=>2 
    [com_banners]=>3
    [com_contact]=>4
    [com_newsfeeds]=>5
    [com_weblinks]=>6
)

array loadObjectList(string key='')

Метод загружает список объектов stdClass или ассоциативный массив объектов stdClass. Если задан параметр key, то ключами полученного массива будут значения поля под названием key:

 
$db = JFactory::getDbo();
$query = 'SELECT * FROM '.
   $db->nameQuote('#__categories');
$db->setQuery($query);
print_r($db->loadObjectList('extension'));

Результат запроса:

Array
(
     [system]=>stdClass Object(
          [id]=>1
          [asset_id]=>0 
          ... [language]=>*
     )
      [com_content]=>stdClass Object(
          [id]=>2 
          [asset_id]=>27 
          ... 
          [language]=>*
     )
      [com_banners]=>stdClass Object(
          [id]=>3 
          [asset_id]=>28 
          ... [language]=>*
     )
      [com_contact]=>stdClass Object(
          [id]=>4 
          [asset_id]=>29 
          ... 
          [language]=>*
     )
      [com_newsfeeds]=>stdClass Object(
          [id]=>5 
          [asset_id]=>30 
          ... 
          [language]=>*
     )
      [com_weblinks]=>stdClass Object(
          [id]=>6
          [asset_id]=>31
           .. 
          [language]=>*
     )
)

Таблицы базы данных (класс JTable)

Класс JTable реализует паттерн Active Record и используется для управления таблицами базы данных.

Для каждой таблицы, которую вы будете создавать для своего компонента, необходимо создать класс, производный от JTable. Каждый такой класс помещается в отдельном файле в папке /administrator/components/com_<имя компонента>/tables. Имя класса строится по схеме Table<название таблицы>, а файла - <название таблицы>.php.

Для каждого поля таблицы необходимо создать одноименное поле класса.

Кроме того, создается конструктор класса, принимающий ссылку на объект JDatabase. Конструктор вызывает родительский конструктор, передавая ему название таблицы, название поля, являющегося первичным ключом таблицы, и объект JDatabase.

Например, пусть имеется таблица #__mycomponent_mytable из двух столбцов: id и name. Тогда производный от JTable класс должен выглядеть так:

class TableMytable extends JTable
{
  var $id = null;
  var $name = null;
  function __construct(&$db)
  {
     parent::__construct(
       '#__mycomponent_mytable', 'id', $db
     );
  }
}

Чтобы использовать в коде вашего компонента файл, содержащий этот класс, нужно добавить папку tables в список директорий, в которых JTable может искать классы таблиц:

JTable::addIncludePath(
   JPATH_ADMINISTRATOR.DS.'components'.
    DS.'com_mycomponent'.DS.'tables'
);

Теперь при создании экземпляра класса TableMytable Joomla будет искать файл mytable.php.

Для получения экземпляра данного класса используется метод getInstance():

object getInstance(
    string $type, string $prefix= 'JTable', 
    array $config=array()
)

где

$type - вторая часть имени класса;
$prefix - первая часть имени класса;
$config - массив, содержащий настройки конфигурации.

В их числе может находиться объект-представитель базы данных, и тогда он будет использован вместо глобального объекта JDatabase. Например:

$row = JTable::getInstance('mytable', 'Table');

Производный от JTable класс наследует в числе прочих методы bind(), store(), load() и delete(), позволяющие управлять записями таблицы без единой строки SQL-кода.

Создание/редактирование записи таблицы

Как правило, для создания или редактирования записи таблицы во фронтенде или бэкенде создается HTML-форма. Значения, введенные в нее пользователем, можно получить с помощью класса JRequest в виде массива. Полученный массив необходимо связать с объектом JTable. Связывание заключается в том, что каждому полю класса присваивается значение элемента массива, ключ которого совпадает с названием этого поля. Для этого используется метод

bool bind(mixed $src, mixed $ignore=array())

где:

$src - ассоциативный массив или объект для связывания с экземпляром класса;
$ignore - массив или разделенный пробелами список полей, которые нужно игнорировать при связывании.

Например:

if (!$row->bind(JRequest::get('post')))
 return JError::raiseWarning(500, $row->getError());

Обычно ошибка связывания возникает, если поля класса не соответствуют ключам массива, - например, когда в HTML-форме в название элемента input вкралась опечатка.

Для сохранения записи используется метод

bool store(bool $updateNulls=false)

Если параметр $updateNulls равен false, то те поля объекта JTable, которые имеют значение null, будут игнорироваться при связывании.

Пример использования метода store():

if (!$row->store())
 JError::raiseError(500, $row->getError());

Метод store() на основе хранящихся в $row значений генерирует запрос UPDATE или INSERT, в зависимости от значения id. Если запись создается впервые, то она не имеет значения id и будет сконструирован запрос INSERT, в противном случае - UPDATE. Это позволяет использовать данный метод как для создания новых записей, так и для обновления существующих.

Получение записи из таблицы

Для получения записи используется метод

bool load(mixed $keys = NULL, bool $reset = true)

где

$keys - первичный ключ записи, которую необходимо получить, или массив полей для поиска соответствий;
$reset - задает, будут ли перед получением новой записи заново присвоены полям значения по умолчанию.

Например:

$row->load($id);

Удаление записи

Для удаления записи используется метод

bool delete(mixed $id=null)

где $id - первичный ключ записи, которую требуется удалить. Если он не задан, то используется значение соответствующего поля объекта.

Пример:

$row->delete($id);

Управление полями ordering, checked_out/checked_out_time, published и hits

Существуют методы класса JTable для управления часто используемыми полями ordering, checked_out/checked_out_time, published и hits.

ordering

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

void reorder([string $where = ''])

При этом записи сортируются по значению ordering, а затем в это поле записываются натуральные числа, начиная с 1. Параметр $where позволяет задать условие ограничения выборки, к которой будет применена эта операция.

Пример использования метода:

$table->reorder();

Изменить значение ordering для одной записи, передвинув ее выше или ниже в списке, можно с помощью метода

void move(int $dirn, [string $where = ''])

где $dirn - величина, которая будет прибавлена к текущему значению ordering (отрицательное значение приведет к смещению записи вверх, а положительное - вниз).

Например, поднимем запись на одну строку вверх:

$table->load($id);
$table->move(-1);

checked_out/checked_out_time

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

bool isCheckedOut(int $with=0, int $against=null)

где:

$with - id пользователя, с которым нужно сравнить значение поля checked_out. Если запись заблокирована как раз этим пользователем, то функция вернет false, как и в том случае, если она не заблокирована вообще. В обоих этих случаях текущий пользователь имеет право работать с ней;
$against - id пользователя, использующийся, если функция вызвана как статическая.

Для блокировки записей используется метод

bool checkOut(int $userId, mixed $pk=null)

где:

$userId - id пользователя, блокирующего запись;
$pk - первичный ключ записи, которую необходимо заблокировать. Если он не задан, используется значения соответствующего поля класса.

При этом в поле checked_out_time будет записано текущее время.

Для разблокировки записей используется метод

bool checkIn(mixed $pk=null)

Рассмотрим пример использования этих методов:

$table->load($id);
$user = JFactory::getUser();
if ($table->isCheckedOut($user->get('id')))
 die('Запись уже заблокирована другим пользователем');
echo 'Запись не заблокирована';
if (!$table->checkout($user->get('id')))
 die('Не удалось заблокировать запись с id текущего пользователя');
echo 'Заблокировали запись';
// работа с записью...
if (!$table->checkin($user->get('id')))
 die('Не удалось разблокировать запись');
echo 'Разблокировали запись';

published

Значение поля published показывает, опубликована ли запись. Чтобы изменить значение этого поля для одной или нескольких записей, используется метод

bool publish(mixed $pks=null, int $state=1, int $userId=0)

где:

$pks - массив ключей записей, к которым необходимо применить операцию;
$state - новое значение поля published (0 или 1);
$userId - используется только когда в таблице существует также поле checked_out. При наличии в таблице этого поля метод publish() может быть применен только к тем записям, для которых checked_out равно 0 или заданному $userId.

Метод вернет true и в том случае, если какие-либо из записей были заблокированы и для них не удалось изменить значение published.

Пример использования этого метода:

$id_list = array($id);
$user = JFactory::getUser();
if (!$table->publish($id_list, 1, $user->get('id')))
 die($table->getError());

hits

В поле hits хранится количество просмотров записи. Для увеличения этого значения на 1 используется метод

bool hit(mixed $pk=null) 

где $pk - первичный ключ записи.

Например:

$table->hit();