Работа с базой реализует слой «модель» в терминологии 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-запросов, что позволяет
достичь независимости кода приложения от конкретного вида используемой СУБД.
// Получить запись по значению первичного ключа
$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));
// Обновить запись по значению первичного ключа
$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));
// По аналогии с запросами UPDATE доступны следующие два метода: $model->deleteById($id); $model->deleteByField($field, $value);
// Для вставки/замены записи доступны методы:
// Оба эти метода вставляют только одну запись
$model->insert($data); // в случае auto_increment метод вернёт id вставленной записи $model->replace($data);
Во фреймворке доступны два основных метода модели для выполнения SQL-запросов — query и exec.
Первым параметром они принимают SQL-запрос, а вторым — необязательный массив значений для плейсхолдеров.
Пример:
$model->query("SELECT * FROM ".$this->table." WHERE id = i:id", array('id' => $id));
Пояснения к примеру:
i:id — это именованный плейсхолдер, в котором i указывает на тип Integer (то есть значение, полученное из массива, будет приведено к целочисленному типу);id — ключ в массиве значений;array('id' => $id) — массив значений.Доступные для использования типы значений плейсхолдеров:
's' — String'i' — Integer'b' — Boolean'f' — Float/DoubleЕсли тип плейсхолдера явно не указан (например, в запросе просто используется :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-запрос и возвращает объект результата. Тип возвращаемого объекта зависит от типа запроса:
waDbResultSelect,waDbResultInsertwaDbResultUpdatewaDbResultDeleteВозвращается объект результата типа 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();
Возвращается объект результата типа waDbResultDelete и waDbResultUpdate.
$result = $model->query("UPDATE ".$this->table." SET name = 'no name' WHERE name = ''");
// Получить количество затронутых записей (функция mysql_affected_rows)
$result->affectedRows()
Возвращается объект типа waDbResultInsert.
// Получить значение первичного ключа созданной записи в случае // auto_increment(функция mysql_insert_id) $result->lastInsertId()
exec($sql, $params = null)Метод выполняет SQL-запрос и возвращает результат, полученный от адаптера работы с БД (в случае с MySQL возвращается
результат выполнения функции mysql_query). Этот метод стоит использовать в тех случаях, когда результат
выполнения запроса не нужен или достаточно проверить, успешно ли выполнился запрос.
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);
}
Все методы для работы с одной таблицей реализуются в соответствующем классе модели. В дальнейшем вы можете усовершенствовать эти методы модели (например добавлять кеширование).
Рекомендуется большинство операций с конкретной таблицей БД реализовывать в собственных методах модели, не привязанных в названию полей. Это позволит в случае рефакторинга данной таблицы внести изменения лишь в один файл — в код класса модели.