Phalcon查询语言(PHQL)
最后更新于:2022-04-02 05:14:18
[TOC]
# Phalcon查询语言(PHQL)
Phalcon查询语言,PhalconQL或简称PHQL是一种高级,面向对象的SQL方言,允许使用标准化的类SQL语言编写查询。PHQL实现为解析器(用C编写),用于转换目标RDBMS的语法。
为了实现最高性能,Phalcon提供了一个使用与[SQLite](http://en.wikipedia.org/wiki/Lemon_Parser_Generator) 相同技术的解析器。该技术提供了一个内存小的解析器,内存占用非常少,同时也是线程安全的。
解析器首先检查传递PHQL语句的语法,然后构建语句的中间表示,最后将其转换为目标RDBMS的相应SQL方言。
在PHQL中,我们实现了一组功能,使您对数据库的访问更加安全:
* 绑定参数是PHQL语言的一部分,可帮助您保护代码
* PHQL仅允许每次调用执行一个SQL语句以防止注入
* PHQL忽略SQL注入中经常使用的所有SQL注释
* PHQL仅允许数据操作语句,避免错误地或在未经授权的情况下外部更改或删除表/数据库
* PHQL实现了高级抽象,允许您将表作为模型和字段处理为类属性
## 用例
为了更好地解释PHQL如何工作,请考虑以下示例。我们有两个模型`Cars`和`Brands`:
```php
belongsTo('brand_id', 'Brands', 'id');
}
}
```
每辆车都有一个品牌,所以品牌有很多车:
```php
hasMany('id', 'Cars', 'brand_id');
}
}
```
## 创建PHQL查询
只需实例化 `Phalcon\Mvc\Model\Query`类即可创建PHQL查询:
```php
getDI()
);
// 执行返回结果的查询(如果有)
$cars = $query->execute();
```
从控制器或视图中,使用注入的`models manager`(`Phalcon\Mvc\Model\Manager`)可以轻松创建/执行它们:
```php
modelsManager->createQuery('SELECT * FROM Cars');
$cars = $query->execute();
// 带有绑定参数
$query = $this->modelsManager->createQuery('SELECT * FROM Cars WHERE name = :name:');
$cars = $query->execute(
[
'name' => 'Audi',
]
);
```
或者只是执行它:
```php
modelsManager->executeQuery(
'SELECT * FROM Cars'
);
// 带有绑定参数
$cars = $this->modelsManager->executeQuery(
'SELECT * FROM Cars WHERE name = :name:',
[
'name' => 'Audi',
]
);
```
## 选取记录
作为熟悉的SQL,PHQL允许使用我们所知道的SELECT语句查询记录,除了不使用指定表,我们使用模型类:
```php
createQuery(
'SELECT * FROM Cars ORDER BY Cars.name'
);
$query = $manager->createQuery(
'SELECT Cars.name FROM Cars ORDER BY Cars.name'
);
```
命名空间中的类也是允许的:
```php
createQuery($phql);
$phql = 'SELECT Formula\Cars.name FROM Formula\Cars ORDER BY Formula\Cars.name';
$query = $manager->createQuery($phql);
$phql = 'SELECT c.name FROM Formula\Cars c ORDER BY c.name';
$query = $manager->createQuery($phql);
```
PHQL支持大多数SQL标准,甚至是非标准指令,例如LIMIT:
```php
createQuery($phql);
```
### 结果类型
根据我们查询的列类型,结果类型会有所不同。如果检索单个整个对象,则返回的对象是
`Phalcon\Mvc\Model\Resultset\Simple`。这种结果集是一组完整的模型对象:
```php
executeQuery($phql);
foreach ($cars as $car) {
echo 'Name: ', $car->name, "\n";
}
```
这完全相同:
```php
'name'
]
);
foreach ($cars as $car) {
echo 'Name: ', $car->name, "\n";
}
```
可以修改完整对象并将其重新保存在数据库中,因为它们代表关联表的完整记录。还有其他类型的查询不返回完整对象,例如:
```php
executeQuery($phql);
foreach ($cars as $car) {
echo 'Name: ', $car->name, "\n";
}
```
我们只请求表中的某些字段,因此不能将它们视为整个对象,因此返回的对象仍然是
`Phalcon\Mvc\Model\Resultset\Simple`类型的结果集。但是,每个元素都是一个标准对象,只包含请求的两列。
这些不代表完整对象的值就是我们所说的标量。PHQL允许您查询所有类型的标量:字段,函数,文字,表达式等:
```php
executeQuery($phql);
foreach ($cars as $car) {
echo $car->id_name, "\n";
}
```
由于我们可以查询完整的对象或标量,我们也可以同时查询两个:
```php
executeQuery($phql);
```
在这种情况下的结果是对象 `Phalcon\Mvc\Model\Resultset\Complex`。这允许一次访问完整对象和标量:
```php
cars->name, "\n";
echo 'Price: ', $row->cars->price, "\n";
echo 'Taxes: ', $row->taxes, "\n";
}
```
标量映射为每个“行”的属性,而完整对象则映射为具有其相关模型名称的属性。
### 连接
使用PHQL从多个模型中请求记录很容易。支持大多数种类的连接。当我们在模型中定义关系时,PHQL会自动添加这些条件:
```php
executeQuery($phql);
foreach ($rows as $row) {
echo $row->car_name, "\n";
echo $row->brand_name, "\n";
}
```
默认情况下,假定为INNER JOIN。您可以在查询中指定JOIN的类型:
```php
executeQuery($phql);
$phql = 'SELECT Cars.*, Brands.* FROM Cars LEFT JOIN Brands';
$rows = $manager->executeQuery($phql);
$phql = 'SELECT Cars.*, Brands.* FROM Cars LEFT OUTER JOIN Brands';
$rows = $manager->executeQuery($phql);
$phql = 'SELECT Cars.*, Brands.* FROM Cars CROSS JOIN Brands';
$rows = $manager->executeQuery($phql);
```
也可以手动设置JOIN的条件:
```php
executeQuery($phql);
```
此外,可以使用FROM子句中的多个表创建联接:
```php
executeQuery($phql);
foreach ($rows as $row) {
echo 'Car: ', $row->cars->name, "\n";
echo 'Brand: ', $row->brands->name, "\n";
}
```
如果使用别名重命名查询中的模型,那么将使用这些别名来命名结果的每一行中的属性:
```php
executeQuery($phql);
foreach ($rows as $row) {
echo 'Car: ', $row->c->name, "\n";
echo 'Brand: ', $row->b->name, "\n";
}
```
当联接模型与from模型具有多对多关系时,中间模型将隐式添加到生成的查询中:
```php
modelsManager->executeQuery($phql);
```
此代码在MySQL中执行以下SQL:
```sql
SELECT `artists`.`name`, `songs`.`name` FROM `artists`
INNER JOIN `albums` ON `albums`.`artists_id` = `artists`.`id`
INNER JOIN `songs` ON `albums`.`songs_id` = `songs`.`id`
WHERE `artists`.`genre` = 'Trip-Hop'
```
### 聚合
以下示例显示如何在PHQL中使用聚合:
```php
executeQuery($phql)->getFirst();
echo $row['summatory'];
// 每个品牌有多少辆汽车?
$phql = 'SELECT Cars.brand_id, COUNT(*) FROM Cars GROUP BY Cars.brand_id';
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
echo $row->brand_id, ' ', $row['1'], "\n";
}
// 每个品牌有多少辆汽车?
$phql = 'SELECT Brands.name, COUNT(*) FROM Cars JOIN Brands GROUP BY 1';
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
echo $row->name, ' ', $row['1'], "\n";
}
$phql = 'SELECT MAX(price) AS maximum, MIN(price) AS minimum FROM Cars';
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
echo $row['maximum'], ' ', $row['minimum'], "\n";
}
// 统计不同的二手品牌
$phql = 'SELECT COUNT(DISTINCT brand_id) AS brandId FROM Cars';
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
echo $row->brandId, "\n";
}
```
### 条件
条件允许我们过滤我们想要查询的记录集。WHERE子句允许这样做:
```php
executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.price > 10000';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE TRIM(Cars.name) = "Audi R8"';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.name LIKE "Ferrari%"';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.name NOT LIKE "Ferrari%"';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.price IS NULL';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.id IN (120, 121, 122)';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.id NOT IN (430, 431)';
$cars = $manager->executeQuery($phql);
$phql = 'SELECT * FROM Cars WHERE Cars.id BETWEEN 1 AND 100';
$cars = $manager->executeQuery($phql);
```
此外,作为PHQL的一部分,准备好的参数会自动转义输入数据,从而带来更多安全性:
```php
executeQuery(
$phql,
[
'name' => 'Lamborghini Espada'
]
);
$phql = 'SELECT * FROM Cars WHERE Cars.name = ?0';
$cars = $manager->executeQuery(
$phql,
[
0 => 'Lamborghini Espada'
]
);
```
## 插入数据
使用PHQL,可以使用熟悉的INSERT语句插入数据:
```php
executeQuery($phql);
// 指定要插入的列
$phql = 'INSERT INTO Cars (name, brand_id, year, style) '
. 'VALUES ("Lamborghini Espada", 7, 1969, "Grand Tourer")';
$manager->executeQuery($phql);
// 使用占位符插入
$phql = 'INSERT INTO Cars (name, brand_id, year, style) '
. 'VALUES (:name:, :brand_id:, :year:, :style)';
$manager->executeQuery(
$phql,
[
'name' => 'Lamborghini Espada',
'brand_id' => 7,
'year' => 1969,
'style' => 'Grand Tourer',
]
);
```
Phalcon不仅将PHQL语句转换为SQL。执行模型中定义的所有事件和业务规则,就像我们手动创建单个对象一样。让我们在模型车上添加一个商业规则。一辆车不能低于$10,000:
```php
price < 10000) {
$this->appendMessage(
new Message('A car cannot cost less than $ 10,000')
);
return false;
}
}
}
```
如果我们在模型Cars中进行了以下 `INSERT` ,则操作将不会成功,因为价格不符合我们实施的业务规则。通过检查插入的状态,我们可以打印内部生成的任何验证消息:
```php
executeQuery($phql);
if ($result->success() === false) {
foreach ($result->getMessages() as $message) {
echo $message->getMessage();
}
}
```
## 更新数据
更新行与插入行非常相似。您可能知道,更新记录的指令是更新。更新记录时,将对每行执行与更新操作相关的事件。
```php
executeQuery($phql);
// 更新多个列
$phql = 'UPDATE Cars SET price = 15000.00, type = "Sedan" WHERE id = 101';
$manager->executeQuery($phql);
// 更新多行
$phql = 'UPDATE Cars SET price = 7000.00, type = "Sedan" WHERE brands_id > 5';
$manager->executeQuery($phql);
// 使用占位符
$phql = 'UPDATE Cars SET price = ?0, type = ?1 WHERE brands_id > ?2';
$manager->executeQuery(
$phql,
[
0 => 7000.00,
1 => 'Sedan',
2 => 5,
]
);
```
`UPDATE` 语句分两个阶段执行更新:
* 首先,如果 `UPDATE` 有一个 `WHERE` 子句,它将检索符合这些条件的所有对象,
* 其次,基于查询的对象,它更新/更改将它们存储到关系数据库的请求属性
这种操作方式允许事件,虚拟外键和验证参与更新过程。总之,以下代码:
```php
101';
$result = $manager->executeQuery($phql);
if ($result->success() === false) {
$messages = $result->getMessages();
foreach ($messages as $message) {
echo $message->getMessage();
}
}
```
有点相当于:
```php
101');
foreach ($cars as $car) {
$car->price = 15000;
if ($car->save() === false) {
$messages = $car->getMessages();
return false;
}
}
return true;
};
$success = $process();
```
## 删除数据
删除记录时,将对每行执行与删除操作相关的事件:
```php
executeQuery($phql);
// 删除多行
$phql = 'DELETE FROM Cars WHERE id > 100';
$manager->executeQuery($phql);
// 使用占位符
$phql = 'DELETE FROM Cars WHERE id BETWEEN :initial: AND :final:';
$manager->executeQuery(
$phql,
[
'initial' => 1,
'final' => 100,
]
);
```
`DELETE` 操作也分两个阶段执行,如 `UPDATE`。要检查删除是否产生任何验证消息,您应该检查返回的状态代码:
```php
100';
$result = $manager->executeQuery($phql);
if ($result->success() === false) {
$messages = $result->getMessages();
foreach ($messages as $message) {
echo $message->getMessage();
}
}
```
## 使用查询生成器创建查询
可以使用构建器创建PHQL查询,而无需编写PHQL语句,还提供IDE工具:
```php
modelsManager->createBuilder()
->from('Robots')
->join('RobotsParts')
->orderBy('Robots.name')
->getQuery()
->execute();
// 获取第一行
$robots = $this->modelsManager->createBuilder()
->from('Robots')
->join('RobotsParts')
->orderBy('Robots.name')
->getQuery()
->getSingleResult();
```
这与以下相同:
```php
executeQuery($phql);
```
更多生成器的例子:
```php
from('Robots');
// 'SELECT Robots.*, RobotsParts.* FROM Robots, RobotsParts';
$builder->from(
[
'Robots',
'RobotsParts',
]
);
// 'SELECT * FROM Robots';
$phql = $builder->columns('*')
->from('Robots');
// 'SELECT id FROM Robots';
$builder->columns('id')
->from('Robots');
// 'SELECT id, name FROM Robots';
$builder->columns(['id', 'name'])
->from('Robots');
// 'SELECT Robots.* FROM Robots WHERE Robots.name = 'Voltron'';
$builder->from('Robots')
->where("Robots.name = 'Voltron'");
// 'SELECT Robots.* FROM Robots WHERE Robots.id = 100';
$builder->from('Robots')
->where(100);
// 'SELECT Robots.* FROM Robots WHERE Robots.type = 'virtual' AND Robots.id > 50';
$builder->from('Robots')
->where("type = 'virtual'")
->andWhere('id > 50');
// 'SELECT Robots.* FROM Robots WHERE Robots.type = 'virtual' OR Robots.id > 50';
$builder->from('Robots')
->where("type = 'virtual'")
->orWhere('id > 50');
// 'SELECT Robots.* FROM Robots GROUP BY Robots.name';
$builder->from('Robots')
->groupBy('Robots.name');
// 'SELECT Robots.* FROM Robots GROUP BY Robots.name, Robots.id';
$builder->from('Robots')
->groupBy(['Robots.name', 'Robots.id']);
// 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name';
$builder->columns(['Robots.name', 'SUM(Robots.price)'])
->from('Robots')
->groupBy('Robots.name');
// 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name HAVING SUM(Robots.price) > 1000';
$builder->columns(['Robots.name', 'SUM(Robots.price)'])
->from('Robots')
->groupBy('Robots.name')
->having('SUM(Robots.price) > 1000');
// 'SELECT Robots.* FROM Robots JOIN RobotsParts';
$builder->from('Robots')
->join('RobotsParts');
// 'SELECT Robots.* FROM Robots JOIN RobotsParts AS p';
$builder->from('Robots')
->join('RobotsParts', null, 'p');
// 'SELECT Robots.* FROM Robots JOIN RobotsParts ON Robots.id = RobotsParts.robots_id AS p';
$builder->from('Robots')
->join('RobotsParts', 'Robots.id = RobotsParts.robots_id', 'p');
// 'SELECT Robots.* FROM Robots
// JOIN RobotsParts ON Robots.id = RobotsParts.robots_id AS p
// JOIN Parts ON Parts.id = RobotsParts.parts_id AS t';
$builder->from('Robots')
->join('RobotsParts', 'Robots.id = RobotsParts.robots_id', 'p')
->join('Parts', 'Parts.id = RobotsParts.parts_id', 't');
// 'SELECT r.* FROM Robots AS r';
$builder->addFrom('Robots', 'r');
// 'SELECT Robots.*, p.* FROM Robots, Parts AS p';
$builder->from('Robots')
->addFrom('Parts', 'p');
// 'SELECT r.*, p.* FROM Robots AS r, Parts AS p';
$builder->from(['r' => 'Robots'])
->addFrom('Parts', 'p');
// 'SELECT r.*, p.* FROM Robots AS r, Parts AS p';
$builder->from(['r' => 'Robots', 'p' => 'Parts']);
// 'SELECT Robots.* FROM Robots LIMIT 10';
$builder->from('Robots')
->limit(10);
// 'SELECT Robots.* FROM Robots LIMIT 10 OFFSET 5';
$builder->from('Robots')
->limit(10, 5);
// 'SELECT Robots.* FROM Robots WHERE id BETWEEN 1 AND 100';
$builder->from('Robots')
->betweenWhere('id', 1, 100);
// 'SELECT Robots.* FROM Robots WHERE id IN (1, 2, 3)';
$builder->from('Robots')
->inWhere('id', [1, 2, 3]);
// 'SELECT Robots.* FROM Robots WHERE id NOT IN (1, 2, 3)';
$builder->from('Robots')
->notInWhere('id', [1, 2, 3]);
// 'SELECT Robots.* FROM Robots WHERE name LIKE '%Art%';
$builder->from('Robots')
->where('name LIKE :name:', ['name' => '%' . $name . '%']);
// 'SELECT r.* FROM Store\Robots WHERE r.name LIKE '%Art%';
$builder->from(['r' => 'Store\Robots'])
->where('r.name LIKE :name:', ['name' => '%' . $name . '%']);
```
### 绑定参数
查询构建器中的绑定参数可以在执行时一次构造或过去查询时设置:
```php
modelsManager->createBuilder()
->from('Robots')
->where('name = :name:', ['name' => $name])
->andWhere('type = :type:', ['type' => $type])
->getQuery()
->execute();
// 在查询执行中传递参数
$robots = $this->modelsManager->createBuilder()
->from('Robots')
->where('name = :name:')
->andWhere('type = :type:')
->getQuery()
->execute(['name' => $name, 'type' => $type]);
```
## 在PHQL中禁止文字
可以在PHQL中禁用文字,这意味着不允许直接在PHQL字符串中使用字符串,数字和布尔值。如果创建了PHQL语句,并在其上嵌入外部数据,则可以打开应用程序以进行潜在的SQL注入:
```php
executeQuery($phql);
```
如果 `$login` 更改为 `' OR '' = '`,则生成的PHQL为:
```sql
SELECT * FROM Models\Users WHERE login = '' OR '' = ''
```
无论数据库中存储的是什么,都始终`true`。
如果不允许文字,则可以将字符串用作PHQL语句的一部分,因此将抛出异常,迫使开发人员使用绑定参数。可以用以下安全方式编写相同的查询:
```php
executeQuery(
$phql,
[
'type' => $type,
]
);
```
您可以通过以下方式禁用文字:
```php
false
]
);
```
即使允许或不允许文字,也可以使用绑定参数。禁止它们只是开发人员可以在Web应用程序中采取的另一个安全决策。
## 转义保留字
PHQL有一些保留字,如果要将它们中的任何一个用作属性或模型名称,则需要使用跨数据库转义分隔符 `[` 和 `]`来转义这些字:
```php
executeQuery($phql);
$phql = 'SELECT id, [Like] FROM Posts';
$result = $manager->executeQuery($phql);
```
根据当前运行应用程序的数据库系统,分隔符会动态转换为有效的分隔符。
## PHQL生命周期
作为一种高级语言,PHQL使开发人员能够个性化和定制不同方面,以满足他们的需求。以下是每个执行的PHQL语句的生命周期:
* 解析PHQL并将其转换为中间表示(IR),它独立于数据库系统实现的SQL
* 根据与模型关联的数据库系统将IR转换为有效的SQL
* PHQL语句被解析一次并缓存在内存中。进一步执行相同的语句会导致执行速度稍快
## 使用原始SQL
数据库系统可以提供PHQL不支持的特定SQL扩展,在这种情况下,原始SQL可能是合适的:
```php
0';
// 基础模型
$robot = new Robots();
// 执行查询
return new Resultset(
null,
$robot,
$robot->getReadConnection()->query($sql)
);
}
}
```
如果原始SQL查询在您的应用程序中很常见,则可以在模型中添加通用方法:
```php
getReadConnection()->query($sql, $params)
);
}
}
```
上面的`findByRawSql`可以用如下:
```php
?',
[
10
]
);
```
## 故障排除
使用PHQL时要记住的一些事项:
* 类是区分大小写的,如果没有使用与创建时相同的名称定义类,这可能会导致在具有区分大小写的文件系统(如Linux)的操作系统中出现意外行为。
* 必须在连接中定义正确的字符集才能成功绑定参数。
* 别名类不会被完整的命名空间类替换,因为这只发生在PHP代码中,而不是在字符串内。
* 如果启用了列重命名,请避免使用与要重命名的列具有相同名称的列别名,这可能会使查询解析程序混淆。
';