Phalcon Query Language (PHQL) ============================= 译者注:学过JAVA,略懂Hibernate的人应该会知道,hibernate是javaee中一个非常流程的ORM软件,它其中生成的中间语句就叫做HQL。 Phalcon查询语言,也可以叫PhalconQL或PHQL,是一个高层次的,允许你使用一种类SQL语言的方式的一种SQL方言。PHQL是一个用C语言编写的SQL语法分析器。 为了达到尽可能高的性能,Phalcon提供了一个分析器,使用了和 SQLite_ 相同的技术。该技术提供了一个小型的内存分析器,具有非常低的内存占用,同时也是线程安全的。 解析器首先检查传递过来的PHQL语句,然后把它们转化成一种中间性的语句,最后再将其转换为相应的RDBMS所需要的SQL方言。 在PHQL中,我们已经实现了一系列的功能,以保证你在访问数据库时是安全的: * Bound parameters are part of the PHQL language helping you to secure your code * PHQL only allows one SQL statement to be executed per call preventing injections * PHQL ignores all SQL comments which are often used in SQL injections * PHQL only allows data manipulation statements, avoiding altering or dropping tables/databases by mistake or externally without authorization * PHQL implements a high level abstraction allowing you handling models as tables and class attributes as fields 使用示例 ------------- 为了更好的展示PHQL是如何工作的,我们将使用模型 “Cars” 和 “Brands”: .. code-block:: php belongsTo('brand_id', 'Brands', 'id'); } } 每个Car只有一个Brand,一个Brand有多个Cars: .. code-block:: php hasMany('id', 'Cars', 'brand_id'); } } Creating PHQL Queries --------------------- PHQL查询可以通过实例化 :doc:`Phalcon\\Mvc\\Model\\Query <../api/Phalcon_Mvc_Model_Query>` 来创建: .. code-block:: php setDI($di); // Execute the query returning a result if any $robots = $query->execute(); 在控制器或视图文件中,它可以使用服务容器中的一个注入服务 :doc:`models manager <../api/Phalcon_Mvc_Model_Manager>` 来轻松的实现create/execute .. code-block:: php modelsManager->createQuery("SELECT * FROM Cars"); $robots = $query->execute(); 或者像下面这样: .. code-block:: php modelsManager->executeQuery("SELECT * FROM Cars"); Selecting Records ----------------- 作为大家所熟悉的SQL,PHQL允许你在查询中使用SELECT语句,只是需要使用模型类的名称来替代数据表名: .. code-block:: php createQuery("SELECT * FROM Cars ORDER BY Cars.name"); $query = $manager->createQuery("SELECT Cars.name FROM Cars ORDER BY Cars.name"); 带有命名空间的模型类同样可以: .. code-block:: 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); Phalcon支持大部分的SQL标准,甚至是非标准指令,如,LIMIT: .. code-block:: php createQuery($phql); Results Types ^^^^^^^^^^^^^ 根据我们查询列的类型,返回的结果类型会稍有不同。如果你检索一个整体对象,它将返回 :doc:`Phalcon\\Mvc\\Model\\Resultset\\Simple <../api/Phalcon_Mvc_Model_Resultset_Simple>` 的对象实例。这种结果集是一组完整的模型对象: .. code-block:: php executeQuery($phql); foreach ($cars as $car) { echo "Name: ", $car->name, "\n"; } 这是完全一样的: .. code-block:: php "name")); foreach ($cars as $car) { echo "Name: ", $car->name, "\n"; } 完整的对象可以被修改和重新保存到数据库,因为他们代表着关联数据表的一个完整记录。有一些其他类型的查询不返回完整的对象,例如: .. code-block:: php executeQuery($phql); foreach ($cars as $car) { echo "Name: ", $car->name, "\n"; } 我们只查询了数据表中的某些字段,因此,这不能算是一个完整的对象。在这种情况下,也返回 :doc:`Phalcon\\Mvc\\Model\\Resultset\\Simple <../api/Phalcon_Mvc_Model_Resultset_Simple>` 的实例对象。然而,这个对象只包含两列属性值。 这些值不代表完整的对象,我们称他们为标量。PHQL允许你查询各种类型的标量,如fields,functions,literals, expressions等 .. code-block:: php executeQuery($phql); foreach ($cars as $car) { echo $car->id_name, "\n"; } 我们既可以只查询完整的对象或标量,也可以同时查询他们: .. code-block:: php executeQuery($phql); 在这种情况下,返回的是 :doc:`Phalcon\\Mvc\\Model\\Resultset\\Complex <../api/Phalcon_Mvc_Model_Resultset_Complex>` 的实例对象,这允许同时访问完整对象和标量: .. code-block:: php cars->name, "\n"; echo "Price: ", $row->cars->price, "\n"; echo "Taxes: ", $row->taxes, "\n"; } 标量的属性值映射到"row"上,而完整的对象则是被映射到与它相关的模型对象上。 Joins ^^^^^ 使用PHQL可以很方便的通过多个模型来获取数据,Phalcon支持大多数类型的Joins。我们在模型中定义的关系,在使用PHQL时会自动的添加到条件上: .. code-block:: php executeQuery($phql); foreach ($rows as $row) { echo $row->car_name, "\n"; echo $row->brand_name, "\n"; } 默认情况下,将使用INNER JOIN的方式,你也可以在查询中使用其他类型的JOIN: .. code-block:: php executeQuery($phql); $phql = "SELECT CCars.*, 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中手工设置SQL条件: .. code-block:: php executeQuery($phql); 同时,Joins还可以在使用以下方式: .. code-block:: php executeQuery($phql); foreach ($rows as $row) { echo "Car: ", $row->cars->name, "\n"; echo "Brand: ", $row->brands->name, "\n"; } 如果在查询时使用了别名,获取属性值将使用别名的名称做为row的名称: .. code-block:: php executeQuery($phql); foreach ($rows as $row) { echo "Car: ", $row->c->name, "\n"; echo "Brand: ", $row->b->name, "\n"; } Aggregations ^^^^^^^^^^^^ 下面的示例将展示如何在PHQL中使用聚合: .. code-block:: php executeQuery($phql)->getFirst(); echo $row['summatory']; // How many cars are by each brand? $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"; } // How many cars are by each brand? $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"; } // Count distinct used brands $phql = "SELECT COUNT(DISTINCT brand_id) AS brandId FROM Cars"; $rows = $manager->executeQuery($phql); foreach ($rows as $row) { echo $row->brandId, "\n"; } 条件(Conditions) ^^^^^^^^^^^^^^^^^^^^^ 条件的作用是允许你过滤查询内容,WHERE条件可以这样使用: .. code-block:: 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的另一特点,prepared参数自动转义用户输入数据,下面将介绍的是与安全相关: .. code-block:: php executeQuery($phql, array("name" => 'Lamborghini Espada')); $phql = "SELECT * FROM Cars WHERE Cars.name = ?0"; $cars = $manager->executeQuery($phql, array(0 => 'Lamborghini Espada')); Inserting Data -------------- PHQL是使用熟悉的INSERT语句插入数据: .. code-block:: php executeQuery($phql); // Specifyng columns to insert $phql = "INSERT INTO Cars (name, brand_id, year, style) " . "VALUES ('Lamborghini Espada', 7, 1969, 'Grand Tourer')"; $manager->executeQuery($phql); // Inserting using placeholders $phql = "INSERT INTO Cars (name, brand_id, year, style) " . "VALUES (:name:, :brand_id:, :year:, :style)"; $manager->executeQuery($sql, array( 'name' => 'Lamborghini Espada', 'brand_id' => 7, 'year' => 1969, 'style' => 'Grand Tourer', ) ); Phalcon中不只是用PHQL语句转换为SQL语句的。如果我们是手工创建模型对象,里面的所有事件及定义的业务规则都会被执行。现在,我们添加一个模型Cars的业务规则,让car的价格不低于$ 10,000: .. code-block:: php price < 10000) { $this->appendMessage(new Message("A car cannot cost less than $ 10,000")); return false; } } } 如果我们在模型中使用以下的INSERT语句,INSERT操作将不成功,因为价格不符合定义的规则: .. code-block:: php executeQuery($phql); if ($result->success() == false) { foreach ($result->getMessages() as $message) { echo $message->getMessage(); } } 更新数据(Updating Data) ----------------------------- 更新一行记录和插入一行记录非常相似。正如你所知道的,更新数据记录的指令是UPDATE。当更新一行记录时,对应的模型事件将被执行。 .. code-block:: php executeQuery($phql); // Updating multiples columns $phql = "UPDATE Cars SET price = 15000.00, type = 'Sedan' WHERE id = 101"; $manager->executeQuery($phql); // Updating multiples rows $phql = "UPDATE Cars SET price = 7000.00, type = 'Sedan' WHERE brands_id > 5"; $manager->executeQuery($phql); // Using placeholders $phql = "UPDATE Cars SET price = ?0, type = ?1 WHERE brands_id > ?2"; $manager->executeQuery( $phql, array( 0 => 7000.00, 1 => 'Sedan', 2 => 5 ) ); 删除数据(Deleting Data) ------------------------ 当删除数据时,对应的模型事件将被执行: .. code-block:: php executeQuery($phql); // Deleting multiple rows $phql = "DELETE FROM Cars WHERE id > 100"; $manager->executeQuery($phql); // Using placeholders $phql = "DELETE FROM Cars WHERE id BETWEEN :initial: AND :final:"; $manager->executeQuery( $phql, array( 'initial' => 1, 'final' => '100 ) ); 使用Query Builder创建queries(Creating queries using the Query Builder) ------------------------------------------------------------------------- Query Builder可以创建一个PHQL query,而不需要编写PHQL语句了,同时Query Builder对IDE工具是友好的(可以自动提示): .. code-block:: php createBuilder() >join('RobotsParts'); ->limit(20); ->order('Robots.name') ->getQuery() ->execute(); 与下面是相同的: .. code-block:: php executeQuery($phql); 更多关于query builder的示例: .. code-block:: php from('Robots') // 'SELECT Robots.* FROM Robots' // 'SELECT Robots.*, RobotsParts.* FROM Robots, RobotsParts' $builder->from(array('Robots', 'RobotsParts')) // 'SELECT * FROM Robots' $phql = $builder->columns('*') ->from('Robots') // 'SELECT id, name FROM Robots' $builder->columns(array('id', 'name')) ->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 GROUP BY Robots.name' $builder->from('Robots') ->groupBy('Robots.name') // 'SELECT Robots.* FROM Robots GROUP BY Robots.name, Robots.id' $builder->from('Robots') ->groupBy(array('Robots.name', 'Robots.id')) // 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name' $builder->columns(array('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(array('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(array('r' => 'Robots')) ->addFrom('Parts', 'p') // 'SELECT r.*, p.* FROM Robots AS r, Parts AS p'); $builder->from(array('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) .. _SQLite: http://en.wikipedia.org/wiki/Lemon_Parser_Generator