数据库抽象层

最后更新于:2022-04-02 05:14:16

[TOC] # 数据库抽象层 `Phalcon\Db`是 `Phalcon\Mvc\Model` 背后的组件,它为框架中的模型层提供支持。它由一个完全用C语言编写的数据库系统的独立高级抽象层组成。 与使用传统模型相比,此组件允许更低级别的数据库操作。 ## 数据库适配器 此组件使用适配器来封装特定的数据库系统详细信息。Phalcon使用PDO连接数据库。支持以下数据库引擎: | 类 | 描述 | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Phalcon\Db\Adapter\Pdo\Mysql` | 是世界上使用最多的关系数据库管理系统(RDBMS),它作为服务器运行,提供对多个数据库的多用户访问。 | | `Phalcon\Db\Adapter\Pdo\Postgresql` | PostgreSQL是一个功能强大的开源关系数据库系统。它拥有超过15年的积极开发经验和经过验证的架构,在可靠性,数据完整性和正确性方面赢得了良好的声誉。 | | `Phalcon\Db\Adapter\Pdo\Sqlite` | SQLite是一个软件库,它实现了一个独立的,无服务器,零配置的事务SQL数据库引擎。 | ### 工厂 使用`适配器`选项加载PDO适配器类。例如: ```php 'localhost', 'dbname' => 'blog', 'port' => 3306, 'username' => 'sigma', 'password' => 'secret', 'adapter' => 'mysql', ]; $db = Factory::load($options); ``` ### 实现自己的适配器 必须实现`Phalcon\Db\AdapterInterface`接口才能创建自己的数据库适配器或扩展现有数据库适配器。 ## 数据库方言 Phalcon用方言封装了每个数据库引擎的具体细节。它们为适配器提供了通用功能和SQL生成器。 | 类 | 描述 | | ---------------------------------- | --------------------------------------------------- | | `Phalcon\Db\Dialect\Mysql` | MySQL数据库系统的SQL特定方言 | | `Phalcon\Db\Dialect\Postgresql` | PostgreSQL数据库系统的SQL特定方言 | | `Phalcon\Db\Dialect\Sqlite` | SQLite数据库系统的SQL特定方言 | ### 实现自己的方言 必须实现`Phalcon\Db\DialectInterface`接口才能创建自己的数据库方言或扩展现有数据库方言。 ## 连接到数据库 要创建连接,必须实例化适配器类。它只需要一个带有连接参数的数组。下面的示例显示了如何创建传递必需参数和可选参数的连接: ```php '127.0.0.1', 'username' => 'mike', 'password' => 'sigma', 'dbname' => 'test_db', ]; // 可选 $config['persistent'] = false; // 创建连接 $connection = new \Phalcon\Db\Adapter\Pdo\Mysql($config); ``` ```php 'localhost', 'username' => 'postgres', 'password' => 'secret1', 'dbname' => 'template', ]; // 可选 $config['schema'] = 'public'; // 创建连接 $connection = new \Phalcon\Db\Adapter\Pdo\Postgresql($config); ``` ```php '/path/to/database.db', ]; // 创建连接 $connection = new \Phalcon\Db\Adapter\Pdo\Sqlite($config); ``` ## 设置其他PDO选项 您可以通过传递参数选项在连接时设置PDO选项: ```php 'localhost', 'username' => 'root', 'password' => 'sigma', 'dbname' => 'test_db', 'options' => [ PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'", PDO::ATTR_CASE => PDO::CASE_LOWER, ] ] ); ``` ## 使用工厂连接 您还可以使用简单的`ini`文件来配置/连接`db`服务。 ```ini [database] host = TEST_DB_MYSQL_HOST username = TEST_DB_MYSQL_USER password = TEST_DB_MYSQL_PASSWD dbname = TEST_DB_MYSQL_NAME port = TEST_DB_MYSQL_PORT charset = TEST_DB_MYSQL_CHARSET adapter = mysql ``` ```php set('config', $config); $di->set( 'db', function () { return Factory::load($this->config->database); } ); ``` 以上将返回正确的数据库实例,并且还具有以下优点:您可以更改连接凭据甚至数据库适配器,而无需更改应用程序中的单行代码。 ## 查找行 `Phalcon\Db` 提供了几种从表中查询行的方法。在这种情况下,需要目标数据库引擎的特定SQL语法: ```php query($sql); // 打印每个 robot name while ($robot = $result->fetch()) { echo $robot['name']; } // 获取数组中的所有行 $robots = $connection->fetchAll($sql); foreach ($robots as $robot) { echo $robot['name']; } // 只获得第一行 $robot = $connection->fetchOne($sql); ``` 默认情况下,这些调用会创建具有关联索引和数字索引的数组。您可以使用`Phalcon\Db\Result::setFetchMode()`更改此行为。此方法接收常量,定义需要哪种索引。 | 常量 | 描述 | | -------------------------- | --------------------------------------------------------- | | `Phalcon\Db::FETCH_NUM` | 返回带有数字索引的数组 | | `Phalcon\Db::FETCH_ASSOC` | 返回带关联索引的数组 | | `Phalcon\Db::FETCH_BOTH` | 返回包含关联索引和数字索引的数组 | | `Phalcon\Db::FETCH_OBJ` | 返回一个对象而不是一个数组 | ```php query($sql); $result->setFetchMode(Phalcon\Db::FETCH_NUM); while ($robot = $result->fetch()) { echo $robot[0]; } ``` `Phalcon\Db::query()`返回`Phalcon\Db\Result\Pdo`的实例。这些对象封装了与返回的结果集相关的所有功能,即遍历,寻找特定记录,计数等。 ```php query($sql); // 遍历结果集 while ($robot = $result->fetch()) { echo $robot['name']; } // 寻找第三排 $result->seek(2); $robot = $result->fetch(); // 计算结果集 echo $result->numRows(); ``` ## 绑定参数 `Phalcon\Db`也支持绑定参数。虽然使用绑定参数对性能影响最小,但建议您使用此方法,以消除代码受SQL注入攻击的可能性。支持字符串和位置占位符。绑定参数可以简单地实现如下: ```php query( $sql, [ 'Wall-E', ] ); // 与命名占位符绑定 $sql = 'INSERT INTO `robots`(name`, year) VALUES (:name, :year)'; $success = $connection->query( $sql, [ 'name' => 'Astro Boy', 'year' => 1952, ] ); ``` 使用数字占位符时,您需要将它们定义为整数,即1或2.在这种情况下,“1”或“2”被视为字符串而不是数字,因此无法成功替换占位符。任何适配器数据都会使用[PDO Quote](http://www.php.net/manual/en/pdo.quote.php)自动转义。 此函数考虑了连接字符集,因此建议在连接参数或数据库服务器配置中定义正确的字符集,因为错误的字符集在存储或检索数据时会产生不良影响。 此外,您可以将参数直接传递给`execute`或`query`方法。在这种情况下,绑定参数直接传递给PDO: ```php query( $sql, [ 1 => 'Wall-E', ] ); ``` ## 类型占位符 占位符允许您绑定参数以避免SQL注入: ```php :id:"; $robots = $this->modelsManager->executeQuery($phql, ['id' => 100]); ``` 但是,某些数据库系统在使用占位符时需要执行其他操作,例如指定绑定参数的类型: ```php modelsManager->executeQuery( $phql, ['number' => 10], Column::BIND_PARAM_INT ); ``` 您可以在参数中使用类型化占位符,而不是在`executeQuery()`中指定绑定类型: ```php modelsManager->executeQuery( $phql, ['number' => 10] ); $phql = "SELECT * FROM Store\Robots WHERE name <> {name:str}"; $robots = $this->modelsManager->executeQuery( $phql, ['name' => $name] ); ``` 如果您不需要指定它,也可以省略该类型: ```php {name}"; $robots = $this->modelsManager->executeQuery( $phql, ['name' => $name] ); ``` 类型占位符也更强大,因为我们现在可以绑定静态数组,而不必将每个元素作为占位符独立传递: ```php modelsManager->executeQuery( $phql, ['ids' => [1, 2, 3, 4]] ); ``` 可以使用以下类型: | 绑定类型 | 绑定类型常量 | 示例 | | --------- | --------------------------------- | ------------------- | | str | `Column::BIND_PARAM_STR` | `{name:str}` | | int | `Column::BIND_PARAM_INT` | `{number:int}` | | double | `Column::BIND_PARAM_DECIMAL` | `{price:double}` | | bool | `Column::BIND_PARAM_BOOL` | `{enabled:bool}` | | blob | `Column::BIND_PARAM_BLOB` | `{image:blob}` | | null | `Column::BIND_PARAM_NULL` | `{exists:null}` | | array | Array of `Column::BIND_PARAM_STR` | `{codes:array}` | | array-str | Array of `Column::BIND_PARAM_STR` | `{names:array-str}` | | array-int | Array of `Column::BIND_PARAM_INT` | `{flags:array-int}` | ## 强制绑定参数值 默认情况下,绑定参数不会在PHP用户空间中转换为指定的绑定类型,此选项允许您在将它们与PDO绑定之前制定Phalcon转换值。此问题引发的典型情况是在`LIMIT`/`OFFSET`占位符中传递一个字符串: ```php executeQuery( 'SELECT * FROM Some\Robots LIMIT {number:int}', ['number' => $number] ); ``` 这会导致以下异常: Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''100'' at line 1' in /Users/scott/demo.php:78 这是因为100是一个字符串变量。可以通过先将值转换为整数来轻松修复: ```php executeQuery( 'SELECT * FROM Some\Robots LIMIT {number:int}', ['number' => (int) $number] ); ``` 但是,此解决方案要求开发人员特别关注如何传递绑定参数及其类型。为了简化此任务并避免意外异常,您可以指示Phalcon为您执行此转换: ```php true]); ``` 根据指定的绑定类型执行以下操作: | 绑定类型 | 动作 | | ---------------------------- | -------------------------------------- | | Column::BIND_PARAM_STR | 将值转换为原生PHP字符串 | | Column::BIND_PARAM_INT | 将值转换为原生PHP整型 | | Column::BIND_PARAM_BOOL | 将值转换为原生PHP布尔值 | | Column::BIND_PARAM_DECIMAL | 将值转换为原生PHP变量 | ## 混合转换 无论值是属于数值列还是布尔类型列,数据库系统返回的值始终由PDO表示为字符串值。发生这种情况是因为某些列类型由于其大小限制而无法使用其对应的PHP原生类型表示。例如,MySQL中的`BIGINT` 可以存储在PHP中不能表示为32位整数的大整数。因此,默认情况下,PDO和ORM做出安全决定,将所有值保留为字符串。 您可以设置ORM以自动将那些被认为安全的类型转换为相应的PHP原生类型: ```php true]); ``` 这样,您可以使用严格运算符或对变量类型进行假设: ```php id) { echo $robot->name; } ``` ## 插入/更新/删除行 要插入,更新或删除行,您可以使用原始SQL或使用类提供的预设函数: ```php execute($sql); // 占位符 $sql = 'INSERT INTO `robots`(`name`, `year`) VALUES (?, ?)'; $success = $connection->execute( $sql, [ 'Astro Boy', 1952, ] ); // 动态生成必要的SQL $success = $connection->insert( 'robots', [ 'Astro Boy', 1952, ], [ 'name', 'year', ], ); // 动态生成必要的SQL(另一种语法) $success = $connection->insertAsDict( 'robots', [ 'name' => 'Astro Boy', 'year' => 1952, ] ); // 使用原始SQL语句更新数据 $sql = 'UPDATE `robots` SET `name` = 'Astro boy' WHERE `id` = 101'; $success = $connection->execute($sql); // 占位符 $sql = 'UPDATE `robots` SET `name` = ? WHERE `id` = ?'; $success = $connection->execute( $sql, [ 'Astro Boy', 101, ] ); // 动态生成必要的SQL $success = $connection->update( 'robots', [ 'name', ], [ 'New Astro Boy', ], 'id = 101' // 警告!在这种情况下,值不会被转义 ); // 动态生成必要的SQL(另一种语法) $success = $connection->updateAsDict( 'robots', [ 'name' => 'New Astro Boy', ], 'id = 101' // 警告!在这种情况下,值不会被转义 ); // 转义条件 $success = $connection->update( 'robots', [ 'name', ], [ 'New Astro Boy', ], [ 'conditions' => 'id = ?', 'bind' => [101], 'bindTypes' => [PDO::PARAM_INT], // 可选参数 ] ); $success = $connection->updateAsDict( 'robots', [ 'name' => 'New Astro Boy', ], [ 'conditions' => 'id = ?', 'bind' => [101], 'bindTypes' => [PDO::PARAM_INT], // Optional parameter ] ); // 使用原始SQL语句删除数据 $sql = 'DELETE `robots` WHERE `id` = 101'; $success = $connection->execute($sql); // 占位符 $sql = 'DELETE `robots` WHERE `id` = ?'; $success = $connection->execute($sql, [101]); // 动态生成必要的SQL $success = $connection->delete( 'robots', 'id = ?', [ 101, ] ); ``` ## 事务和嵌套事务 PDO支持使用事务处理。在事务内执行数据操作通常可以提高大多数数据库系统的性能: ```php begin(); // 执行一些SQL语句 $connection->execute('DELETE `robots` WHERE `id` = 101'); $connection->execute('DELETE `robots` WHERE `id` = 102'); $connection->execute('DELETE `robots` WHERE `id` = 103'); // 如果一切顺利,commit $connection->commit(); } catch (Exception $e) { // 发生了异常回滚事务 $connection->rollback(); } ``` 除标准事务外,`Phalcon\Db` 还为[嵌套事务](http://en.wikipedia.org/wiki/Nested_transaction)提供内置支持(如果使用的数据库系统支持它们)。当您第二次调用begin()时,会创建一个嵌套事务: ```php begin(); // 执行一些SQL语句 $connection->execute('DELETE `robots` WHERE `id` = 101'); try { // 开始嵌套事务 $connection->begin(); // 将这些SQL语句执行到嵌套事务中 $connection->execute('DELETE `robots` WHERE `id` = 102'); $connection->execute('DELETE `robots` WHERE `id` = 103'); // 创建一个保存点 $connection->commit(); } catch (Exception $e) { // 发生错误,释放嵌套事务 $connection->rollback(); } // 继续,执行更多SQL语句 $connection->execute('DELETE `robots` WHERE `id` = 104'); // 如果一切顺利,commit $connection->commit(); } catch (Exception $e) { // 发生了异常回滚事务 $connection->rollback(); } ``` ## 数据库事件 `Phalcon\Db` 能够将事件发送到EventsManager(如果它存在)。返回布尔值false时的某些事件可能会停止活动操作。支持以下事件: | 事件名称 | Triggered | Can stop operation? | | --------------------- | ---------------------------------------------------- |:-------------------:| | `afterConnect` | 成功连接到数据库系统后 | No | | `beforeQuery` | 在将SQL语句发送到数据库执行之前 | Yes | | `afterQuery` | 将SQL语句发送到数据库执行后 | No | | `beforeDisconnect` | 在关闭临时数据​​库连接之前 | No | | `beginTransaction` | 在开始事务之前 | No | | `rollbackTransaction` | 在事务被回滚之前 | No | | `commitTransaction` | 在提交事务之前 | No | 将EventsManager绑定到连接很简单,`Phalcon\Db`将使用`db`类型触发事件: ```php attach('db', $dbListener); $connection = new Connection( [ 'host' => 'localhost', 'username' => 'root', 'password' => 'secret', 'dbname' => 'invo', ] ); // 将eventsManager分配给db adapter实例 $connection->setEventsManager($eventsManager); ``` 如果您想要实现一些最后资源SQL注入器检查器,则停止SQL操作非常有用: ```php attach( 'db:beforeQuery', function (Event $event, $connection) { $sql = $connection->getSQLStatement(); // 检查SQL语句中的恶意词 if (preg_match('/DROP|ALTER/i', $sql)) { // 应用程序中不允许DROP / ALTER操作, // 这必须是SQL注入! return false; } // It's OK return true; } ); ``` ## 分析SQL语句 `Phalcon\Db`包含一个名为 `Phalcon\Db\Profiler` 的分析组件,用于分析数据库操作的性能,以便诊断性能问题并发现瓶颈。 使用`Phalcon\Db\Profiler`,数据库分析非常简单: ```php attach( 'db', function (Event $event, $connection) use ($profiler) { if ($event->getType() === 'beforeQuery') { $sql = $connection->getSQLStatement(); // 开始分析 $profiler->startProfile($sql); } if ($event->getType() === 'afterQuery') { // 停止分析 $profiler->stopProfile(); } } ); // 将事件管理器分配给连接 $connection->setEventsManager($eventsManager); $sql = 'SELECT buyer_name, quantity, product_name ' . 'FROM buyers ' . 'LEFT JOIN products ON buyers.pid = products.id'; // 执行SQL语句 $connection->query($sql); // 获取分析器中的最后一个分析SQL语句 $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`创建自己的分析类,以记录发送到数据库系统的语句的实时统计信息: ```php getSQLStatement(); } /** * 在SQL语句发送到数据库服务器之后执行 */ public function afterEndProfile(Item $profile) { echo $profile->getTotalElapsedSeconds(); } } // 创建事件管理器 $eventsManager = new EventsManager(); // 创建监听器 $dbProfiler = new DbProfiler(); // 附加监听器监听所有数据库事件 $eventsManager->attach('db', $dbProfiler); ``` ## 记录SQL语句 使用`Phalcon\Db`等高级抽象组件来访问数据库,很难理解哪些语句被发送到数据库系统。`Phalcon\Logger`与`Phalcon\Db`交互,在数据库抽象层上提供日志记录功能。 ```php attach( 'db:beforeQuery', function (Event $event, $connection) use ($logger) { $sql = $connection->getSQLStatement(); $logger->log($sql, Logger::INFO); } ); // 将eventsManager分配给db adapter实例 $connection->setEventsManager($eventsManager); // 执行一些SQL语句 $connection->insert( 'products', [ 'Hot pepper', 3.50, ], [ 'name', 'price', ] ); ``` 如上所述,文件`app/logs/db.log`将包含以下内容: ```bash [Sun, 29 Apr 12 22:35:26 -0500][DEBUG][Resource Id #77] INSERT INTO products (name, price) VALUES ('Hot pepper', 3.50) ``` ## 实现自己的Logger 您可以通过创建一个实现名为`log`的单个方法的类来为数据库查询实现自己的记录器类。该方法需要接受一个`字符串`作为第一个参数。然后,您可以将您的日志记录对象传递给`Phalcon\Db::setLogger()`,然后在执行的任何SQL语句中都会调用该方法来记录结果。 ## 描述表/视图 `Phalcon\Db` 还提供了检索有关表和视图的详细信息的方法: ```php listTables('test_db'); // 数据库中是否有表'robots'? $exists = $connection->tableExists('robots'); // 获取'robots'字段的名称,数据类型和特殊功能 $fields = $connection->describeColumns('robots'); foreach ($fields as $field) { echo 'Column Type: ', $field['Type']; } // 获取'robots'表上的索引 $indexes = $connection->describeIndexes('robots'); foreach ($indexes as $index) { print_r( $index->getColumns() ); } // 在'robots'表上获取外键 $references = $connection->describeReferences('robots'); foreach ($references as $reference) { // 打印外键列 print_r( $reference->getReferencedColumns() ); } ``` 表描述与MySQL `DESCRIBE`命令非常相似,它包含以下信息: | 字段 | 类型 | Key | Null | | ------------ | ----------- | -------------------------------------------------- | ---------------------------------- | | 字段名 | 列类型 | 列的一部分是主键还是索引?| 该列是否允许空值?| 还为每个受支持的数据库系统实现了获取有关视图的信息的方法: ```php listViews('test_db'); // 数据库中是否有'robots' 视图? $exists = $connection->viewExists('robots'); ``` ## 创建/更改/删除表 不同的数据库系统(MySQL,Postgresql等)提供了使用`CREATE`,`ALTER`或`DROP`等命令创建,更改或删除表的功能。SQL语法因使用的数据库系统而异。`Phalcon\Db`提供了一个统一的界面来更改表,而无需区分基于目标存储系统的SQL语法。 ### 创建表 以下示例显示如何创建表: ```php 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` 常量(请参阅下面的列表) | No | | `primary` | 如果列是表的主键的一部分,则为True | Yes | | `size` | 某些类型的列(如`VARCHAR`或`INTEGER`)可能具有特定大小 | Yes | | `scale` | `DECIMAL` 或 `NUMBER`列可以有一个比例来指定应存储多少小数 | Yes | | `unsigned` | `INTEGER` 列可以是有符号或无符号的。此选项不适用于其他类型的列 | Yes | | `notNull` | 列可以存储null值吗? | Yes | | `default` | 默认值(与`'notNull' => true`一起使用时)。 | Yes | | `autoIncrement` | 使用此属性列将自动填充自动增量整数。表中只有一列可以具有此属性。 | Yes | | `bind` | 其中一个`BIND_TYPE_*`常量,用于说明在保存列之前必须如何绑定列 | Yes | | `first` | 列必须按列顺序放置在第一个位置 | Yes | | `after` | 必须在指定的列之后放置 | Yes | `Phalcon\Db` 支持以下数据库列类型: * `Phalcon\Db\Column::TYPE_INTEGER` * `Phalcon\Db\Column::TYPE_DATE` * `Phalcon\Db\Column::TYPE_VARCHAR` * `Phalcon\Db\Column::TYPE_DECIMAL` * `Phalcon\Db\Column::TYPE_DATETIME` * `Phalcon\Db\Column::TYPE_CHAR` * `Phalcon\Db\Column::TYPE_TEXT` 在 `Phalcon\Db::createTable()` 中传递的关联数组可以具有可能的key: | Index | 描述 | 是否可选 | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |:--------:| | `columns` | 具有一组使用 `Phalcon\Db\Column`定义的表列的数组 | No | | `indexes` | 具有一组使用 `Phalcon\Db\Index` 定义的表索引的数组 | Yes | | `references` | 带有一组使用 `Phalcon\Db\Reference` 定义的表引用(外键)的数组 | Yes | | `options` | 包含一组表创建选项的数组。这些选项通常与生成迁移的数据库系统有关。| Yes | ### 修改表 随着应用程序的增长,您可能需要更改数据库,作为重构或添加新功能的一部分。并非所有数据库系统都允许修改现有列或在两个现有列之间添加列。`Phalcon\Db`受这些限制的限制。 ```php addColumn( 'robots', null, new Column( 'robot_type', [ 'type' => Column::TYPE_VARCHAR, 'size' => 32, 'notNull' => true, 'after' => 'name', ] ) ); // 修改现有列 $connection->modifyColumn( 'robots', null, new Column( 'name', [ 'type' => Column::TYPE_VARCHAR, 'size' => 40, 'notNull' => true, ] ) ); // 删除列 'name' $connection->dropColumn( 'robots', null, 'name' ); ``` ### 删除表 要从当前数据库中删除现有表,请使用 `dropTable`方法。要从自定义数据库中删除表,请使用第二个参数描述数据库名称。删除表的示例: ```php dropTable('robots'); // 从数据库'machines'中删除表'robots' $connection->dropTable('robots', 'machines'); ```
';