Phalcon查詢語言,簡稱PhalconQL或PHQL,是一種面向對象的高級SQL語言,容許用標準化的SQL編寫。PHQL實現了把操做語句解析爲RDBMS目標語言的解析器(C語言編寫)。php
爲了達到最佳性能,Phalcon提供了與SQLite相同的解析器,其線程安全,內存佔用極低。linux
解析器先檢查傳遞的PHQL語句的語法,而後構建中間語句,最後將其轉換爲RDBMS對應的SQL語句。web
PHQL實現了一系列功能,能夠更安全的操做數據庫。sql
爲了更好的解釋PHQL工做原理,請參考下例。有Cars
和Brands
兩個模型:數據庫
<?php use Phalcon\Mvc\Model; class Cars extends Model { public $id; public $name; public $brand_id; public $price; public $year; public $style; // 模型Cars映射sample_cars表 public function getSource() { return 'sample_cars'; } // 一輛車屬於一個品牌,但一個品牌有多輛車 public function initialize() { $this->belongsTo('brand_id', 'Brands', 'id'); } }
每輛車都屬於一個品牌,每一個品牌有多輛車:api
<?php use Phalcon\Mvc\Model; class Brands extends Model { public $id; public $name; // 模型Brands映射表'sample_brands' public function getSource() { return 'sample_brands'; } // 一個品牌有多輛車 public function initialize() { $this->hasMany('id', 'Cars', 'brand_id'); } }
實例化Phalcon\Mvc\Model\Query
類便可建立PHQL查詢:安全
<?php use Phalcon\Mvc\Model\Query; // 實例化Query $query = new Query( "SELECT * FROM Cars", $this->getDI() ); // 執行查詢,返回結果(若是有的話) $cars = $query->execute();
控制器或視圖中,使用Phalcon\Mvc\Model\Manager
能夠很容易的建立、執行PHQL查詢:app
<?php // 執行簡單查詢 $query = $this->modelsManager->createQuery("SELECT * FROM Cars"); $cars = $query->execute(); // 使用參數綁定 $query = $this->modelsManager->createQuery("SELECT * FROM Cars WHERE name = :name:"); $cars = $query->execute( [ 'name' => 'Audi', ] );
或者直接執行查詢:less
<?php // 執行簡單查詢 $cars = $this->modelsManager->executeQuery( "SELECT * FROM Cars" ); // 使用參數綁定 $cars = $this->modelsManager->executeQuery( "SELECT * FROM Cars WHERE name = :name:", [ 'name' => 'Audi', ] );
PHQL容許使用咱們熟知的SELECT語句查詢記錄,使用模型名字代替表名:函數
<?php $query = $manager->createQuery( "SELECT * FROM Cars ORDER BY Cars.name" ); $query = $manager->createQuery( "SELECT Cars.name FROM Cars ORDER BY Cars.name" );
容許帶命名空間的模型名:
<?php $phql = "SELECT * FROM Formula\Cars ORDER BY Formula\Cars.name"; $query = $manager->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語法,非標準的SQL語法也一樣支持,如LIMIT:
<?php $phql = "SELECT c.name FROM Cars AS c WHERE c.brand_id = 21 ORDER BY c.name LIMIT 100"; $query = $manager->createQuery($phql);
結果集類型根據咱們查詢字段的不一樣而不一樣,若是檢索單個完整對象,則返回Phalcon\Mvc\Model\Resultset\Simple
對象。這種結果集是一組完整的模型對象:
<?php $phql = "SELECT c.* FROM Cars AS c ORDER BY c.name"; $cars = $manager->executeQuery($phql); foreach ($cars as $car) { echo 'Name: ', $car->name, "\n"; }
下面這種方式也同樣:
<?php $cars = Cars::find( [ 'order' => 'name', ] ); foreach ($cars as $car) { echo 'Name: ', $car->name, "\n"; }
完整模型對象中的數據可以被修改,並從新保存到數據庫中,由於它們表明關聯表的完整記錄。下面這種查詢方式不會返回完整模型對象:
<?php $phql = "SELECT c.id, c.name FROM Cars AS c ORDER BY c.name"; $cars = $manager->executeQuery($phql); foreach ($cars as $car) { echo 'Name: ', $car->name, "\n"; }
咱們僅僅查詢了表中的某些字段,雖然返回的結果集仍然是Phalcon\Mvc\Model\Resultset\Simple
對象,但不能當成完整模型對象。該對象的每一個成員都是一個包含所查詢字段的標準對象。
這些不表示完整對象的值就是咱們所說的標量,PHQL容許查詢全部類型的標量:字段,函數,字面兩,表達式等:
<?php $phql = "SELECT CONCAT(c.id, ' ', c.name) AS id_name FROM Cars AS c ORDER BY c.name"; $cars = $manager->execute($phql); foreach ($cars as $car) { echo $car->id_name, "\n"; }
咱們能夠查詢完整對象或標量,也能夠同時查詢它們:
<?php $phql = "SELECT c.price*0.16 AS taxes, c.* FROM Cars AS c ORDER BY c.name"; $result = $manager->executeQuery($phql);
這種狀況下的結果集是一個Phalcon\Mvc\Model\Resultset\Complex
對象,能夠同時訪問完整對象和標量:
<?php foreach ($result as $row) { echo 'Name: ', $row->cars->name, "\n"; echo 'Price: ', $row->cars->price, "\n"; echo 'Taxes: ', $row->taxes, "\n"; }
使用PHQL能夠很容易的從多個模型請求記錄,支持大部分的JOIN方式。咱們在模型中定義關係以後,PHQL會自動添加這些條件:
<?php $phql = "SELECT Cars.name AS car_name, Brands.name AS brand_name FROM Cars JOIN Brands"; $rows = $manager->executeQuery($phql); foreach ($rows as $row) { echo $row->car_name, "\n"; echo $row->brand_name, "\n"; }
默認使用INNER JOIN,能夠指定JOIN類型:
<?php $phql = "SELECT Cars.*, Brands.* FROM Cars INNER JOIN Brands"; $rows = $manager->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 $phql = "SELECT Cars.*, Brands.* FROM Cars INNER JOIN Brands ON Brands.id = Cars.brands_id"; $rows = $manager->executeQuery($phql);
若是查詢中爲模型定義別名,則將使用別名爲結果集中的每一條記錄命名:
<?php $phql = "SELECT c.*, b.* FROM Cars c, Brands b WHERE b.id = c.brands_id"; $rows = $manager->executeQuery($phql); foreach ($rows as $row) { echo 'Car: ', $row->c->name, "\n"; echo 'Brand: ', $row->b->name, "\n"; }
若是鏈接模型與from
以後的模型具備多對多關係時,中間模型將隱式的添加到查詢中:
<?php $phql = "SELECT Artists.name, Songs.name FROM Artists JOIN Songs WHERE Artists.genre = 'Trip-Hop'"; $result = $this->modelsManager->executeQuery($phql);
上述代碼在MySQL中執行下列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 // 全部汽車的總價值 $phql = "SELECT SUM(price) AS summatory FROM Cars"; $row = $manager->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 // 簡單條件 $phql = "SELECT * FROM Cars WHERE Cars.name = 'Lamborghini Espada'"; $cars = $manager->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 $phql = "SELECT * FROM Cars WHERE Cars.name = :name:"; $cars = $manager->executeQuery( $phql, [ 'name' => 'Lamborghini Espada', ] ); $phql = "SELECT * FROM Cars WHERE Cars.name = ?0"; $cars = $manager->executeQuery( $phql, [ 0 => 'Lamborghini Espada', ] );
經過PHQL,可使用咱們很是熟悉的INSERT語句插入數據:
<?php // 插入數據,不指定字段 $phql = "INSERT INTO Cars VALUES (NULL, 'Lamborghini Espada', 7, 10000.00, 1969, 'Grand Tourer')"; $manager->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,模型中定義的全部事件和業務規則都會執行,就像咱們手動建立對象那樣。咱們爲模型Cars建立一條規則,車的價格不能低於$ 10,000:
<?php use Phalcon\Mvc\Model; use Phalcon\Mvc\Model\Message; class Cars extends Model { public function beforeCreate() { if ($this->price < 10000) { $this->appendMessage( new Message('A car cannot cost less than $ 10,000') ); return false; } } }
若是咱們在模型Cars中執行下面的INSERT
語句,操做將會失敗,由於price不知足咱們制定的規則。經過檢查插入狀態,咱們能夠打印任何內部生成的驗證消息:
<?php $phql = "INSERT INTO Cars VALUES (NULL, 'Nissan Versa', 7, 9999.00, 2015, 'Sedan')"; $result = $manager->executeQuery($phql); if ($result->success() === false) { foreach ($result->getMessages() as $message) { echo $message->getMessage(); } }
更新記錄與插入記錄很是類似,更新記錄使用UPDATE
命令。更新記錄時,將爲每條記錄執行與更新操做相關的事件。
<?php // 更新一個字段 $phql = "UPDATE Cars SET price = 15000.00 WHERE id = 101"; $manager->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 $phql = "UPDATE Cars SET price = 15000.00 WHERE id > 101"; $result = $manager->executeQuery($phql); if ($result->success() === false) { $messages = $result->getMessages(); foreach ($messages as $message) { echo $message->getMessage(); } }
上面代碼至關於:
<?php $messages = null; $process = function () use (&$messages) { $cars = Cars::find('id > 101'); foreach ($cars as $car) { $car->price = 15000; if ($car->save() === false) { $messages = $car->getMessages(); return false; } } return true; }; $success = $process();
刪除記錄時,與刪除操做相關的事件將逐一執行:
<?php // 刪除一條記錄 $phql = "DELETE FROM Cars WHERE id = 101"; $manager->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, ] );
和UPDATE
同樣,DELETE
操做也分兩步執行,要檢查刪除操做是否產生驗證消息,你能夠檢查返回的狀態:
<?php // 刪除多條記錄 $phql = "DELETE FROM Cars WHERE id > 100"; $result = $manager->executeQuery($phql); if ($result->success() === false) { $messages = $result->getMessages(); foreach ($messages as $message) { echo $message->getMessage(); } }
查詢構造器可用於建立PHQL查詢,無需編寫PHQL語句:
<?php // 獲取全部記錄 $robots = $this->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 $phql = "SELECT Robots.* FROM Robots JOIN RobotsParts p ORDER BY Robots.name LIMIT 20"; $result = $manager->executeQuery($phql);
查詢構造器更多示例:
<?php // "SELECT Robots.* FROM Robots"; $builder->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('RobotsParts', '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, 10); // "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 // 構建查詢時傳遞參數 $robots = $this->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語句中直接使用PHP字符串、數字和布爾值。若是在PHQL語句中嵌入外部數據,可能致使潛在的注入攻擊:
<?php $login = 'voltron'; $phql = "SELECT * FROM Models\Users WHERE login = '{$login}'"; $result = $manager->executeQuery($phql);
若是$login
的值爲' OR ' ' = ' ,將產生以下PHQL語句:
SELECT * FROM Models\Users WHERE login = '' OR '' = '';
不管存儲在數據庫中的login
是何值,條件老是true
。
若是字面量被禁用,在PHQL中使用PHP字面量會拋出異常,以強制開發者使用參數綁定。上面的查詢這樣寫更安全:
<?php $type = 'virtual'; $phql = "SELECT Robots.* FROM Robots WHERE Robots.type = :type:"; $result = $manager->executeQuery( $phql, [ 'type' => $type, ] );
能夠經過如下方式禁用字面量:
<?php use Phalcon\Mvc\Model; Model::setup( ['phqlLiterals' => false] );
不管字面量是否禁用,參數綁定均可以正常使用。禁用只是開發人員可以在web應用中採起的一項安全策略。
PHQL有一些保留字,若是想將保留字做爲模型名或字段名使用,則須要使用轉義分隔符[
和]
來轉義關鍵字:
<?php $phql = "SELECT * FROM [Update]"; $result = $manager->executeQuery($phql); $phql = "SELECT id, [Like] FROM Posts"; $result = $manager->executeQuery($phql);
做爲高級語言,PHQL賦予了開發者個性化定製的能力,以知足不一樣的需求。如下是PHQL語句的生命週期:
某些數據庫系統可能會提供PHQL不支持的特殊SQL擴展,這種狀況適合使用原生SQL:
<?php use Phalcon\Mvc\Model; use Phalcon\Mvc\Model\Resultset\Simple as Resultset; class Robots extends Model { public static function findByCreateInterval() { // 原生SQL $sql = "SELECT * FROM robots WHERE id > 0"; // 模型 $robot = new Robots(); // 執行查詢 return new Resultset( null, $robot, $robot->getReadConnection()->query($sql) ); } }
若是原生SQL查詢在應用中很廣泛,能夠在模型中添加通用方法:
<?php use Phalcon\Mvc\Model; use Phalcon\Mvc\Model\Resultset\Simple as Resultset; class Robots extends Model { public static function findByRawSql($conditions, $params = null) { // 原生SQL $sql = "SELECT * FROM robots WHERE {$conditions}"; // 模型 $robot = new Robots(); // 執行查詢 return new Resultset( null, $robot, $robot->getReadConnection()->query($sql), ); } }
上述findByRawSQL
能夠以下使用:
<?php $robots = Robots::findByRawSql( 'id > ?', [ 10, ] );
PHQL中的一些注意事項: