Работа с базой данных реализует слой «модель» в терминологии 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(['name' => $name, 'contact_id' => $contact_id]);
// Если необходимо провести отбор по нескольким возможным значениям одного поля,
// то вместо одной переменной задается массив, в этом случае при построении запроса
// будет использоваться оператор IN
$model->getByField('name' , [$name1, $name2]);
// Агрегатные запросы типа SELECT COUNT(*) ... WHERE ... создаются методом countByField
// Параметры вызова метода полностью аналогичны getByField
$model->countByField($field, $value);
$model->countByField(['name' => $name, 'contact_id' => $contact_id]);
$model->countByField('name' , [$name1, $name2]);
Запросы UPDATE
// Обновить запись по значению первичного ключа
$model->updateById($id, $data); // $data - ассоциативный массив значений, которые нужно обновить
$model->updateById($id, ['field_1' => $field_1, 'field_2' => $field_2]);
// По аналогии с методом getByField также доступен метод updateByField,
// которому в качестве третьего параметра передается массив обновляемых значений:
$model->updateByField('contact_id', $contact_id, ['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", ['id' => $id]);
Пояснения к примеру:
i:id— это именованный плейсхолдер, в которомiуказывает на тип Integer (то есть значение, полученное из массива, будет приведено к целочисленному типу);id— ключ в массиве значений;['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 заключается в результате, который они возвращают.
Подробнее об использовании разных видов плейсхолдеров — в описании класса waModel ›
Метод query($sql, $params = null)
Метод выполняет SQL-запрос и возвращает объект результата. Тип возвращаемого объекта зависит от типа запроса:
- для SELECT-запросов возвращается объект типа
waDbResultSelect, - для INSERT-запросов —
waDbResultInsert - для UPDATE-запросов —
waDbResultUpdate - для DELETE-запросов —
waDbResultDelete
Запросы 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');
Методы конструктора возвращают экземпляр класса waDbQuery с публичным методом getQuery() — с его помощью удобно проверить, какой SQL-запрос сформировал конструктор. Для этого нужно заменить метод получения результата — fetch(), fetchField(), fetchAssoc(), fetchAll() — на метод getQuery():
$sql = $model
->select('id, name, datetime')
->where('contact_id = ' . (int)$contact_id)
->order('datetime DESC')
->getQuery();
echo $sql;
// SELECT id, name, datetime FROM myapp_table_name WHERE contact_id = 42 ORDER BY datetime DESC
Рекомендации по реализации дополнительных методов модели в приложении
Чтобы во всех местах приложения не писать один и тот же код (напрямую используя базовые методы модели), можно в модели вашего приложения объявить методы для реализации часто используемых операций.
Пример:
// метод, возвращающий все записи контакта
public function getByContact($contact_id)
{
return $this->getByField('contact_id', $contact_id, true);
}
Все методы для работы с одной таблицей реализуются в соответствующем классе модели. В дальнейшем вы можете усовершенствовать эти методы модели (например добавлять кеширование).
Рекомендуется большинство операций с конкретной таблицей БД реализовывать в собственных методах модели, не привязанных к названию полей. Это позволит в случае рефакторинга данной таблицы внести изменения лишь в один файл — в код класса модели.
Подключение к другим базам данных
Класс waModel можно использовать для подключения к другим базам кроме той, в которой хранятся данные Вебасиста. Это может быть полезно, например, для импорта данных из стороннего программного обеспечения.
Для подключения к другой базе данных необходимо при создании экземпляра модели в качестве аргумента конструктора указать параметры подключения. Сделать это можно несколькими способами.
Создание постоянной конфигурации подключения
Если вы планируете многократно использовать в вашем приложении подключение к другой (какой-то определенной) базе данных, добавьте параметры подключения в конфигурационный файл фреймворка wa-config/db.php в виде дополнительного массива, как показано в примере:
<?php
return [
'default' => [
'host' => 'localhost',
'user' => 'user1',
'password' => '12345678',
'database' => 'webasyst',
'type' => 'mysqli',
],
'extra' => [
'host' => 'localhost',
'user' => 'user2,
'password' => '87654321',
'database' => 'wordpress',
'type' => 'mysqli',
],
];
В приведенном выше примере добавлена новая запись с параметрами подключения, условно обозначенная как extra. Для подключения к базе данных wordpress (в данном примере) необходимо в PHP-коде приложения создать экземпляр модели следующим образом:
$extra_model = new waModel('extra');
При использовании методов созданной таким образом модели обращение будет выполняться только к базе данных, имя которой указано в соответствующей конфигурации.
Динамическое указание параметров подключения
Если использовать постоянное подключение к какой-то заранее известной базе данных не планируется, то при создании экземпляра модели параметры подключения можно указать явно — в виде массива данных. Пример создания модели в PHP-коде приложения таким способом показан ниже:
$extra_connection = [
'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);
При использовании таких подключений в моделях вашего приложения дополнительные (созданные вами) методы этих моделей должны учитывать структуру таблиц указываемой базы данных.
Для подключения к базам данных с различной структурой рекомендуется создавать отдельные классы моделей. Это позволит упростить структуру вашего приложения и облегчит отладку программного кода.









