Phalcon查询语言(PHQL)

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

[TOC] # Phalcon查询语言(PHQL) Phalcon查询语言,PhalconQL或简称PHQ​​L是一种高级,面向对象的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代码中,而不是在字符串内。 * 如果启用了列重命名,请避免使用与要重命名的列具有相同名称的列别名,这可能会使查询解析程序混淆。
';