Язык запросов Phalcon, PhalconQL или просто PHQL - это высокоуровневый объектно-ориентированный SQL-диалект, который позволяет писать запросы с использованием стандартизованного SQL-подобного языка. PHQL реализуется как синтаксический анализатор (написанный на языке C), который преобразует синтаксис в синтаксис целевого СУБД.

Для достижения максимальной производительности Phalcon предоставляет парсер, который использует ту же технологию, что и SQLite. Эта технология обеспечивает небольшой парсер в памяти с очень низким объемом памяти, который также является потокобезопасным.

Парсер сначала проверяет синтаксис инструкции прохода PHQL, затем создает промежуточное представление оператора и, наконец, преобразует его в соответствующий диалект SQL целевой RDBMS.

В PHQL мы внедрили набор функций, чтобы сделать ваш доступ к базам данных более безопасным:

  • Связанные параметры являются частью языка PHQL, помогая вам защитить ваш код
  • PHQL позволяет выполнять только одну инструкцию SQL за каждый вызов, предотвращающий инъекции
  • PHQL игнорирует все комментарии SQL, которые часто используются в SQL-инъекциях
  • PHQL допускает только операции манипулирования данными, избегая изменения или удаления таблиц / баз данных по ошибке или извне без разрешения
  • PHQL реализует абстракцию высокого уровня, позволяющую обрабатывать таблицы как модели и поля как атрибуты класса

Пример использования

Чтобы лучше объяснить, как работает PHQL, рассмотрим следующий пример. У нас есть две модели: Invoicesи Customers:

<?php

namespace MyApp\Models;

use MyApp\Models\Customers;
use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public $inv_cst_id;

    public $inv_id;

    public $inv_status_flag;

    public $inv_title;

    public $inv_created_at;

    public function initialize()
    {
        $this->setSource('co_invoices');

        $this->belongsTo(
            'inv_cst_id', 
            Customers::class, 
            'cst_id'
        );
    }
}

 И у каждого клиента есть один или несколько счетов-фактур.:

<?php

namespace MyApp\Models;

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model;

class Customers extends Model
{
    public $cst_id;

    public $cst_active_flag;
    
    public $cst_name_last;
    
    public $cst_name_first;
    
    public $cst_created_at;

    public function initialize()
    {
        $this->setSource('co_customers');

        $this->hasMany(
            'cst_id', 
            Invoices::class, 
            'inv_cst_id'
        );
    }
}

Создание запросов PHQL

Запросы PHQL могут быть созданы только путем создания экземпляра класса Phalcon\Mvc\Model\Query:

<?php

use Phalcon\Mvc\Model\Query;

$container = Di::getDefault();
$query     = new Query(
    'SELECT * FROM Invoices',
    $container
);

$invoices = $query->execute();

Для запроса Phalcon\Mvc\Model\Query требуется, чтобы вторым параметром конструктора был контейнер DI. При вызове вышеуказанного кода из контроллера или любого класса, расширяющего Phalcon\Di\Injectable, вы можете использовать:

<?php

use Phalcon\Di;
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Query;
use Phalcon\Mvc\View;

/**
 * @property Di   $di
 * @property View $view
 */
class Invoices extends Controller
{
    public function listAction()
    {
        $query = new Query(
            'SELECT * FROM Invoices',
            $this->di
        );

        $invoices = $query->execute();
        
        $this->view->setVar('invoices', $invoices);
    }
}

Менеджер Моделей

Мы также можем использовать Phalcon\Mvc\Model\Manager, который вводится в контейнер DI:

<?php

use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Mvc\View;

/**
 * @property Manager $modelsManager
 * @property View    $view
 */
class Invoices extends Controller
{
    public function listAction()
    {
        $query = $this
            ->modelsManager
            ->createQuery(
                'SELECT * FROM Invoices'
            )
        ;

        $invoices = $query->execute();
        
        $this->view->setVar('invoices', $invoices);
    }
}

 Использование связанных параметров:

<?php

use Phalcon\Http\Request;
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Mvc\View;

/**
 * @property Manager $modelsManager
 * @property Request $request
 * @property View    $view
 */
class Invoices extends Controller
{
    public function viewAction()
    {
        $invoiceId = $this->request->getQuery('id', 'int');
        $query     = $this
            ->modelsManager
            ->createQuery(
                'SELECT * FROM Invoices WHERE inv_id = :id:'
            )
        ;

        $invoices = $query->execute(
            [
                'id' => $invoiceId,
            ]
        );
        
        $this->view->setVar('invoices', $invoices);
    }
}

 Можно также пропустить создание запроса и его последующее выполнение, а вместо этого выполнить запрос непосредственно из объекта Models Manager:

<?php

use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Mvc\View;

/**
 * @property Manager $modelsManager
 * @property View    $view
 */
class Invoices extends Controller
{
    public function listAction()
    {
        $invoices = $this
            ->modelsManager
            ->executeQuery(
                'SELECT * FROM Invoices'
            )
        ;

        $this->view->setVar('invoices', $invoices);
    }
}

Использование связанных параметров:

<?php

use Phalcon\Http\Request;
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Mvc\View;

/**
 * @property Manager $modelsManager
 * @property Request $request
 * @property View    $view
 */
class Invoices extends Controller
{
    public function viewAction()
    {
        $invoiceId = $this->request->getQuery('id', 'int');
        $invoices  = $this
            ->modelsManager
            ->executeQuery(
                'SELECT * FROM Invoices WHERE inv_id = :id:',
                [
                    'id' => $invoiceId,
                ]
            )
        ;
        
        $this->view->setVar('invoices', $invoices);
    }
}

Select

Как и привычный SQL, PHQL позволяет выбирать записи с помощью оператора SELECT, за исключением того, что вместо указания таблиц мы используем классы моделей:

Модели

SELECT 
    * 
FROM   
    Invoices  
ORDER BY 
    Invoices.inv_title
SELECT 
    Invoices.inv_id, 
    Invoices.inv_title, 
    Invoices.inv_status_flag
FROM   
    Invoices  
ORDER BY 
    Invoices.inv_title

Модели с пространством имен

SELECT 
    * 
FROM   
    MyApp\Models\Invoices
ORDER BY 
    MyApp\Models\Invoices.inv_title'

 Псевдонимы

SELECT 
    i.inv_id, 
    i.inv_title, 
    i.inv_status_flag
FROM   
    Invoices i  
ORDER BY 
    i.inv_title

CASE

SELECT 
    i.inv_id, 
    i.inv_title, 
    CASE i.inv_status_flag
        WHEN 1 THEN 'Paid'
        WHEN 0 THEN 'Unpaid'
    END AS status_text
FROM   
    Invoices i
WHERE  
    i.inv_status_flag = 1  
ORDER BY 
    i.inv_title
LIMIT 100

LIMIT

SELECT 
    i.inv_id, 
    i.inv_title, 
    i.inv_status_flag
FROM   
    Invoices i
WHERE  
    i.inv_status_flag = 1  
ORDER BY 
    i.inv_title
LIMIT 100

Псевдонимы пространств имен

Вы можете определить псевдонимы в пространствах имен, чтобы сделать ваш код немного более читаемым. Это настраивается при регистрации modelsManager в контейнере DI:

<?php

use MyApp\Models\Invoices;
use Phalcon\Di\FactoryDefault;
use Phalcon\Mvc\Model\Manager;

$container = new FactoryDefault();
$container->set(
    'modelsManager',
    function () {
        $modelsManager = new Manager();
        $modelsManager->registerNamespaceAlias(
            'inv',
             Invoices::class
        );

        return $modelsManager;
    }
);

а теперь наш запрос можно записать так:

SELECT 
    i.inv_id 
FROM   
    inv:Invoices i
WHERE  
    i.inv_status_flag = 1  

Вышесказанное сокращает все пространство имен для модели, заменяя его псевдонимом.

Подзапросы

PHQL также поддерживает подзапросы. Синтаксис аналогичен синтаксису, предлагаемому PDO.

SELECT 
    i.inv_id 
FROM   
    Invoices i
WHERE EXISTS (  
    SELECT 
        cst_id
    FROM
        Customers c
    WHERE 
        c.cst_id = i.inv_cst_id
)
SELECT 
    inv_id 
FROM   
    Invoices 
WHERE inv_cst_id IN (  
    SELECT 
        cst_id
    FROM
        Customers 
    WHERE 
        cst_name LIKE '%ACME%'
)

Результаты

В зависимости от столбцов, которые мы запрашиваем, а также таблиц, типы результатов будут отличаться.

Если вы извлекаете все столбцы из одной таблицы,вы получите обратно полностью функциональный объект Phalcon\Mvc\Model\Resultset\Simple. Возвращаемый объект является полным и может быть изменен и повторно сохранен в базе данных, поскольку они представляют собой полную запись связанной таблицы.

Следующие примеры дают идентичные результаты:

Модель

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
    [
        'order' => 'inv_title'
    ]
);

foreach ($invoices as $invoice) {
    echo $invoice->inv_id, ' - ', $invoice->inv_name, PHP_EOL;
}
<?php

$phql = "
    SELECT 
        * 
    FROM 
        Invoices 
    ORDER BY 
        inv_title";
$invoices  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($invoices as $invoice) {
    echo $invoice->inv_id, ' - ', $invoice->inv_name, PHP_EOL;
}

Любые запросы, использующие определенные столбцы, не возвращают полные объекты, и поэтому операции с базой данных не могут выполняться над ними. Однако они намного меньше, чем их полные аналоги, и предлагают микро-оптимизацию в коде.

<?php

$phql = "
    SELECT 
        inv_id, inv_title 
    FROM 
        Invoices 
    ORDER BY 
        inv_title";
$invoices  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($invoices as $invoice) {
    echo $invoice->inv_id, ' - ', $invoice->inv_name, PHP_EOL;
}

Возвращаемый результат-это объект Phalcon\Mvc\Model\Resultset\Simple. Тем не менее, однако, каждый элемент-это стандартный объект, который содержит только два столбца, которые были запрошены.

Эти значения, которые не представляют полные объекты, мы называем скалярами. PHQL позволяет запрашивать все типы скаляров: поля, функции, литералы, выражения и т. д..:

<?php

$phql = "
    SELECT 
        CONCAT(inv_id, ' - ', inv_title) AS id_name 
    FROM 
        Invoices 
    ORDER BY 
        inv_title";
$invoices  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($invoices as $invoice) {
    echo $invoice->id_name, PHP_EOL;
}

Мы можем запрашивать полные объекты или скаляры, поэтому можем также запрашивать и то и другое одновременно:

<?php

$phql = "
    SELECT 
        i.*, 
        IF(i.inv_status_flag = 1, 'Paid', 'Unpaid') AS status 
    FROM 
        Invoices i 
    ORDER BY 
        i.inv_title";
$invoices  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Результатом в этом случае является объект Phalcon\Mvc\Model\Resultset\Complex. Это позволяет одновременно получить доступ как к полным объектам, так и к скалярам:

<?php

foreach ($invoices as $invoice) {
    echo $invoice->status, 
         $invoice->i->inv_id, 
         $invoice->i->inv_name, 
         PHP_EOL
    ;
}

Скаляры сопоставляются как свойства каждой "строки", в то время как полные объекты сопоставляются как свойства с именем связанной модели. В приведенном выше примере скалярный статус status непосредственно из объекта, в то время как к строке базы данных может обращаться свойство invoices, имя которого совпадает с именем модели.

Если смешать * выборки из одной модели со столбцами из другой, то в итоге появятся как скаляры, так и объекты.

<?php

$phql = "
    SELECT 
        i.*, 
        IF(i.inv_status_flag = 1, 'Paid', 'Unpaid') AS status
        c.* 
    FROM 
        Invoices i
    JOIN
        Customers c
    ON
        i.inv_cst_id = c.cst_id 
    ORDER BY 
        i.inv_title";
$invoices  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Вышеизложенное приведет к следующим результатам:

<?php

foreach ($invoices as $invoice) {
    echo $invoice->status, 
         $invoice->i->inv_id, 
         $invoice->i->inv_name, 
         $invoice->c->cst_id, 
         $invoice->c->cst_name_last, 
         PHP_EOL
    ;
}

Другой пример:

<?php

$phql = "
    SELECT 
        i.*, 
        c.cst_name_last AS name_last 
    FROM 
        Invoices i
    JOIN
        Customers c
    ON
        i.inv_cst_id = c.cst_id 
    ORDER BY 
        i.inv_title";
$invoices  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Вышеизложенное приведет к следующим результатам:

<?php

foreach ($invoices as $invoice) {
    echo $invoice->name_last, 
         $invoice->i->inv_id, 
         $invoice->i->inv_name, 
         PHP_EOL
    ;
}

Обратите внимание, что мы выбираем один столбец из модели Customers и нам нужно назвать его (name_last), чтобы он стал скалярным в нашем результирующем наборе.

Joins

С помощью PHQL легко запрашивать записи из нескольких моделей. Большинство типов соединений поддерживаются. Поскольку мы определили отношения в моделях, PHQL автоматически добавляет эти условия:

<?php

$phql = "
    SELECT 
        Invoices.inv_id AS invoice_id, 
        Invoices.inv_title AS invoice_title, 
        Customers.cst_id AS customer_id,
        Customers.cst_name_last,
        Customers.cst_name_first 
    FROM 
        Customers
    INNER JOIN 
        Invoices 
    ORDER BY 
        Customers.cst_name_last, Customers.cst_name_first";
$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($records as $record) {
    echo $record->invoice_id, 
         $record->invoice_title, 
         $record->customer_id,
         $record->cst_name_last,
         $record->cst_name_first, 
         PHP_EOL
    ;
}

Примечание: по умолчанию предполагается внутреннее соединение.

В запросе можно указать следующие типы соединений:

  • CROSS JOIN
  • LEFT JOIN
  • LEFT OUTER JOIN
  • INNER JOIN
  • JOIN
  • RIGHT JOIN
  • RIGHT OUTER JOIN

Синтаксический анализатор PHQL автоматически разрешает условия операции JOIN, в зависимости от отношений, установленных в initialize() Это требования к hasMany, hasOne, belongsTo и т.д.

Однако можно вручную задать условия JOIN:

<?php

$phql = "
    SELECT 
        Invoices.inv_id AS invoice_id, 
        Invoices.inv_title AS invoice_title, 
        Customers.cst_id AS customer_id,
        Customers.cst_name_last,
        Customers.cst_name_first 
    FROM 
        Customers
    INNER JOIN 
        Invoices
    ON 
        Customers.cst_id = Invoices.inv_cst_id 
    ORDER BY 
        Customers.cst_name_last, Customers.cst_name_first";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Кроме того, соединения можно создавать с помощью нескольких таблиц в предложении FROM, используя альтернативный синтаксис соединения:

<?php

$phql = "
    SELECT 
        Invoices.*, 
        Customers.* 
    FROM 
        Customers, Invoices
    WHERE 
        Customers.cst_id = Invoices.inv_cst_id 
    ORDER BY 
        Customers.cst_name_last, Customers.cst_name_first";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($records as $record) {
    echo $record->invoices->inv_id, 
         $record->invoices->inv_title, 
         $record->customers->cst_id,
         $record->customers->cst_name_last,
         $record->customers->cst_name_first, 
         PHP_EOL
    ;
}

Если псевдонимы используются для моделей, то набор результатов будет использовать эти псевдонимы для именования атрибутов в каждой строке результата:

<?php

$phql = "
    SELECT 
        i.*, 
        c.* 
    FROM 
        Customers c, Invoices i
    WHERE 
        c.cst_id = i.inv_cst_id 
    ORDER BY 
        c.cst_name_last, c.cst_name_first";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($records as $record) {
    echo $record->i->inv_id, 
         $record->i->inv_title, 
         $record->c->cst_id,
         $record->c->cst_name_last,
         $record->c->cst_name_first, 
         PHP_EOL
    ;
}

Когда соединенная модель имеет отношение "многие ко многим" к модели from, промежуточная модель неявно добавляется к сгенерированному запросу. Для этого примера у нас есть модели Invoices, InvoicesXProducts иProducts:

<?php

$phql = "
    SELECT 
        Invoices.inv_id, 
        Invoices.inv_title, 
        Products.prd_id,
        Products.prd_title 
    FROM 
        Invoices
    JOIN
        Products
    WHERE 
        Invoices.inv_id = 1 
    ORDER BY 
        Products.prd_name";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Этот код выполняет следующий SQL в MySQL:

SELECT 
    co_invoices.inv_id, 
    co_invoices.inv_title, 
    co_products.prd_id,
    co_products.prd_title 
FROM 
    co_invoices
JOIN JOIN
    co_invoices_x_products 
ON 
    co_invoices.inv_id = co_invoices_x_products.ixp_inv_id
JOIN JOIN
    co_products 
ON 
    co_invoices_x_products.ixp_prd_id = co_products.prd_id
WHERE
    co_invoices.inv_id = 1
ORDER BY
    co_products.prd_name

Агрегации

В следующих примерах показано, как использовать агрегации в PHQL:

Средний

Какова средняя сумма счетов-фактур для клиента с inv_cst_id = 1

<?php

$phql = "
    SELECT 
        AVERAGE(inv_total) AS invoice_average
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = 1";

$results  = $this
    ->modelsManager
    ->executeQuery($phql)
;

echo $results['invoice_average'], PHP_EOL;

Count

Сколько счетов выставляет каждый клиент

<?php

$phql = "
    SELECT 
        inv_cst_id,
        COUNT(*) AS invoice_count
    FROM 
        Invoices
    GROUP BY 
        Invoices.inv_cst_id
    ORDER BY 
        Invoices.inv_cst_id";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($records as $record) {
    echo $record->inv_cst_id, 
         $record->invoice_count, 
         PHP_EOL
    ;
}

Count Distinct

Сколько счетов выставляет каждый клиент

<?php

$phql = "
    SELECT 
        COUNT(DISTINCT inv_cst_id) AS customer_id
    FROM 
        Invoices
    ORDER BY 
        Invoices.inv_cst_id";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

foreach ($records as $record) {
    echo $record->inv_cst_id, 
         PHP_EOL
    ;
}

Max

Какова максимальная сумма счета для клиента с inv_cst_id = 1

<?php

$phql = "
    SELECT 
        MAX(inv_total) AS invoice_max
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = 1";

$results  = $this
    ->modelsManager
    ->executeQuery($phql)
;

echo $results['invoice_max'], PHP_EOL;

Min

Какова минимальная сумма счета для клиента с inv_cst_id = 1

<?php

$phql = "
    SELECT 
        MIN(inv_total) AS invoice_min
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = 1";

$results  = $this
    ->modelsManager
    ->executeQuery($phql)
;

echo $results['invoice_min'], PHP_EOL;

Sum

Какова общая сумма счетов-фактур для клиента с inv_cst_id = 1

<?php

$phql = "
    SELECT 
        SUM(inv_total) AS invoice_total
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = 1";

$results  = $this
    ->modelsManager
    ->executeQuery($phql)
;

echo $results['invoice_total'], PHP_EOL;

 

Conditions

Условия позволяют нам фильтровать набор записей, которые мы хотим запросить, используя ключевое слово WHERE .

Выберите запись с одним числовым сравнением:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = 1";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи с большим, чем числовое сравнение:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_total > 1000";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выбор записей с одним сравнением текста с помощью TRIM

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        TRIM(Invoices.inv_title) = 'Invoice for ACME Inc.'";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи, используя ключевое слово LIKE:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_title LIKE '%ACME%'";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи, используя ключевые слова NOT LIKE:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_title NOT LIKE '%ACME%'";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи, в которых Поле равно NULL:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_total IS NULL";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи с помощью ключевого слова IN:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id IN (1, 3, 5)";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи, используя ключевые слова NOT IN:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id NOT IN (1, 3, 5)";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Выберите записи, используя ключевые слова BETWEEN:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id BETWEEN 1 AND 5";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Параметры

PHQL автоматически экранирует параметры, обеспечивая дополнительную безопасность:

Использование именованных параметров:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = :customer_id:";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            'customer_id' => 1,
        ]
    )
;

Использование числовых индексов:

<?php

$phql = "
    SELECT 
        *
    FROM 
        Invoices
    WHERE 
        Invoices.inv_cst_id = ?2";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            2 => 1,
        ]
    )
;

Insert

С PHQL можно вставлять данные, используя знакомый оператор INSERT:

Вставка данных без столбцов:

<?php

$phql = "
    INSERT INTO Invoices
    VALUES (
        NULL,
        1,
        0,
        'Invoice for ACME Inc.',
        0
    )";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Вставка данных с определенными столбцами:

<?php

$phql = "
    INSERT INTO Invoices (
        inv_id,
        inv_cst_id,
        inv_status_flag,
        inv_title,
        inv_total
    )
    VALUES (
        NULL,
        1,
        0,
        'Invoice for ACME Inc.',
        0
    )";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Вставка данных с именованными местозаполнителями:

<?php

$phql = "
    INSERT INTO Invoices (
        inv_id,
        inv_cst_id,
        inv_status_flag,
        inv_title,
        inv_total
    )
    VALUES (
        :id:,
        :cst_id:,
        :status_flag:,
        :title:,
        :total:
    )";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            'id'          => NULL,
            'cst_id'      => 1,
            'status_flag' => 0,
            'title'       => 'Invoice for ACME Inc.',
            'total'       => 0
        ]
    )
;

Вставка данных с числовыми местозаполнителями:

<?php

$phql = "
    INSERT INTO Invoices (
        inv_id,
        inv_cst_id,
        inv_status_flag,
        inv_title,
        inv_total
    )
    VALUES (
        ?0,
        ?1,
        ?2,
        ?3,
        ?4
    )";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            0 => NULL,
            1 => 1,
            2 => 0,
            3 => 'Invoice for ACME Inc.',
            4 => 0
        ]
    )
;

Phalcon не только преобразует инструкции PHQL в SQL. Все события и бизнес-правила, определенные в модели, выполняются как при создании отдельных объектов вручную.

При добавлении бизнес-правила в событие beforeCreate для модели Invoices событие вызывается и выполняется код. При условии, что мы добавляем правило, в котором счет-фактура не может иметь отрицательную итоговую сумму:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message;

class Invoices extends Model
{
    public function beforeCreate()
    {
        if ($this->inv_total < 0) {
            $this->appendMessage(
                new Message('An invoice cannot have a negative total')
            );

            return false;
        }
    }
}

Если мы выдадим следующую инструкцию INSERT:

<?php

$phql = "
    INSERT INTO Invoices (
        inv_id,
        inv_cst_id,
        inv_status_flag,
        inv_title,
        inv_total
    )
    VALUES (
        ?0,
        ?1,
        ?2,
        ?3,
        ?4
    )";

$result  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            0 => NULL,
            1 => 1,
            2 => 0,
            3 => 'Invoice for ACME Inc.',
            4 => -100
        ]
    )
;

if (false === $result->success()) {
    foreach ($result->getMessages() as $message) {
        echo $message->getMessage();
    }
}

После того, как мы попытались вставить отрицательное число для inv_total , beforeCreate записи был вызван вызов до Create. В результате операция завершается неуспешно, и соответствующие сообщения об ошибках отправляются обратно.

Обновление

При обновлении строк используются те же правила, что и при вставке строк. Для этой операции используется команда UPDATE. Как и при вставке строк, при обновлении записи события, связанные с операцией обновления, будут выполняться для каждой строки.

Обновление одной колонки

<?php

$phql = "
    UPDATE Invoices
    SET
        inv_total = 0
    WHERE
        inv_cst_id = 1";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Обновление нескольких колонок

<?php

$phql = "
    UPDATE Invoices
    SET
        inv_status_flag = 0,
        inv_total = 0
    WHERE
        inv_cst_id = 1";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Обновление нескольких строк:

<?php

$phql = "
    UPDATE Invoices
    SET
        inv_status_flag = 0,
        inv_total = 0
    WHERE
        inv_cst_id > 10";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Обновление данных с помощью именованных заполнителей:

<?php

$phql = "
    UPDATE Invoices
    SET
        inv_status_flag = :status:,
        inv_total = :total:
    WHERE
        inv_cst_id > :customerId:";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            'status'     => 0,
            'total'      => 0,
            'customerId' => 10,
        ]
    )
;

Обновление данных с помощью числовых заполнителей:

<?php

$phql = "
    UPDATE Invoices
    SET
        inv_status_flag = ?0,
        inv_total = ?1
    WHERE
        inv_cst_id > ?2";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            0 => 0,
            1 => 0,
            2 => 10,
        ]
    )
;

Инструкция UPDATE выполняет обновление в два этапа:

  • Если UPDATE содержит предложение WHERE , оно извлекает все объекты, соответствующие этим критериям,
  • На основе запрошенных объектов он обновляет запрошенные атрибуты, сохраняя их в базе данных

Этот способ работы позволяет выполнять события, виртуальные внешние ключи и проверки во время процесса обновления. Короче говоря, код:

<?php

$phql = "
    UPDATE Invoices
    SET
        inv_status_flag = 0,
        inv_total = 0
    WHERE
        inv_cst_id > 10";

$result = $this
    ->modelsManager
    ->executeQuery($phql)
;

if (false === $result->success()) {
    $messages = $result->getMessages();

    foreach ($messages as $message) {
        echo $message->getMessage();
    }
}

является чем-то эквивалентным:

<?php

use MyApp\Models\Invoices;

$messages = [];
$invoices = Invoices::find(
    [
        'conditions' => 'inc_cst_id = :customerId:',
        'bind'       => [
            'customerId' => 10,
        ],
    ]  
);

foreach ($invoices as $invoice) {
    $invoice->inv_status_flag = 0;
    $invoice->inv_total       = 0;
    
    $result = $invoice->save();
    if (false === $result) {
        $messages[] = $invoice->getMessages();
    } 
}

Удаление данных.

Аналогично обновлению записей, удаление записей использует те же правила. Для этой операции мы используем команду DELETE. При удалении записи для каждой строки будут выполняться события, связанные с операцией обновления.

Удаление одной строки

<?php

$phql = "
    DELETE
    FROM 
        Invoices
    WHERE
        inv_cst_id = 1";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Удаление нескольких строк:

<?php

$phql = "
    DELETE
    FROM 
        Invoices
    WHERE
        inv_cst_id > 10";

$records  = $this
    ->modelsManager
    ->executeQuery($phql)
;

Удаление данных с именованными заполнителями:

<?php

$phql = "
    DELETE
    FROM 
        Invoices
    WHERE
        inv_cst_id > :customerId:";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            'customerId' => 10,
        ]
    )
;

Удаление данных с числовыми заполнителями:

<?php

$phql = "
    DELETE
    FROM 
        Invoices
    WHERE
        inv_cst_id > ?2";

$records  = $this
    ->modelsManager
    ->executeQuery(
        $phql,
        [
            2 => 10,
        ]
    )
;

Оператор DELETE выполняет удаление в два этапа:

  • Если DELETE имеет предложение WHERE, он извлекает все объекты, соответствующие этим критериям,
  • На основе запрошенных объектов он удаляет запрошенные объекты из реляционной базы данных

Как и остальные операции, проверка возвращенного кода состояния позволяет получить обратно любые сообщения проверки, возвращенные операциями, подключенными к вашим моделям

<?php

$phql = "
    DELETE
    FROM
        Invoices
    WHERE
        inv_cst_id > 10";

$result = $this
    ->modelsManager
    ->executeQuery($phql)
;

if (false === $result->success()) {
    $messages = $result->getMessages();

    foreach ($messages as $message) {
        echo $message->getMessage();
    }
}

Построитель запросов.

Phalcon\Mvc\Model\Query\Builder - это очень удобный конструктор, который позволяет создавать операторы PHQL объектно-ориентированным способом. Большинство методов возвращают объект buider, что позволяет использовать свободный интерфейс и достаточно гибко позволяет добавлять условные обозначения, если это необходимо, без необходимости создавать сложные операторы if и конкатенации строк, создающие оператор PHQL.

Запрос PHQL:

SELECT 
    * 
FROM 
    Invoices 
ORDER BY 
    inv_title

может быть создан и выполнен следующим образом:

<?php

use MyApp\Models\Invoices;

$invoices = $this
    ->modelsManager
    ->createBuilder()
    ->from(Invoices::class)
    ->orderBy('inv_title')
    ->getQuery()
    ->execute();

Чтобы получить одну строку:

<?php

use MyApp\Models\Invoices;

$invoices = $this
    ->modelsManager
    ->createBuilder()
    ->from(Invoices::class)
    ->orderBy('inv_title')
    ->getQuery()
    ->getSingleResult();

Параметры

Независимо от того, создаете ли вы объект Phalcon\Mvc\Model\Query\Builder напрямую или используете метод createBuilder непосредственно или используется метод createBuilder диспетчера моделей, можно всегда использовать интерфейс пользователя для построения запроса или передачи массива с параметрами в конструкторе. Ключи массива:

  • bind - array - Массив данных для привязки
  • bindTypes - array - Типы параметра PDO
  • columns - array | string - столбцы для выбора
  • conditions - array | string - условия (where)
  • distinct - string - отдельная колонка
  • for_update - bool - для обновления или нет
  • group - array - группировка по столбцам
  • having - string - наличие колонок
  • joins - array - классы моделей, используемые для соединений
  • limit - array | int - ограничения для записей (т. е. 20 или [20, 20])
  • models - array - используемые классы моделей
  • offset - int - сдвиг
  • order - array | string - порядок столбцов
  • shared_lock - bool - вопрос общей блокировки или нет
<?php

use PDO;
use Phalcon\Mvc\Model\Query\Builder;

$params = [
    "models"     => [
        Users::class,
    ],
    "columns"    => ["id", "name", "status"],
    "conditions" => [
        [
            "created > :min: AND created < :max:",
            [
                "min" => "2013-01-01",
                "max" => "2014-01-01",
            ],
            [
                "min" => PDO::PARAM_STR,
                "max" => PDO::PARAM_STR,
            ],
        ],
    ],
    // or "conditions" => "created > '2013-01-01' AND created < '2014-01-01'",
    "group"      => ["id", "name"],
    "having"     => "name = 'Kamil'",
    "order"      => ["name", "id"],
    "limit"      => 20,
    "offset"     => 20,
    // or "limit" => [20, 20],
];

$builder = new Builder($params);

Геттеры

  • autoescape(string $identifier) - string - Автоматически экранирует идентификаторы, но только если они должны быть экранированы.
  • getBindParams(): array - Возвращает параметры привязки по умолчанию
  • getBindTypes(): array - Возвращает типы Привязок по умолчанию
  • getColumns() - string | array - Возврат запрашиваемых столбцов
  • getDistinct() - bool - Возвращает предложение SELECT DISTINCT / SELECT ALL
  • getFrom() - string | array - Return the models for the query
  • getGroupBy() - array - Возвращает предложение GROUP BY
  • getHaving() - string - Возвращает предложение HAVING
  • getJoins() - array - Возвращает соединяемые части запроса JOIN
  • getLimit() - string | array - Возвращает текущее предложение LIMIT
  • getModels() - string | array | null - Возвращает модели, задействованные в запросе
  • getOffset() - int - Возвращает текущее предложение OFFSET
  • getOrderBy() - string / array - Возвращает предложение ORDER BY
  • getPhql() - string - Возвращает сгенерированную инструкцию PHQL
  • getQuery() - QueryInterface - Возвращает построенный запрос
  • getWhere() - string | array - Возврат условий запроса

Методы

public function addFrom(
    string $model, 
    string $alias = null
): BuilderInterface

Добавляет модель. Первый параметр-это модель, а второй-псевдоним модели.

<?php

$builder->addFrom(
    Customers::class
);

$builder->addFrom(
    Customers::class,
    "c"
);
public function andHaving(
    mixed $conditions, 
    array $bindParams = [], 
    array $bindTypes = []
): BuilderInterface

Добавляет условие к текущему набору условий HAVING с помощью оператора AND. Первым параметром является выражение. Второй параметр - массив с привязанным именем параметра в качестве ключа. Последним параметром является массив, определяющий привязанный тип для каждого параметра. Привязанными типами являются константы PDO.

<?php

$builder->andHaving("SUM(Invoices.inv_total) > 1000");

$builder->andHaving(
    "SUM(Invoices.inv_total) > :sum:",
    [
        "sum" => 1000,
    ],
    [
        "sum" => PDO::PARAM_INT,
    ]
);
public function andWhere(
    mixed $conditions, 
    array $bindParams = [], 
    array $bindTypes = []
): BuilderInterface

Добавляет условие к текущему набору условия WHERE с помощью оператора AND. Первым параметром является выражение. Второй параметр - массив с привязанным именем параметра в качестве ключа. Последним параметром является массив, определяющий привязанный тип для каждого параметра. Привязанными типами являются константы PDO.

<?php

$builder->andWhere("SUM(Invoices.inv_total) > 1000");

$builder->andWhere(
    "SUM(Invoices.inv_total) > :sum:",
    [
        "sum" => 1000,
    ],
    [
        "sum" => PDO::PARAM_INT,
    ]
);
public function betweenHaving(
    string $expr, 
    mixed $minimum, 
    mixed $maximum, 
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие BETWEEN к текущему набору условий HAVING . Метод принимает выражение, минимум и максимум, а также оператор для BETWEEN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->betweenHaving(
    "SUM(Invoices.inv_total)",
    1000,
    5000
);
public function betweenWhere(
    string $expr, 
    mixed $minimum, 
    mixed $maximum, 
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие BETWEEN к текущему набору условий WHERE. Метод принимает выражение, минимум и максимум, а также оператор для параметра BETWEEN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->betweenWhere(
    "Invoices.inv_total",
    1000,
    5000
);
public function columns(mixed $columns): BuilderInterface

Задает столбцы для запроса. Метод принимает либо string , либо array. Если вы зададите массив с определенными ключами, они будут использоваться в качестве псевдонимов для соответствующих столбцов.

<?php

// SELECT inv_id, inv_title
$builder->columns("inv_id, inv_title");

// SELECT inv_id, inv_title
$builder->columns(
    [
        "inv_id",
        "inv_title",
    ]
);

// SELECT inv_cst_id, inv_total
$builder->columns(
    [
        "inv_cst_id",
        "inv_total" => "SUM(inv_total)",
    ]
);
public function distinct(mixed $distinct): BuilderInterface

Устанавливает флаг SELECT DISTINCT / SELECT ALL

<?php

$builder->distinct("status");
$builder->distinct(null);
public function forUpdate(bool $forUpdate): BuilderInterface

Задает предложение FOR UPDATE

<?php

$builder->forUpdate(true);
public function from(mixed $models): BuilderInterface

Устанавливает модели для запроса. Метод принимает string или array. Если указать массив с определенными ключами, они будут использоваться в качестве псевдонимов для соответствующих моделей.

<?php

$builder->from(
    Invoices::class
);

$builder->from(
    [
        Invoices::class,
        Customers::class,
    ]
);

$builder->from(
    [
        'i' => Invoices::class,
        'c' => Customers::class,
    ]
);
public function groupBy(mixed $group): BuilderInterface

Добавляет условие GROUP BY к построителю.

<?php

$builder->groupBy(
    [
        "Invoices.inv_cst_id",
    ]
);
public function having(
    mixed $conditions, 
    array $bindParams = [], 
    array $bindTypes = []
): BuilderInterface

Устанавливает условие HAVING . Первым параметром является выражение. Второй параметр - массив с привязанным именем параметра в качестве ключа. Последним параметром является массив, определяющий привязанный тип для каждого параметра. Привязанными типами являются константы PDO.

<?php

$builder->having("SUM(Invoices.inv_total) > 1000");

$builder->having(
    "SUM(Invoices.inv_total) > :sum:",
    [
        "sum" => 1000,
    ],
    [
        "sum" => PDO::PARAM_INT,
    ]
);
public function inHaving(
    string $expr, 
    array $values, 
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие IN к текущему набору условий HAVING. Метод принимает выражение, массив со значениями IN, а также оператор для IN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->inHaving(
    "SUM(Invoices.inv_total)",
    [
        1000,
        5000,
    ]
);
public function innerJoin(
    string $model, 
    string $conditions = null, 
    string $alias = null
): BuilderInterface

Добавление INNER к запросу join. Первый параметр - модель. Условия соединения вычисляются автоматически, если соответствующие отношения были правильно установлены в соответствующих моделях. Однако можно задать условия вручную, используя второй параметр - это условия, в то время как третий (если указан) - это псевдоним.

<?php

$builder->innerJoin(
    Customers::class
);

$builder->innerJoin(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id"
);

$builder->innerJoin(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id",
    "c"
);
public function inWhere(
    string $expr, 
    array $values,  
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие IN к текущему набору условий WHERE. Метод принимает выражение, массив со значениями для предложения IN, а также оператор для IN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->inWhere(
    "Invoices.inv_id",
    [1, 3, 5]
);
public function join(
    string $model, 
    string $conditions = null, 
    string $alias = null, 
    string $type = null
): BuilderInterface

Добавление JOIN к запросу. Первый параметр - модель. Условия соединения вычисляются автоматически, если соответствующие отношения были правильно установлены в соответствующих моделях. Однако можно задать условия вручную, используя второй параметр - это условия, в то время как третий (если указан) - это псевдоним. Последний параметр определяет тип соединения. По умолчанию соединение имеет значение INNER. Допустимые значения: INNER, LEFT и RIGHT.

<?php

$builder->join(
    Customers::class
);

$builder->join(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id"
);

$builder->join(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id",
    "c"
);

$builder->join(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id",
    "c",
    "INNER"
);
public function leftJoin(
    string $model, 
    string $conditions = null, 
    string $alias = null
): BuilderInterface

Добавление LEFT к запросу JOIN. Первый параметр - модель. Условия соединения вычисляются автоматически, если соответствующие отношения были правильно установлены в соответствующих моделях. Однако можно задать условия вручную, используя второй параметр - это условия, в то время как третий (если указан) - это псевдоним.

<?php

$builder->leftJoin(
    Customers::class
);

$builder->leftJoin(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id"
);

$builder->leftJoin(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id",
    "c"
);
public function limit(
    int $limit, 
    mixed $offset = null
): BuilderInterface

Задает предложение LIMIT, необязательно предложение offset в качестве второго параметра

<?php

$builder->limit(100);
$builder->limit(100, 20);
$builder->limit("100", "20");
public function notBetweenHaving(
    string $expr, 
    mixed $minimum, 
    mixed $maximum, 
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие NOT BETWEEN к текущему набору условий HAVING. Метод принимает выражение, минимум и максимум, а также оператор для NOT BETWEEN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->notBetweenHaving(
    "SUM(Invoices.inv_total)",
    1000,
    5000
);
public function notBetweenWhere(
    string $expr, 
    mixed $minimum, 
    mixed $maximum, 
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие NOT BETWEEN к текущему набору условий WHERE. Метод принимает выражение, минимум и максимум, а также оператор для NOT BETWEEN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->notBetweenWhere(
    "Invoices.inv_total",
    1000,
    5000
);
public function notInHaving(
    string $expr, 
    array $values, 
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие NOT IN к текущему набору условий HAVING.  Метод принимает выражение, массив со значениями IN, а также оператор для NOT IN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->notInHaving(
    "SUM(Invoices.inv_total)",
    [
        1000,
        5000,
    ]
);
public function notInWhere(
    string $expr, 
    array $values,  
    string $operator = BuilderInterface::OPERATOR_AND
): BuilderInterface

Добавляет условие NOT IN к текущему набору условий WHERE. Метод принимает выражение, массив со значениями для предложения IN, а также оператор для предложения NOT IN (OPERATOR_AND или OPERATOR_OR)

<?php

$builder->notInWhere(
    "Invoices.inv_id",
    [1, 3, 5]
);
public function offset(int $offset): BuilderInterface

Задает предложение OFFSET

<?php

$builder->offset(30);
public function orderBy(mixed $orderBy): BuilderInterface

Задает условие ORDER BY. Параметр может быть строкой или массивом. Для определения направления заказа можно также задать суффикс каждого столбца с помощью ASC или DESC.

<?php

$builder->orderBy("Invoices.inv_total");

$builder->orderBy(
    [
        "Invoices.inv_total",
    ]
);

$builder->orderBy(
    [
        "Invoices.inv_total DESC",
    ]
);
public function orHaving(
    mixed $conditions, 
    array $bindParams = [], 
    array $bindTypes = []
): BuilderInterface

Добавляет условие к текущему предложению условия HAVING с помощью оператора OR. Первым параметром является выражение. Второй параметр - массив с привязанным именем параметра в качестве ключа. Последним параметром является массив, определяющий привязанный тип для каждого параметра. Привязанными типами являются константы PDO.

<?php

$builder->orHaving("SUM(Invoices.inv_total) > 1000");

$builder->orHaving(
    "SUM(Invoices.inv_total) > :sum:",
    [
        "sum" => 1000,
    ],
    [
        "sum" => PDO::PARAM_INT,
    ]
);
public function orWhere(
    mixed $conditions, 
    array $bindParams = [], 
    array $bindTypes = []
): BuilderInterface

Добавляет условие в текущее условие WHERE с помощью оператора OR. Первым параметром является выражение. Второй параметр - массив с привязанным именем параметра в качестве ключа. Последним параметром является массив, определяющий привязанный тип для каждого параметра. Привязанными типами являются константы PDO.

<?php

$builder->orWhere("SUM(Invoices.inv_total) > 1000");

$builder->orWhere(
    "SUM(Invoices.inv_total) > :sum:",
    [
        "sum" => 1000,
    ],
    [
        "sum" => PDO::PARAM_INT,
    ]
);
public function rightJoin(
    string $model, 
    string $conditions = null, 
    string $alias = null
): BuilderInterface

Добавление RIGHT к запросу JOIN. Первый параметр - модель. Условия соединения вычисляются автоматически, если соответствующие отношения были правильно установлены в соответствующих моделях. Однако можно задать условия вручную, используя второй параметр - это условия, в то время как третий (если указан) - это псевдоним.

<?php

$builder->rightJoin(
    Customers::class
);

$builder->rightJoin(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id"
);

$builder->rightJoin(
    Customers::class,
    "Invoices.inv_cst_id = Customers.cst_id",
    "c"
);
public function setBindParams(
    array $bindParams, 
    bool $merge = false
): BuilderInterface

Задает параметры привязки по умолчанию. Первый параметр является массивом, где ключ является именем или номером связанного параметра. Второй параметр является логическим, предписывающим компоненту объединить предоставленные параметры с существующим стеком или нет.

<?php

$builder->setBindParams(
    [
        "sum" => 1000,
    ]
);

$builder->setBindParams(
    [
        "cst_id" => 10,
    ],
    true
);

$builder->where(
    "SUM(Invoices.inv_total) > :sum: AND inv_cst_id > :cst_id:",
    [
        "sum"    => PDO::PARAM_INT,
        "cst_id" => PDO::PARAM_INT,
    ]
);
public function setBindTypes(
    array bindTypes, 
    bool $merge = false
): BuilderInterface

Установите типы Привязок по умолчанию. Первый параметр-это массив, где ключом является имя или число связанного параметра. Второй параметр является логическим, указывая компоненту, следует ли объединить предоставленные параметры в существующий стек или нет. Связанные типы-это константы PDO.

<?php

$builder->setBindParams(
    [
        "sum" => 1000,
    ]
);

$builder->setBindParams(
    [
        "cst_id" => 10,
    ],
    true
);

$builder->setBindTypes(
    [
        "sum" => PDO::PARAM_INT,
    ]
);

$builder->setBindTypes(
    [
        "cst_id" => PDO::PARAM_INT,
    ],
    true
);

$builder->where(
    "SUM(Invoices.inv_total) > :sum: AND inv_cst_id > :cst_id:"
);
public function where(
    mixed $conditions, 
    array $bindParams = [], 
    array $bindTypes = []
): BuilderInterface

Задает условие WHERE E. Первый параметр-это выражение. Второй параметр-это массив с именем связанного параметра в качестве ключа. Последний параметр-это массив, который определяет тип привязки для каждого параметра. Связанные типы-это константы PDO.

<?php

$builder->where("SUM(Invoices.inv_total) > 1000");

$builder->where(
    "SUM(Invoices.inv_total) > :sum:",
    [
        "sum" => 1000,
    ],
    [
        "sum" => PDO::PARAM_INT,
    ]
);

Примеры

<?php

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices
$builder->from(Invoices::class);

// SELECT 
//      Invoices*, 
//      Customers.* 
// FROM 
//      Invoices, 
//      Customers
$builder->from(
    [
        Invoices::class,
        Customers::class,
    ]
);

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices
$builder
    ->columns('*')
    ->from(Invoices::class)
;

// SELECT 
//      Invoices.inv_id 
// FROM 
//      Invoices
$builder
    ->columns('inv_id')
    ->from(Invoices::class)
;

// SELECT 
//      Invoices.inv_id, 
//      Invoices.inv_title 
// FROM 
//      Invoices
$builder
    ->columns(
        [
            'inv_id', 
            'inv_title',
        ]
    )
    ->from(Invoices::class)
;

// SELECT 
//      Invoices.inv_id, 
//      Invoices.title_alias 
// FROM 
//      Invoices
$builder
    ->columns(
        [
            'inv_id', 
            'title_alias' => 'inv_title',
        ]
    )
    ->from(Invoices::class)
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      Invoices.inv_cst_id = 1
$builder
    ->from(Invoices::class)
    ->where("Invoices.inv_cst_id = 1")
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      Invoices.inv_id = 1
$builder
    ->from(Invoices::class)
    ->where(1)
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      Invoices.inv_cst_id = 1
// AND 
//      Invoices.inv_total > 1000
$builder
    ->from(Invoices::class)
    ->where("inv_cst_id = 1")
    ->andWhere('inv_total > 1000')
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      Invoices.inv_cst_id = 1
// OR 
//      Invoices.inv_total > 1000
$builder
    ->from(Invoices::class)
    ->where("inv_cst_id = 1")
    ->orWhere('inv_total > 1000')
;


// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// GROUP BY 
//      Invoices.inv_cst_id
$builder
    ->from(Invoices::class)
    ->groupBy('Invoices.inv_cst_id')
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// GROUP BY 
//      Invoices.inv_cst_id,
//      Invoices.inv_status_flag
$builder
    ->from(Invoices::class)
    ->groupBy(
        [
            'Invoices.inv_cst_id',
            'Invoices.inv_status_flag',
        ]
    )
;

// SELECT 
//      Invoices.inv_title, 
//      SUM(Invoices.inv_total) AS total
// FROM 
//      Invoices 
// GROUP BY 
//      Invoices.inv_cst_id
$builder
    ->columns(
        [
            'Invoices.inv_title', 
            'total' => 'SUM(Invoices.inv_total)'
        ]
    )
    ->from(Invoices::class)
    ->groupBy('Invoices.inv_cst_id')
;

// SELECT 
//      Invoices.inv_title, 
//      SUM(Invoices.inv_total) AS total
// FROM 
//      Invoices 
// GROUP BY 
//      Invoices.inv_cst_id
// HAVING
//      Invoices.inv_total > 1000
$builder
    ->columns(
        [
            'Invoices.inv_title', 
            'total' => 'SUM(Invoices.inv_total)'
        ]
    )
    ->from(Invoices::class)
    ->groupBy('Invoices.inv_cst_id')
    ->having('SUM(Invoices.inv_total) > 1000')
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// JOIN 
//      Customers
$builder
    ->from(Invoices::class)
    ->join(Customers::class)
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// JOIN 
//      Customers AS c
$builder
    ->from(Invoices::class)
    ->join(Customers::class, null, 'c')
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices AS i
// JOIN 
//      Customers AS c
// ON
//      i.inv_cst_id = c.cst_id
$builder
    ->from(Invoices::class, 'i')
    ->join(
        Customers::class, 
        'i.inv_cst_id = c.cst_id', 
        'c'
    )
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices AS i
// JOIN 
//      InvoicesXProducts AS x
// ON
//      i.inv_id = x.ixp_inv_id
// JOIN 
//      Products AS prd
// ON
//      x.ixp_prd_id = p.prd_id
$builder
    ->addFrom(Invoices::class, 'i')
    ->join(
        InvoicesXProducts::class, 
        'i.inv_id = x.ixp_inv_id', 
        'x'
    )
    ->join(
        Products::class, 
        'x.ixp_prd_id = p.prd_id', 
        'p'
    )
;

// SELECT 
//      Invoices.*, 
//      c.* 
// FROM 
//      Invoices, 
//      Customers AS c
$builder
    ->from(Invoices::class)
    ->addFrom(Customers::class, 'c')
;

// SELECT 
//      i.*, 
//      c.* 
// FROM 
//      Invoices AS i, 
//      Customers AS c
$builder
    ->from(
        [
            'i' => Invoices::class,
            'c' => Customers::class,
        ]
    )
;


// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// LIMIT 
//      10
$builder
    ->from(Invoices::class)
    ->limit(10)
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// LIMIT 
//      10
// OFFSET
//      5
$builder
    ->from(Invoices::class)
    ->limit(10, 5)
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      inv_id 
// BETWEEN 
//      1 
// AND 
//      100
$builder
    ->from(Invoices::class)
    ->betweenWhere('inv_id', 1, 100)
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      inv_id 
// IN 
//      (1, 2, 3)
$builder
    ->from(Invoices::class)
    ->inWhere(
        'inv_id', 
        [1, 2, 3]
    )
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      inv_id 
// NOT IN 
//      (1, 2, 3)
$builder
    ->from(Invoices::class)
    ->notInWhere(
        'inv_id', 
        [1, 2, 3]
    )
;

// SELECT 
//      Invoices.* 
// FROM 
//      Invoices 
// WHERE 
//      inv_title 
// LIKE 
//      '%ACME%';
$title = 'ACME';
$builder
    ->from(Invoices::class)
    ->where(
        'inv_title LIKE :title:', 
        [
            'title' => '%' . $title . '%',
        ]
    )
;

Связанные Параметры.

Привязанные параметры в построителе запросов можно задавать по мере построения запроса или при его выполнении:

<?php

$invoices = $this
    ->modelsManager
    ->createBuilder()
    ->from(Invoices::class)
    ->where(
        'inv_cst_id = :cst_id:', 
        [
            'cst_id' => 1,
        ]
    )
    ->andWhere(
        'inv_total = :total:', 
        [
            'total' => 1000,
        ]
    )
    ->getQuery()
    ->execute();

$invoices = $this
    ->modelsManager
    ->createBuilder()
    ->from(Invoices::class)
    ->where('inv_cst_id = :cst_id:')
    ->andWhere('inv_total = :total:')
    ->getQuery()
    ->execute(
        [
            'cst_id' => 1,
            'total'  => 1000,
        ]
    )
;

Отключить литералы в PHQL

Литералы могут быть отключены в PHQL. Это означает, что вы не сможете использовать строки, числа или логические значения в PHQL. Вместо этого вам придется использовать связанные параметры.

Примечание: отключение литералов повышает безопасность операторов базы данных и уменьшает возможность SQL-инъекций.

Примечание: Эта настройка может быть установлена глобально для всех моделей. Пожалуйста, обратитесь к документу модели для получения инструкции и дополнительных настроек.

Следующий запрос потенциально может привести к инъекции SQL:

<?php

$login  = 'admin';
$phql   = "SELECT * FROM Users WHERE login = '$login'";
$result = $manager->executeQuery($phql);

Если $login изменен на ' OR '' = ', то созданный PHQL будет:

SELECT * FROM Users WHERE login = '' OR '' = ''

Что всегда верно, независимо от того, какой логин хранится в базе данных. Если литералы отключены, использование строк, чисел или логических символов в строках PHQL вызовет исключение, вынуждающее разработчика использовать связанные параметры. Тот же запрос может быть записан более надежно, как:

<?php

$login  = 'admin';
$phql   = "SELECT * FROM Users WHERE login = :login:";
$result = $manager->executeQuery(
    $phql,
    [
        'login' => $login,
    ]
);

Вы можете запретить литералы следующим образом:

<?php

use Phalcon\Mvc\Model;

Model::setup(
    [
        'phqlLiterals' => false
    ]
);

Вы можете (и должны) использовать привязанные параметры независимо от того, отключены ли литералы.

Зарезервированные слова

PHQL использует некоторые зарезервированные слова внутри. Если вы хотите использовать какие-либо из них в качестве атрибутов или имен моделей, вам нужно будет экранировать их с помощью разделителей экранирования между базами данных [ и ]:

<?php

$phql   = 'SELECT * FROM [Update]';
$result = $manager->executeQuery($phql);

$phql   = 'SELECT id, [Like] FROM Posts';
$result = $manager->executeQuery($phql);

Разделители динамически преобразуются в допустимые разделители в зависимости от системы базы данных, к которой подключается приложение.

Пользовательский Диалект

Из-за различий в диалектах SQL, основанных на выбранных вами СУБД, поддерживаются не все методы. Однако вы можете расширить диалект, чтобы использовать дополнительные функции, поддерживаемые вашей СУБД.

Для примера ниже мы используем метод MATCH_AGAINST для MySQL.

<?php

use Phalcon\Db\Dialect\MySQL as Dialect;
use Phalcon\Db\Adapter\Pdo\MySQL as Connection;

$dialect = new Dialect();
$dialect->registerCustomFunction(
    'MATCH_AGAINST',
    function ($dialect, $expression) {
        $arguments = $expression['arguments'];
        return sprintf(
            " MATCH (%s) AGAINST (%)",
            $dialect->getSqlExpression($arguments[0]),
            $dialect->getSqlExpression($arguments[1])
         );
    }
);

$connection = new Connection(
    [
        "host"          => "localhost",
        "username"      => "root",
        "password"      => "secret",
        "dbname"        => "phalcon",
        "dialectClass"  => $dialect
    ]
);

Теперь вы можете использовать эту функцию в PHQL, и она внутренне преобразуется в правильный SQL с помощью пользовательской функции:

$phql = "SELECT *
   FROM Invoices
   WHERE MATCH_AGAINST(inv_title, :pattern:)";

$invoices = $modelsManager
    ->executeQuery(
        $phql, 
        [
            'pattern' => $pattern
        ]
    )
;

Другой пример демонстрации GROUP_CONCAT:

<?php

use Phalcon\Db\Dialect\MySQL as Dialect;
use Phalcon\Db\Adapter\Pdo\MySQL as Connection;

$dialect = new Dialect();
$dialect->registerCustomFunction(
    'MATCH_AGAINST',
    function ($dialect, $expression) {
        $arguments = $expression['arguments'];
        if (true !== empty($arguments[2])) {
            return sprintf(
                " GROUP_CONCAT(DISTINCT %s SEPARATOR %s)",
                $dialect->getSqlExpression($arguments[0]),
                $dialect->getSqlExpression($arguments[1])
            );
        }

        if (true !== empty($arguments[1])) {
            return sprintf(
                " GROUP_CONCAT(%s SEPARATOR %s)",
                $dialect->getSqlExpression($arguments[0]),
                $dialect->getSqlExpression($arguments[1])
            );
        }

        return sprintf(
            " GROUP_CONCAT(%s)",
            $dialect->getSqlExpression($arguments[0])
        );
    }
);

$connection = new Connection(
    [
        "host"          => "localhost",
        "username"      => "root",
        "password"      => "secret",
        "dbname"        => "phalcon",
        "dialectClass"  => $dialect
    ]
);

Теперь вы можете использовать эту функцию в PHQL, и она внутренне преобразуется в правильный SQL с помощью пользовательской функции:

$phql = "SELECT *
   FROM Invoices
   WHERE GROUP_CONCAT(inv_title, :first:, :separator:, :distinct:)";

$invoices = $modelsManager
    ->executeQuery(
        $phql, 
        [
            'pattern'   => $pattern,
            'separator' => $separator,
            'distinct'  => $distinct,
        ]
    )
;

Выше будет создан GROUP_CONCAT на основе параметров, переданных в метод. Если три параметра переданы, мы получим GROUP_CONCAT с DISTINCT и SEPARATOR, если прошли два параметра, у нас будет GROUP_CONCAT с SEPARATOR и если только один параметр прошел только GROUP_CONCAT

Кэширование

Запросы PHQL можно кэшировать. Вы также можете проверить документ кэширования моделей для получения дополнительной информации.

<?php

$phql  = 'SELECT * FROM Customers WHERE cst_id = :cst_id:';
$query = $this
    ->modelsManager
    ->createQuery($phql)
;

$query->cache(
    [
        'key'      => 'customers-1',
        'lifetime' => 300,
    ]
);

$invoice = $query->execute(
    [
        'cst_id' => 1,
    ]
);

Жизненный цикл

Будучи языком высокого уровня, PHQL дает разработчикам возможность персонализировать и настраивать различные аспекты в соответствии с их потребностями. Ниже приведен жизненный цикл каждого выполняемого оператора PHQL:

  • PHQL анализируется и преобразуется в промежуточное представление (IR), которое не зависит от SQL, реализуемого системой баз данных
  • IR преобразуется в допустимый SQL в соответствии с системой баз данных, связанной с моделью
  • Операторы PHQL анализируются один раз и кэшируются в памяти. Дальнейшее выполнение того же оператора приводит к немного более быстрому выполнению

Raw SQL

Система баз данных может предлагать определенные расширения SQL, которые не поддерживаются PHQL, в этом случае может быть подходящим необработанный SQL:

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset\Simple as Resultset;

class Invoices extends Model
{
    public static function findByCreateInterval()
    {
        $sql     = 'SELECT * FROM Invoices WHERE inv_id > 1';
        $invoice = new Invoices();

        // Execute the query
        return new Resultset(
            null,
            $invoice,
            $invoice->getReadConnection()->query($sql)
        );
    }
}

Если в приложении распространены необработанные SQL-запросы, в модель можно добавить общий метод:

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset\Simple as Resultset;

class Invoices extends Model
{
    public static function findByRawSql(
        string $conditions, 
        array $params = null
    ) {
        $sql     = 'SELECT * FROM Invoices WHERE ' . $conditions;
        $invoice = new Invoices();

        // Execute the query
        return new Resultset(
            null,
            $invoice,
            $invoice->getReadConnection()->query($sql, $params)
        );
    }
}

Приведенный выше findByRawSql можно использовать следующим образом:

<?php

$robots = Invoices::findByRawSql(
    'id > ?0',
    [
        10
    ]
);

Диагностика

Некоторые вещи, которые следует иметь в виду при использовании PHQL:

  • Классы чувствительны к регистру, если класс не определен с тем же именем, как он был создан, это может привести к неожиданному поведению в операционных системах с чувствительными к регистру файловыми системами, такими как Linux.
  • Для успешной привязки параметров в соединении должна быть определена правильная кодировка.
  • Псевдонимные классы не заменяются полными классами пространства имен, так как это происходит только в коде PHP, а не внутри строк.
  • Если переименование столбцов включено, избегайте использования псевдонимов столбцов с тем же именем, что и переименованные столбцы, это может привести к путанице в распознавателе запросов.