База данных

Содержание...

Работа с базой реализует слой «модель» в терминологии MVC. В состав фреймворка входит абстрактный класс waModel для описания модели. Классы описания сущностей предметной области приложения наследуются от waModel.

Файлы классов модели размещаются в файловой структуре приложения в подкаталоге приложения lib/models/.

В описании модели есть только одно обязательное поле — название таблицы в базе данных в переменной $table. Пример класса описания модели:

<?php
class blogModel extends waModel
{
    protected $table = 'blog';
}

Модель инициализируется в коде приложения следующим образом:

$model = new blogModel();

В этом примере blog — это пример идентификатора приложения. После инициализации модель получает от базы данных описание указанной таблицы (с помощью запроса DESCRIBE $table) и кеширует его. На этом этапе определяется главный ключ (primary key) таблицы, определяются поля и их типы.

В коде контроллеров и экшенов все взаимодействие с базой данных происходит исключительно через классы моделей приложения:

// Создаем экземпляр модели для получения данных из БД
$model = new guestbookModel();
// Получаем записи гостевой книги из БД
$records = $model->order('datetime DESC')->fetchAll();

Класс waModel содержит ряд сервисных методов для автоматического построения SQL-запросов, что позволяет достичь независимости кода приложения от конкретного вида используемой СУБД.

Методы автоматического построения SQL-запросов

Запросы SELECT

// Получить запись по значению первичного ключа
$model->getById($id);
// Получить запись по значению одного из полей
$model->getByField('name', $name);  // возвращает первую найденную запись
// Если необходимо вернуть все записи в предыдущем примере,
// то нужно добавить в вызов метода третий параметр - true
$model->getByField('name', $name, true);
// Можно задать условия отбора по значению нескольких полей
// (в запросе они будут объединены через AND)
$model->getByField(array('name' => $name, 'contact_id' => $contact_id));
// Если необходимо провести отбор по нескольким возможным значениям одного поля,
// то вместо одной переменной задается массив, в этом случае при построении запроса
// будет использоваться оператор IN
$model->getByField('name' , array($name1, $name2));
// Агрегатные запросы типа SELECT COUNT(*) ... WHERE ... создаются методом countByField
// Параметры вызова метода полностью аналогичны getByField
$model->countByField($field, $value);
$model->countByField(array('name' => $name, 'contact_id' => $contact_id));
$model->countByField('name' , array($name1, $name2));

Запросы UPDATE

// Обновить запись по значению первичного ключа
$model->updateById($id, $data); // $data - ассоциативный массив значений, которые нужно обновить
$model->updateById($id, array('field_1' => $field_1, 'field_2' => $field_2));
// По аналогии с методом getByField также доступен метод updateByField,
// которому в качестве третьего параметра передается массив обновляемых значений:
$model->updateByField('contact_id', $contact_id, array('published' => true));

Запросы DELETE

// По аналогии с запросами UPDATE доступны следующие два метода:
$model->deleteById($id);
$model->deleteByField($field, $value);

Запросы INSERT и REPLACE

// Для вставки/замены записи доступны методы:
// Оба эти метода вставляют только одну запись
$model->insert($data); // в случае auto_increment метод вернет id вставленной записи
$model->replace($data);

Выполнение SQL-запросов, записанных в явном виде

Во фреймворке доступны два основных метода модели для выполнения SQL-запросов — query и exec. Первым параметром они принимают SQL-запрос, а вторым — необязательный массив значений для плейсхолдеров.

Пример:

$model->query("SELECT * FROM ".$this->table." WHERE id = i:id", array('id' => $id));

Пояснения к примеру:

Доступные для использования типы значений плейсхолдеров:

Если тип плейсхолдера явно не указан (например, в запросе просто используется :id, то по умолчанию используется тип String). Использование плейсхолдеров упрощает построение запросов, а главное исключает возможность написания небезопасных SQL-запросов, подверженных SQL-инъекциям.

Использование плейсхолдеров не является обязательным, однако в случае отказа от них разработчик должен самостоятельно позаботиться о подстановке данных в запрос. Здесь может быть полезным метод модели escape, который экранирует строки (при использовании СУБД MySQL его работа аналогична результату выполнения функции mysql_real_escape_string):

$model->query("SELECT * FROM ".$this->table." WHERE id = ".(int)$id);
$model->query("SELECT * FROM ".$this->table." WHERE name LIKE '".$this->escape($name)."'");

Единственное отличие между методами query и exec заключается в результате, который они возвращают.

Метод query($sql, $params = null)

Метод выполняет SQL-запрос и возвращает объект результата. Тип возвращаемого объекта зависит от типа запроса:

Запросы SELECT

Возвращается объект результата типа waDbResultSelect, который можно использовать, например, в цикле foreach. Примеры работы с waDbResultSelect:

$result = $model->query("SELECT * FROM ".$this->table." WHERE text LIKE '%тест%'");
// Получаем количество записей в результате (аналогично функции mysql_num_rows)
$result->count();
// Проходим по всем записям результата в цикле:
foreach ($result as $row) {
    ...
}
// Получить все записи в массив
$data = $result->fetchAll();
// Если в методе fetchAll указать первым параметром название поля, то результатом будет
// ассоциативный массив с ключами, равными значению этого поля
$data = $result->fetchAll('id');
// Если известно, что в результате только одна запись, либо если нужно получить данные
// только одной записи, то будут полезны следующие методы:
// аналог mysql_fetch_assoc
$result->fetch();
// выполняет fetch и возвращает значение по переданному ключу, либо значение первого
// элемента массива, если ключ не указан
$result->fetchField('id');
// например, так можно получить результат запроса COUNT
$n = $model->query("SELECT count(*) FROM ".$this->table." WHERE ...")->fetchField();

Запросы DELETE и UPDATE

Возвращается объект результата типа waDbResultDelete и waDbResultUpdate.

$result = $model->query("UPDATE ".$this->table." SET name = 'no name' WHERE name = ''");
// Получить количество затронутых записей (функция mysql_affected_rows)
$result->affectedRows()

Запросы INSERT

Возвращается объект типа waDbResultInsert.

// Получить значение первичного ключа созданной записи в случае
// auto_increment(функция mysql_insert_id)
$result->lastInsertId()

Метод exec($sql, $params = null)

Метод выполняет SQL-запрос и возвращает результат, полученный от адаптера работы с БД (в случае с MySQL возвращается результат выполнения функции mysql_query). Этот метод стоит использовать в тех случаях, когда результат выполнения запроса не нужен или достаточно проверить, успешно ли выполнился запрос.

Конструктор SELECT-запросов

SELECT-запросы можно строить с помощью простого конструктора запросов, например:

// Выбрать все записи отсортированные по полю datetime:
$records = $model->order('datetime DESC')->fetchAll();
// Получить ассоциативный массив id => name
$data = $model->select('id,name')->fetchAll('id', true);
// В текущей версии доступны методы select, where, order
$model->select('id, name, datetime')
      ->where('contact_id = '.(int)$contact_id)
      ->order('datetime DESC')
      ->fetchAll('id');
// Предыдущий пример выполняет то же самое, что и следующий:
$model->query("SELECT id, name, datetime WHERE contact_id = ".(int)$contact_id." ORDER BY datetime DESC")->fetchAll('id');

Рекомендации по реализации дополнительных методов модели в приложении

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

Пример:

// метод, возвращающий все записи контакта
public function getByContact($contact_id)
{
    return $this->getByField('contact_id', $contact_id, true);
}

Все методы для работы с одной таблицей реализуются в соответствующем классе модели. В дальнейшем вы можете усовершенствовать эти методы модели (например добавлять кеширование).

Рекомендуется большинство операций с конкретной таблицей БД реализовывать в собственных методах модели, не привязанных к названию полей. Это позволит в случае рефакторинга данной таблицы внести изменения лишь в один файл — в код класса модели.

Подключение к другим базам данных

Класс waModel можно использовать для подключения к другим базам кроме той, в которой хранятся данные Вебасиста. Это может быть полезно, например, для импорта данных из стороннего программного обеспечения.

Для подключения к другой базе данных необходимо при создании экземпляра модели в качестве аргумента конструктора указать параметры подключения. Сделать это можно несколькими способами.

Создание постоянной конфигурации подключения

Если вы планируете многократно использовать в вашем приложении подключение к другой (какой-то определенной) базе данных, добавьте параметры подключения в конфигурационный файл фреймворка wa-config/db.php в виде дополнительного массива, как показано в примере:

<?php
return array (
    'default' => 
        array (
            'host' => 'localhost',
            'user' => 'user1',
            'password' => '12345678',
            'database' => 'webasyst',
            'type' => 'mysqli',
        ),
      'extra' => 
        array (
            'host' => 'localhost',
            'user' => 'user2,
            'password' => '87654321',
            'database' => 'wordpress',
            'type' => 'mysqli',
        ),
);

В приведенном выше примере добавлена новая запись с параметрами подключения, условно обозначенная как extra. Для подключения к базе данных wordpress (в данном примере) необходимо в PHP-коде приложения создать экземпляр модели следующим образом:

$extra_model = new waModel('extra');

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

Динамическое указание параметров подключения

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

$extra_connection = array (
    'host' => 'localhost',
    'user' => 'user2',
    'password' => '87654321',
    'database' => 'somedb',
    'type' => 'mysqli',
);
$extra_model = new waModel($extra_connection);

Оба приведенных выше способа подключения к другой базе данных применимы не только к экземпляру базового класса waModel, но и к экземплярам классов моделей вашего приложения (например, myappSomeModel):

$extra_model = new myappSomeModel('extra');

либо

//массив $extra_connection должен быть заполнен данными подключения к БД
$extra_model = new myappSomeModel($extra_connection);

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

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