Уровень абстракции базы данных
Phalcon\Db — это компонент, стоящий за Phalcon\Mvc\Model, который управляет слоем модели в фреймворке. Он состоит из независимого уровня абстракции высокого уровня для систем баз данных, полностью написанного на C.
Этот компонент позволяет манипулировать базой данных на более низком уровне, чем с помощью традиционных моделей.
Данное руководство не предназначено для полного описания доступных методов и их аргументов. Для получения полной справки посетите API.
Адаптеры баз данных
Этот компонент использует адаптеры для инкапсуляции деталей конкретной системы баз данных. Phalcon использует PDO для подключения к базам данных. Поддерживаются следующие типы баз данных:
| Класс | Описание |
|---|---|
| Phalcon\Db\Adapter\Pdo\Mysql | Самая используемая в мире система управления реляционными базами данных (СУБД), работающая как сервер, предоставляющий многопользовательский доступ к ряду баз данных |
| Phalcon\Db\Adapter\Pdo\Postgresql | PostgreSQL — мощная, открытая система реляционных баз данных. Она имеет более 15 лет активного развития и проверенную архитектуру, что обеспечило ей хорошую репутацию в плане надёжности, целостности данных и корректности. |
| Phalcon\Db\Adapter\Pdo\Sqlite | SQLite — программная библиотека, реализующая автономный, бессерверный, безконфигурационный, транзакционный движок SQL базы данных |
Реализация собственных адаптеров
Для создания собственных адаптеров баз данных или расширения существующих необходимо реализовать интерфейс Phalcon\Db\AdapterInterface.
Диалекты баз данных
Phalcon инкапсулирует специфические детали каждого движка базы данных в диалектах. Они предоставляют адаптерам общие функции и генератор SQL.
| Класс | Описание | |
|---|---|---|
| Phalcon\Db\Dialect\Mysql | Специфичный для SQL диалект для системы баз данных MySQL | |
| Phalcon\Db\Dialect\Postgresql | Специфичный для SQL диалект для системы баз данных PostgreSQL | |
| Phalcon\Db\Dialect\Sqlite | Специфичный для SQL диалект для системы баз данных SQLite | |
Реализация собственных диалектов
Для создания собственных диалектов баз данных или расширения существующих необходимо реализовать интерфейс Phalcon\Db\DialectInterface.
Подключение к базам данных
Для создания подключения необходимо создать экземпляр класса адаптера. Для этого требуется массив с параметрами подключения. Пример ниже демонстрирует, как создать подключение, передав как обязательные, так и необязательные параметры:
// Required
$config = [
"host" => "127.0.0.1",
"username" => "mike",
"password" => "sigma",
"dbname" => "test_db",
];
// Optional
$config["persistent"] = false;
// Create a connection
$connection = new \Phalcon\Db\Adapter\Pdo\Mysql($config);
// Required
$config = [
"host" => "localhost",
"username" => "postgres",
"password" => "secret1",
"dbname" => "template",
];
// Optional
$config["schema"] = "public";
// Create a connection
$connection = new \Phalcon\Db\Adapter\Pdo\Postgresql($config);
// Required
$config = [
"dbname" => "/path/to/database.db",
];
// Create a connection
$connection = new \Phalcon\Db\Adapter\Pdo\Sqlite($config);
Настройка дополнительных параметров PDO
Вы можете настроить параметры PDO во время подключения, передав параметр ‘options’:
// Create a connection with PDO options
$connection = new \Phalcon\Db\Adapter\Pdo\Mysql(
[
"host" => "localhost",
"username" => "root",
"password" => "sigma",
"dbname" => "test_db",
"options" => [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'",
PDO::ATTR_CASE => PDO::CASE_LOWER,
]
]
);
Поиск строк
Phalcon\Db предоставляет несколько методов для запроса строк из таблиц. В этом случае требуется конкретный синтаксис SQL для целевого движка базы данных:
$sql = "SELECT id, name FROM robots ORDER BY name";
// Send a SQL statement to the database system
$result = $connection->query($sql);
// Print each robot name
while ($robot = $result->fetch()) {
echo $robot["name"];
}
// Get all rows in an array
$robots = $connection->fetchAll($sql);
foreach ($robots as $robot) {
echo $robot["name"];
}
// Get only the first row
$robot = $connection->fetchOne($sql);
По умолчанию эти вызовы создают массивы с ассоциативными и числовыми индексами. Вы можете изменить это поведение, используя Phalcon\Db\Result::setFetchMode(). Этот метод принимает константу, определяющую необходимый тип индексов.
| Константа | Описание |
|---|---|
Phalcon\Db::FETCH_NUM | Возвращает массив с числовыми индексами |
Phalcon\Db::FETCH_ASSOC | Возвращает массив с ассоциативными индексами |
Phalcon\Db::FETCH_BOTH | Возвращает массив с ассоциативными и числовыми индексами |
Phalcon\Db::FETCH_OBJ | Возвращает объект вместо массива |
$sql = "SELECT id, name FROM robots ORDER BY name";
$result = $connection->query($sql);
$result->setFetchMode(Phalcon\Db::FETCH_NUM);
while ($robot = $result->fetch()) {
echo $robot[0];
}
Метод Phalcon\Db::query() возвращает экземпляр Phalcon\Db\Result\Pdo. Эти объекты инкапсулируют всю функциональность, связанную с набором результатов запроса, т.е. проход по результатам, поиск конкретных записей, подсчёт и т.д.
$sql = "SELECT id, name FROM robots";
$result = $connection->query($sql);
// Traverse the resultset
while ($robot = $result->fetch()) {
echo $robot["name"];
}
// Seek to the third row
$result->seek(2);
$robot = $result->fetch();
// Count the resultset
echo $result->numRows();
Связывание параметров
Связывание параметров также поддерживается в Phalcon\Db. Хотя использование связанных параметров оказывает минимальное влияние на производительность, рекомендуется использовать этот метод, чтобы исключить возможность SQL-инъекций в ваш код. Поддерживаются как строковые, так и позиционные плейсхолдеры. Связывание параметров можно выполнить следующим образом:
// Binding with numeric placeholders
$sql = "SELECT * FROM robots WHERE name = ? ORDER BY name";
$result = $connection->query(
$sql,
[
"Wall-E",
]
);
// Binding with named placeholders
$sql = "INSERT INTO `robots`(name`, year) VALUES (:name, :year)";
$success = $connection->query(
$sql,
[
"name" => "Astro Boy",
"year" => 1952,
]
);
При использовании числовых плейсхолдеров их необходимо определять как целые числа, т.е. 1 или 2. В этом случае «1» или «2» считаются строками, а не числами, поэтому плейсхолдер не может быть успешно заменён. Во всех адаптерах данные автоматически экранируются с помощью PDO Quote.
Эта функция учитывает кодировку символов подключения, поэтому рекомендуется определять правильную кодировку символов в параметрах подключения или в конфигурации вашего сервера базы данных, так как неправильная кодировка может привести к нежелательным последствиям при хранении или извлечении данных.
Кроме того, вы можете передавать параметры напрямую методам execute/query. В этом случае связанные параметры непосредственно передаются в PDO:
// Binding with PDO placeholders
$sql = "SELECT * FROM robots WHERE name = ? ORDER BY name";
$result = $connection->query(
$sql,
[
1 => "Wall-E",
]
);
Вставка/Обновление/Удаление строк
Для вставки, обновления или удаления строк вы можете использовать исходный SQL или предопределённые функции класса:
// Inserting data with a raw SQL statement
$sql = "INSERT INTO `robots`(`name`, `year`) VALUES ('Astro Boy', 1952)";
$success = $connection->execute($sql);
// With placeholders
$sql = "INSERT INTO `robots`(`name`, `year`) VALUES (?, ?)";
$success = $connection->execute(
$sql,
[
"Astro Boy",
1952,
]
);
// Generating dynamically the necessary SQL
$success = $connection->insert(
"robots",
[
"Astro Boy",
1952,
],
[
"name",
"year",
],
);
// Generating dynamically the necessary SQL (another syntax)
$success = $connection->insertAsDict(
"robots",
[
"name" => "Astro Boy",
"year" => 1952,
]
);
// Updating data with a raw SQL statement
$sql = "UPDATE `robots` SET `name` = 'Astro boy' WHERE `id` = 101";
$success = $connection->execute($sql);
// With placeholders
$sql = "UPDATE `robots` SET `name` = ? WHERE `id` = ?";
$success = $connection->execute(
$sql,
[
"Astro Boy",
101,
]
);
// Generating dynamically the necessary SQL
$success = $connection->update(
"robots",
[
"name",
],
[
"New Astro Boy",
],
"id = 101" // Warning! In this case values are not escaped
);
// Generating dynamically the necessary SQL (another syntax)
$success = $connection->updateAsDict(
"robots",
[
"name" => "New Astro Boy",
],
"id = 101" // Warning! In this case values are not escaped
);
// With escaping conditions
$success = $connection->update(
"robots",
[
"name",
],
[
"New Astro Boy",
],
[
"conditions" => "id = ?",
"bind" => [101],
"bindTypes" => [PDO::PARAM_INT], // Optional parameter
]
);
$success = $connection->updateAsDict(
"robots",
[
"name" => "New Astro Boy",
],
[
"conditions" => "id = ?",
"bind" => [101],
"bindTypes" => [PDO::PARAM_INT], // Optional parameter
]
);
// Deleting data with a raw SQL statement
$sql = "DELETE `robots` WHERE `id` = 101";
$success = $connection->execute($sql);
// With placeholders
$sql = "DELETE `robots` WHERE `id` = ?";
$success = $connection->execute($sql, [101]);
// Generating dynamically the necessary SQL
$success = $connection->delete(
"robots",
"id = ?",
[
101,
]
);
Транзакции и вложенные транзакции
Работа с транзакциями поддерживается так же, как и в PDO. Манипуляции с данными внутри транзакций часто увеличивают производительность большинства систем баз данных:
try {
// Start a transaction
$connection->begin();
// Execute some SQL statements
$connection->execute("DELETE `robots` WHERE `id` = 101");
$connection->execute("DELETE `robots` WHERE `id` = 102");
$connection->execute("DELETE `robots` WHERE `id` = 103");
// Commit if everything goes well
$connection->commit();
} catch (Exception $e) {
// An exception has occurred rollback the transaction
$connection->rollback();
}
В дополнение к стандартным транзакциям, Phalcon\Db предоставляет встроенную поддержку вложенных транзакций (если используемая система баз данных их поддерживает). При повторном вызове begin() создаётся вложенная транзакция:
try {
// Start a transaction
$connection->begin();
// Execute some SQL statements
$connection->execute("DELETE `robots` WHERE `id` = 101");
try {
// Start a nested transaction
$connection->begin();
// Execute these SQL statements into the nested transaction
$connection->execute("DELETE `robots` WHERE `id` = 102");
$connection->execute("DELETE `robots` WHERE `id` = 103");
// Create a save point
$connection->commit();
} catch (Exception $e) {
// An error has occurred, release the nested transaction
$connection->rollback();
}
// Continue, executing more SQL statements
$connection->execute("DELETE `robots` WHERE `id` = 104");
// Commit if everything goes well
$connection->commit();
} catch (Exception $e) {
// An exception has occurred rollback the transaction
$connection->rollback();
}
События базы данных
Phalcon\Db может отправлять события в EventsManager, если он присутствует. Некоторые события при возвращении false могут остановить активную операцию. Поддерживаются следующие события:
| Имя события | Вызывается | Может остановить операцию? |
|---|---|---|
| afterConnect | После успешного подключения к системе баз данных | Нет |
| beforeQuery | Перед отправкой SQL-запроса в систему баз данных | Да |
| afterQuery | После отправки SQL-запроса в систему баз данных | Нет |
| beforeDisconnect | Перед закрытием временного подключения к базе данных | Нет |
| beginTransaction | Перед началом транзакции | Нет |
| rollbackTransaction | Перед откат транзакции | Нет |
| commitTransaction | Перед подтверждением транзакции | Нет |
Связывание EventsManager с подключением простое, Phalcon\Db будет вызывать события с типом “db”:
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Adapter\Pdo\Mysql as Connection;
$eventsManager = new EventsManager();
// Listen all the database events
$eventsManager->attach('db', $dbListener);
$connection = new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
// Assign the eventsManager to the db adapter instance
$connection->setEventsManager($eventsManager);
Остановка SQL-операций очень полезна, например, если вы хотите реализовать проверку SQL-инъекций на последнем этапе:
use Phalcon\Events\Event;
$eventsManager->attach(
"db:beforeQuery",
function (Event $event, $connection) {
$sql = $connection->getSQLStatement();
// Check for malicious words in SQL statements
if (preg_match("/DROP|ALTER/i", $sql)) {
// DROP/ALTER operations aren't allowed in the application,
// this must be a SQL injection!
return false;
}
// It's OK
return true;
}
);
Профилирование SQL-запросов
Phalcon\Db включает компонент профилирования под названием Phalcon\Db\Profiler, который используется для анализа производительности операций с базой данных, чтобы диагностировать проблемы производительности и выявлять узкие места.
Профилирование базы данных очень просто с помощью Phalcon\Db\Profiler:
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Profiler as DbProfiler;
$eventsManager = new EventsManager();
$profiler = new DbProfiler();
// Listen all the database events
$eventsManager->attach(
"db",
function (Event $event, $connection) use ($profiler) {
if ($event->getType() === "beforeQuery") {
$sql = $connection->getSQLStatement();
// Start a profile with the active connection
$profiler->startProfile($sql);
}
if ($event->getType() === "afterQuery") {
// Stop the active profile
$profiler->stopProfile();
}
}
);
// Assign the events manager to the connection
$connection->setEventsManager($eventsManager);
$sql = "SELECT buyer_name, quantity, product_name "
. "FROM buyers "
. "LEFT JOIN products ON buyers.pid = products.id";
// Execute a SQL statement
$connection->query($sql);
// Get the last profile in the profiler
$profile = $profiler->getLastProfile();
echo "SQL Statement: ", $profile->getSQLStatement(), "\n";
echo "Start Time: ", $profile->getInitialTime(), "\n";
echo "Final Time: ", $profile->getFinalTime(), "\n";
echo "Total Elapsed Time: ", $profile->getTotalElapsedSeconds(), "\n";
Вы также можете создать свой собственный класс профиля, основанный на Phalcon\Db\Profiler, чтобы записывать статистику в реальном времени для запросов, отправленных в систему баз данных:
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Profiler as Profiler;
use Phalcon\Db\Profiler\Item as Item;
class DbProfiler extends Profiler
{
/**
* Executed before the SQL statement will sent to the db server
*/
public function beforeStartProfile(Item $profile)
{
echo $profile->getSQLStatement();
}
/**
* Executed after the SQL statement was sent to the db server
*/
public function afterEndProfile(Item $profile)
{
echo $profile->getTotalElapsedSeconds();
}
}
// Create an Events Manager
$eventsManager = new EventsManager();
// Create a listener
$dbProfiler = new DbProfiler();
// Attach the listener listening for all database events
$eventsManager->attach("db", $dbProfiler);
Ведение журнала SQL-запросов
Использование компонентов высокого уровня абстракции, таких как Phalcon\Db для доступа к базе данных, затрудняет понимание, какие запросы отправляются в систему баз данных. Phalcon\Logger взаимодействует с Phalcon\Db, предоставляя возможности ведения журнала на уровне абстракции базы данных.
use Phalcon\Logger;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Logger\Adapter\File as FileLogger;
$eventsManager = new EventsManager();
$logger = new FileLogger("app/logs/db.log");
$eventsManager->attach(
"db:beforeQuery",
function (Event $event, $connection) use ($logger) {
$sql = $connection->getSQLStatement();
$logger->log($sql, Logger::INFO);
}
);
// Assign the eventsManager to the db adapter instance
$connection->setEventsManager($eventsManager);
// Execute some SQL statement
$connection->insert(
"products",
[
"Hot pepper",
3.50,
],
[
"name",
"price",
]
);
Как и выше, файл app/logs/db.log будет содержать что-то вроде этого:
[Sun, 29 Apr 12 22:35:26 -0500][DEBUG][Resource Id #77] INSERT INTO products
(name, price) VALUES ('Hot pepper', 3.50)
Реализация собственного логгера
Вы можете реализовать собственный класс логгера для запросов к базе данных, создав класс, реализующий единственный метод с именем «log». Метод должен принимать строку в качестве первого аргумента. Затем вы можете передать свой объект логгирования в Phalcon\Db::setLogger(), и с этого момента любой SQL-запрос, выполненный, будет вызывать этот метод для регистрации результатов.
Описание таблиц/представлений
Phalcon\Db также предоставляет методы для получения подробной информации о таблицах и представлениях:
// Get tables on the test_db database
$tables = $connection->listTables("test_db");
// Is there a table 'robots' in the database?
$exists = $connection->tableExists("robots");
// Get name, data types and special features of 'robots' fields
$fields = $connection->describeColumns("robots");
foreach ($fields as $field) {
echo "Column Type: ", $field["Type"];
}
// Get indexes on the 'robots' table
$indexes = $connection->describeIndexes("robots");
foreach ($indexes as $index) {
print_r(
$index->getColumns()
);
}
// Get foreign keys on the 'robots' table
$references = $connection->describeReferences("robots");
foreach ($references as $reference) {
// Print referenced columns
print_r(
$reference->getReferencedColumns()
);
}
Описание таблицы очень похоже на команду MySQL describe, оно содержит следующую информацию:
| Индекс | Описание |
|---|---|
| Поле | Имя поля |
| Тип | Тип столбца |
| Ключ | Является ли столбец частью первичного ключа или индекса? |
| Null | Может ли столбец принимать нулевые значения? |
Для каждой поддерживаемой системы баз данных также реализованы методы получения информации о представлениях:
// Get views on the test_db database
$tables = $connection->listViews("test_db");
// Is there a view 'robots' in the database?
$exists = $connection->viewExists("robots");
Создание/Изменение/Удаление таблиц
Разные системы баз данных (MySQL, Postgresql и т.д.) позволяют создавать, изменять или удалять таблицы с помощью команд, таких как CREATE, ALTER или DROP. Синтаксис SQL отличается в зависимости от используемой системы баз данных. Phalcon\Db предлагает унифицированный интерфейс для изменения таблиц без необходимости различать синтаксис SQL в зависимости от целевой системы хранения.
Создание таблиц
Следующий пример демонстрирует создание таблицы:
use \Phalcon\Db\Column as Column;
$connection->createTable(
"robots",
null,
[
"columns" => [
new Column(
"id",
[
"type" => Column::TYPE_INTEGER,
"size" => 10,
"notNull" => true,
"autoIncrement" => true,
"primary" => true,
]
),
new Column(
"name",
[
"type" => Column::TYPE_VARCHAR,
"size" => 70,
"notNull" => true,
]
),
new Column(
"year",
[
"type" => Column::TYPE_INTEGER,
"size" => 11,
"notNull" => true,
]
),
]
]
);
Phalcon\Db::createTable() принимает ассоциативный массив, описывающий таблицу. Столбцы определяются с помощью класса Phalcon\Db\Column. Таблица ниже показывает доступные параметры для определения столбца:
| Параметр | Описание | Необязательно |
|---|---|---|
| “type” | Тип столбца. Должен быть константой Phalcon\Db\Column (см. ниже для списка) | Нет |
| “primary” | Истина, если столбец является частью первичного ключа таблицы | Да |
| “size” | Некоторые типы столбцов, такие как VARCHAR или INTEGER, могут иметь определённый размер | Да |
| “scale” | Столбцы DECIMAL или NUMBER могут иметь масштаб, чтобы указать, сколько десятичных знаков должно храниться | Да |
| “unsigned” | Столбцы INTEGER могут быть со знаком или без знака. Этот параметр не относится к другим типам столбцов | Да |
| “notNull” | Столбец может хранить нулевые значения? | Да |
| “default” | Значение по умолчанию (при использовании с "notNull" => true) | Да |
| “autoIncrement” | С этим атрибутом столбец будет автоматически заполняться целочисленным значением с автоинкрементом. Только один столбец в таблице может иметь этот атрибут. | Да |
| “bind” | Одна из констант BIND_TYPE_*, определяющая, как столбец должен быть привязан перед сохранением | Да |
| “first” | Столбец должен быть помещен на первое место в порядке столбцов | Да |
| “after” | Столбец должен быть помещен после указанного столбца | Да |
Phalcon\Db поддерживает следующие типы столбцов баз данных:
Phalcon\Db\Column::TYPE_INTEGERPhalcon\Db\Column::TYPE_DATEPhalcon\Db\Column::TYPE_VARCHARPhalcon\Db\Column::TYPE_DECIMALPhalcon\Db\Column::TYPE_DATETIMEPhalcon\Db\Column::TYPE_CHARPhalcon\Db\Column::TYPE_TEXT
Ассоциативный массив, передаваемый в Phalcon\Db::createTable(), может содержать следующие возможные ключи:
| Индекс | Описание | Необязательно |
|---|---|---|
| “columns” | Массив с набором столбцов таблицы, определённых с помощью Phalcon\Db\Column | Нет |
| “indexes” | Массив с набором индексов таблицы, определённых с помощью Phalcon\Db\Index | Да |
| “references” | Массив с набором ссылок таблицы (внешние ключи), определённых с помощью Phalcon\Db\Reference | Да |
| “options” | Массив с набором параметров создания таблицы. Эти параметры часто связаны с системой баз данных, в которой была сгенерирована миграция. | Да |
Изменение таблиц
По мере роста вашего приложения вам, возможно, потребуется изменить вашу базу данных в рамках рефакторинга или добавления новых функций. Не все системы баз данных позволяют изменять существующие столбцы или добавлять столбцы между двумя существующими. Phalcon\Db ограничен этими ограничениями.
use Phalcon\Db\Column as Column;
// Adding a new column
$connection->addColumn(
"robots",
null,
new Column(
"robot_type",
[
"type" => Column::TYPE_VARCHAR,
"size" => 32,
"notNull" => true,
"after" => "name",
]
)
);
// Modifying an existing column
$connection->modifyColumn(
"robots",
null,
new Column(
"name",
[
"type" => Column::TYPE_VARCHAR,
"size" => 40,
"notNull" => true,
]
)
);
// Deleting the column "name"
$connection->dropColumn(
"robots",
null,
"name"
);
Удаление таблиц
Примеры удаления таблиц:
// Drop table robot from active database
$connection->dropTable("robots");
// Drop table robot from database "machines"
$connection->dropTable("robots", "machines");
© 2011–2017 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/latest/reference/db.html