Phalcon数据库抽象层

数据库抽象层(Database Abstraction Layer)

Phalcon\DbPhalcon\Mvc\Model底层组件,由它驱动框架中的模型层。它完全由C语言编写,是一个独立的数据库高级抽象层。

与传统模型相比,该组件允许更底层的数据库操作。

数据库适配器(Database Adapters)

该组件使用适配器来封装特定的数据库操作。Phalcon使用PDO连接数据库,支持下列数据库引擎:

说明
Phalcon\Db\Adapter\Pdo\Mysql 世界上最流行的关系型数据库系统(RDBMS),作为服务器运行,支持多用户、多数据库访问
Phalcon\Db\Adapter\Pdo\Postgresql Postgresql是一个强大的开源关系数据库系统,超过15年的发展和通过验证的架构,为其赢得了正确、可靠、数据完整的良好声誉
Phalcon\Db\Adapter\Pdo\Sqlite SQLite是一个实现自包含、无服务、零配置的事务型数据库

工厂类(Factory)

使用适配器选项加载PDO:

 'localhost',
    'dbname'   => 'blog',
    'port'     => 3306,
    'username' => 'sigma',
    'password' => 'secret',
    'adapter'  => 'mysql',
];

$db = Factory::load($options);

自定义适配器(Implementing your own adapters)

创建自定义数据库适配器或扩展现有适配器,必须实现Phalcon\Db\AdapterInterface接口。

数据库语言(Database Dialects)

phalcon语言封装了每个数据库的具体操作,为适配器提供通用方法和SQL生成器。

说明
Phalcon\Db\Dialect\Mysql MySQL特定语言
Phalcon\Db\Dialect\Postgresql Postgresql特定语言
Phalcon\Db\Dialect\Sqlite SQLite特定语言

自定义语言(Implementing your own dialects)

创建自定义数据库语言或扩展现有语言,必须实现Phalcon\Db\DialectInterface接口。

连接数据库(Connection to Databases)

建立数据库连接,必须实例化适配器类,它只接收一个包含连接参数的数组。下面例子展示了如何传递必选参数和可选参数来建立数据库连接:

 '127.0.0.1',
    'username' => 'mike',
    'password' => 'sigma',
    'dbname'   => 'test_db',
];

// 可选参数
$config['persistent'] = false;

// 建立连接
$connection = new \Phalcon\Db\Adapter\Pdo\Mysql($config);
 'localhost',
    'username' => 'postgres',
    'password' => 'secret1',
    'dbname'   => 'template',
];

// 可选参数
$config['schema'] = 'public';

// 建立连接
$connection = new \Phalcon\Db\Adapter\Pdo\Postgresql($config);

设置额外的PDO选项(Setting up additional PDO options)

在建立连接时,传递options参数设置PDO:

 'localhost',
        'username' => 'root',
        'password' => 'sigma',
        'dbname'   => 'test_db',
        'options'  => [
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES UTF8',
            PDO::ATTR_CASE               => PDO::CASE_LOWER,
        ],
    ]
);

使用工厂类连接数据库(Connecting using Factory)

使用一个简单的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
set('config', $config);

$di->set(
    'db',
    function () {
        return Factory::load($this->config->database);
    }
);

上述代码返回数据库连接实例,这样做的好处是可以在不修改应用代码的情况下改变数据库连接甚至是数据库适配器。

查询记录(Finding Rows)

Phalcon\Db提供了多种查询方法。这种情况下,SQL必须遵循数据库引擎的特定语法:

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 返回对象
query($sql);

$result->setFetchMode(Phalcon\Db::FETCH_NUM);
while ($robot = $result->fetch()) {
    echo $robot[0];
}

Phalcon\Db::query()方法返回一个Phalcon\Db\Result\Pdo实例。该对象封装了与返回结果集相关的所有功能,如遍历、查找特定行、统计总行数等。

query($sql);

// 遍历结果集
while ($robot = $result->fetch()) {
    echo $robot['name'];
}

// 查找第三行
$result->seek(2);
$robot = $result->fetch();

// 计算总行数
echo $result->numRows();

参数绑定(Binding Parameters)

Phalcon\Db支持参数绑定。使用参数绑定会影响性能,但可以防止SQL注入。
支持字符串和数字占位符,参数绑定可以简单的实现如下:

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()自动转义。该方法会考虑到连接字符集,因此建议在连接选项或服务器配置中定义正确的字符集,错误的字符集会在存储或检索数据时产生不良影响。
此外,可以将参数直接传递给execute() / query()方法,这种情况下的绑定参数会直接传递给PDO:

query(
    $sql,
    [
        1 => 'Wall-E',
    ]
);

特定类型占位符(Typed Placeholders)

占位符允许执行参数绑定以避免SQL注入:

 :id:";

$robots = $this->modelsManager->executeQuery(
    $phql,
    [
        'id' => 100,
    ]
);

某些数据库系统在使用占位符时需要执行额外操作,如指定绑定参数的类型:

modelsManager->executeQuery(
    $phql,
    [
        'number' => 10,
    ],
    Column::BIND_PARAM_INT
);

可以在参数中使用类型化的占位符,而不用在executeQuery()方法中指定:

modelsManager->executeQuery(
    $phql,
    [
        'number' => 10,
    ]
);

$phql   = "SELECT * FROM Store\Robots WHERE name <> {name:str}";
$robots = $this->modelsManager->executeQuery(
    $phql,
    [
        'name' => $name,
    ]
);

如果不需要指定绑定参数类型,可以省略:

 {name}";
$robots = $this->modelsManager->executeQuery(
    $phql,
    [
        'name' => $name,
    ]
);

类型化的占位符很强大,我们可以绑定静态数组,而无需将每个参数作为占位符单独传递:

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 Column::BIND_PARAM_STR数组 {codes:array}
array-str Column::BIND_PARAM_STR数组 {names:array}
array-int Column::BIND_PARAM_INT数组 {flags:array}

绑定参数类型转换(Cast bound parameters values)

默认情况下,绑定参数不会在PHP中转换为指定类型,
如,在LIMIT / OFFSET中给占位符传递一个字符串值就会导致错误:

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'是一个字符串。可以先将值转换为整型:

executeQuery(
    "SELECT * FROM Some\Robots LIMIT {number:int}",
    [
        'number' => (int) $number,
    ]
);

要解决这个问题,需要开发者格外注意绑定参数类型及其如何传递。为了简化操作并避免异常,可以指定Phalcon自动转换:

 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实际类型:

 true]);

通过这种方式,可以使用严格运算符或对变量类型进行假设:

id) {
    echo $robot->name;
}

插入 / 更新 / 删除记录(Inserting / Updating / Deleting Rows)

可以使用原生SQL或类方法来插入、更新、删除记录:

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' // 注意,这种情况下值不会被自动转义
);

// 条件中数据的转义
$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], // 可选参数
    ]
);

// 使用原生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,
    ]
);

事务和嵌套事务(Transactions and Nested Transactions)

PDO支持事务处理,在事务内部执行数据库操作通常可以提高数据库的性能:

begin();

    // 执行SQL语句
    $connection->execute("DELETE `robots` WHERE `id` = 101");
    $connection->execute("DELETE `robots` WHERE `id` = 102");
    $connection->execute("DELETE `robots` WHERE `id` = 103");

    // 如果一切顺利,提交事务
    $connection->commit();
} catch (Exception $e) {
    // 发生异常,回滚事务
    $connection->rollback();
}

除了标准事务,Phalcon\Db内置了嵌套事务(如果数据库支持)。当再次调用begin()方法时,会创建一个嵌套事务:

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");

    // 如果一切顺利,提交事务
    $connection->commit();
} catch (Exception $e) {
    // 发生异常,回滚事务
    $connection->rollback();
}

数据库事件(Database Events)

Phalcon\Db能够将事件发送给EventManager(如果存在),某些事件返回false时,可能会终止操作。支持以下事件:

事件名称 触发时机 是否会终止操作
afterConnect 成功连接到数据库后
beforeQuery 执行SQL语句前
afterQuery 执行SQL语句后
beforeDisconnect 关闭临时数据库连接前
beginTransaction 事务开启前
rollbackTransaction 事务回滚前
commitTransaction 事务提交前

将EventsManager绑定到数据库连接很简单,Phalcon\Db将触发db类型事件:

attch('db', $dbListener);

$connection = new Connection(
    [
        'host'     => 'localhost',
        'username' => 'root',
        'password' => 'secret',
        'dbname'   => 'invo',
    ]
);

// 将eventsManager分配给数据库适配器实例
$connection->setEventsManager($eventsManager);

数据库事件中,终止SQL操作非常有用。例如,想在SQL执行前实现注入检查:

attch(
    'db:beforeQuery',
    function (Event $event, $connection) {
        $sql = $connection->getSQLStatement();

        // 检查SQL中是否有恶意关键字
        if (preg_match('/DROP|ALTER/i', $sql)) {
            // 不允许DROP / ALTERT操作
            return false;
        }

        return true;
    }
)

分析SQL语句(Profiling SQL Statements)

Phalcon\Db内置了性能分析组件Phalcon\Db\Profiler,用于分析数据库性能,以便诊断问题,发现瓶颈。使用Phalcon\Db\Profiler进行数据库分析相当容易:

attch(
    '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);

// 获取最后一个分析结果
$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创建自己的分析器,以实时统计发送到数据库的SQL语句:

getSQLStatement();
    }

    // SQL语句发送到数据库服务器之后执行
    public function afterEndProfile(Item $profile)
    {
        echo $profile->getTotalElapsedSeconds();
    }
}

// 创建事件管理器
$eventsManager = new EventsManager();

// 创建事件监听器
$dbProfiler = new DbProfiler();

// 设置监听器监听所有数据库事件
$eventsManager->attch('db', $dbProfiler);

记录SQL语句(Logging SQL Statements)

使用诸如Phalcon\Db这样的高级抽象组件来访问数据库时,很难获知哪些语句被发送到了数据库。Phalcon\Logger配合Phalcon\Db使用,可以在数据库抽象层上提供日志记录功能。

attch(
    'db:beforeQuery',
    function (Event $event, $connection) use ($logger) {
        $sql = $connection->getSQLStatement();

        $logger->log($sql, Logger::INFO);
    }
);

// 将eventsManager分配给数据库适配器实例
$connection->setEventsManager($eventsManager);

// 执行SQL语句
$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)

自定义记录器(Implementing your own Logger)

可以自定义记录器以记录数据库操作,通过创建一个实现了log()方法的类,该方法接受一个字符串作为第一个参数。可以将记录器对象传递给Phalcon\Db::setLogger(),这样在执行任何SQL语句时将调用log()方法进行记录。

获取表 / 视图详情(Describing Tables / Views)

Phalcon\Db提供了获取表、视图详情的方法:

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->getReferenceColumns()
    );
}

表详情和MySQL的describe命令返回的信息相似,包含如下信息:

Field Type Key Null
字段名称 字段类型 是否主键或索引列 是否允许为空

对于被支持的数据库系统,同样实现了获取视图详情的方法:

listViews('test_db');

// 视图'robots'是否存在于当前库中
$exists = $connection->viewExists('robots');

创建、修改、删除表(Creating / Alerting / Dropping Tables)

不同的数据库系统(MySQL,Postgresql等)通过CREATE、ALTER、DROP命令提供了用于创建、修改、删除数据表的功能。SQL语法因数据库而异。Phalcon\Db为编辑表提供了统一接口,无需区分不同数据库系统的SQL语法。

创建表(Creating Tables)

下面例子展示如何创建表:

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 字段类型,必须是PhalconDbColumn常量
primary 是否主键
size VARCHARINTEGER类型的字段定义字段长度
scale DEMICALNUMBER类型字段定义数据精度
unsigned INTEGER类型字段定义是否有符号,该选项不适用于其他类型字段
notNull 字段是否非空
default 默认值
autoIncrement 是否自增
bind BIND_TYPE_*常量定义字段在保存前如何绑定数据
first 把字段设置为表的第一个字段
after 设置字段放在指定字段之后

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()方法的关联数组可能包含下列索引:

索引 说明 是否可选
columns Phalcon\Db\Column定义的字段组成的数组
indexes Phalcon\Db\Index定义的表索引组成的数组
references Phalcon\Db\Reference定义的表引用(外键)组成的数组
options 包含表创建选项的数组,这些选项通常与数据库迁移相关

编辑表(Alerting Tables)

随着应用程序越来越庞杂,可能需要调整数据库,作为重构或添加新功能的一部分。并非所有数据库系统都允许修改列或者新增列,Phalcon\Db也受到这些限制:

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'
);

删除表(Dropping Tables)

删除表示例:

dropTable('robots');

// 从'machines'库中删除'robots'表
$connection->dropTable('robots', 'machines');

你可能感兴趣的:(phalcon,php)