Возможно, вы слышали о LINQ (Language-Integrated Query), «наборе функций, представленных в Visual Studio 2008, которые расширяют мощные возможности запросов до синтаксиса языка C # и Visual Basic».
Он предоставляет необходимые интерфейсы и синтаксис для выполнения различных запросов к данному набору данных, чтобы можно было выполнять такие операции, как фильтрация, сортировка, группирование, агрегирование и т. Д.
PINQ (PHP Integrated Query) «основан на Linq .NET и объединяет запросы к массивам / итераторам и внешним источникам данных в одном удобном для чтения и сжатом API». ( Взято с официального сайта PINQ )
Почему другой язык запросов?
PHPers очень удобны для выполнения запросов либо с помощью необработанных операторов SQL, либо с помощью ORM. Мы выбираем данные из базы данных, обрабатываем их и отображаем в выбранном формате (скажем, в форме таблицы). Если нам нужен еще один набор данных, мы создаем другой оператор, обрабатываем возвращенный набор данных и отображаем его.
При нормальных обстоятельствах этот процесс является достаточным и эффективным.
Но есть случаи, когда этот процесс просто не будет работать. Взять, к примеру, типичный сайт электронной коммерции. Пользователь вводит одно поисковое ключевое слово (скажем, «маршрутизатор»), и сайт отображает каждый соответствующий элемент. Первоначальный поиск может искать только элементы с их описанием, категорией или тегами, содержащими ключевое слово. Затем пользователь начнет точную настройку результатов поиска, выбрав марку, диапазон цен и т. Д.
Этот процесс точной настройки называется «граненым» поиском. Некоторые механизмы баз данных (например, SOLR) имеют встроенную возможность (как описано в этой серии: Использование Solarium для поиска SOLR ), но, очевидно, MySQL не поставляется с этой функциональностью.
Это не означает, однако, что MySQL не может предоставить такие функции. В конце концов, это все о создании нового оператора SQL и повторной выборке данных. Это имеет некоторые недостатки, однако:
- Критерии оператора SQL, то есть части «где» и / или «группировать по», могут стать очень сложными после построения SQL.
- Поскольку оператор SQL будет очень динамичным, он не может быть оптимизирован и усложнит индексирование.
- Это создаст огромные издержки при передаче оператора SQL обратно на сервер БД.
В этих случаях PINQ может пригодиться. Это PHP-версия библиотеки LINQ, которая обеспечивает фильтрацию, сортировку, группирование, агрегирование и индексацию по заданному набору данных.
подготовка
В этой серии из двух частей мы продемонстрируем, как использовать PINQ для имитации «граненого» поиска. Мы будем использовать данные приложения-образца для сбора книг (см. « Приспособления данных» в Symfony2 о том, как выгрузить образцы данных), но с некоторыми небольшими изменениями.
Также мы будем использовать Silex , очень легкий PHP-фреймворк и Bootstrap CSS, чтобы упростить настройку приложения. Пожалуйста, следуйте инструкциям на соответствующих веб-сайтах о том, как настроить веб-приложение Silex и интегрировать Bootstrap CSS.
Образцы данных, которые мы использовали в этой демонстрации, немного изменены, и я загрузил их в репозиторий для этой демонстрации . Исходный код также можно найти там.
Установка PINQ
Рекомендуемая установка PINQ — изменить файл composer.json
, поставляемый с Silex, и добавить еще одну строку в его раздел require
:
{ "require": { "silex/silex": "~1.1", "twig/twig": ">=1.8,<2.0-dev", "doctrine/dbal": "2.2.*", "symfony/twig-bridge": "~2.3", "timetoogo/pinq": "~2.0" } }
Обратите внимание, что я также добавил еще несколько зависимостей: Twig (и Twig-Bridge) для отображения результатов и Doctrine, как я, чтобы получить данные из моей базы данных для дальнейшей обработки.
После этого мы можем запустить команду composer.phar update
чтобы установить все необходимые пакеты.
Демо 1: основное использование
Сначала мы покажем несколько строк кода, чтобы продемонстрировать основное использование PINQ. Я возьму книги такими, какие они есть, проведу простой фильтр (по цене от 90 до 99), а затем выведу агрегированную информацию для разных авторов.
Дисплей будет выглядеть так:
Давайте посмотрим, как сделать эту демонстрационную страницу.
ПРИМЕЧАНИЕ. В этой статье я не собираюсь рассказывать об основах настройки приложения Silex. Silex по умолчанию index.php
не включает поддержку Twig / Twig-bridge и Doctrine-DBAL. Вам нужно будет включить эти два модуля в файле index.php
вашего приложения. Пожалуйста, обратитесь к официальному сайту Silex, чтобы узнать как.
Сначала мы создаем Demo
класс в pinqDemo.php
в качестве поставщика данных для нашего приложения:
<?php namespace pinqDemo { class Demo { private $books = ''; public function __construct($app) { $sql = 'select * from book_book order by id'; $this->books = $app['db']->fetchAll($sql); } public function test1($app) { return $this->books; } } }
Этот файл очень прост. В объявлении класса у нас есть конструктор, который получает данные с сервера db, и функция, которая возвращает набор данных обратно вызывающей функции.
Этот класс, конечно, может быть улучшен, например, путем введения некоторых типовых шаблонов проектирования.
Далее мы перейдем к index.php
и увидим некоторые основные способы использования PINQ, примененные к полученным нами данным.
// Excerpt of index.php use Pinq\ITraversable, Pinq\Traversable; ... ... $app->get('/demo1', function () use ($app) { global $demo; $books = $demo->test1($app); $data = Traversable::from($books); //Apply first filter $filter1 = $data ->where(function($row) { return $row['price'] > 90 && $row['price'] < 99; }) ->orderByDescending(function($row) { return $row['id']; }); $filter2 = $filter1 ->groupBy(function($row) { return $row['author']; }) ->select( function(ITraversable $filter1) { return ['author' => $filter1->first()['author'], 'count' => $filter1->count()]; } ); return $app['twig']->render('demo1.html.twig', array('orig' => $data, 'filter1' => $filter1, 'filter2'=>$filter2)); } );
Мы сопоставили URI /demo1
с первым образцом. В функции обработки этого маршрута мы в основном делаем 4 вещи:
- Получите данные, полученные из нашего класса
pinqDemo\Demo
- Примените первый фильтр. В нашем случае мы применяем ценовой диапазон к нашим исходным данным.
- Примените другую операцию (группирование) к данным, сгенерированным на шаге 2.
- Отобразите данные, сгенерированные в шагах 2 и 3
Более глубокий взгляд на заявления, связанные с PINQ
Прежде всего, если мы собираемся использовать PINQ, нам нужно предоставить набор данных.
В терминологии PINQ набор данных для манипулирования является «Обходным». Как и ожидалось, мы можем построить «Traversable» из нашего набора данных, возвращенного из наших запросов SQL:
$data = Traversable::from($books);
Чтобы применить операцию к вновь созданному объекту «Traversable» — я буду использовать «набор данных» и далее — PINQ предоставляет богатый набор функций:
- Фильтрация,
where
пункт; - Упорядочивание, такое как предложение
orderByAscending
иorderByDescending
; - Группировка, предложение
groupBy
; - Агрегирование, например,
count
,average
предложение; - Выбор, выбор и / или построение полей в результирующем наборе данных, таких как предложение
select
; - И другие, как присоединение и т. Д.
Полную ссылку на API можно найти на официальном сайте документации PINQ .
В нашем demo1
первая операция, которую мы применили, это фильтрация и сортировка:
$filter1 = $data ->where(function($row) { return $row['price'] > 90 && $row['price'] < 99; }) ->orderByDescending(function($row) { return $row['id']; });
PINQ использует расширенные функции PHP, такие как замыкания и анонимные функции, для выполнения этих задач.
Синтаксис очень похож на тот, который мы видим, когда используем ORM: команды могут быть объединены в цепочку, каждая из которых переходит к следующей команде в цепочке.
Основные различия между этим и ORM:
- Мы работаем не с БД-соединением или «Entity Manager», а непосредственно с набором данных
- Критерии фильтрации (и другие функции) выражаются не в SQL-подобном синтаксисе, а в PHP
После того, как мы применили фильтр, который является предложением where
, мы также выполнили сортировку отфильтрованных данных по id
в порядке убывания.
Если вы используете те же примеры данных, что и я, результирующий набор данных ( $filter1
) будет содержать 9 записей, и они будут отображаться во 2-м разделе окончательной отрисованной страницы, как показано выше.
Одним из преимуществ продажи PINQ является то, что сгенерированный набор данных можно использовать повторно. Это дает нам гибкость в применении нескольких операций к одному и тому же набору данных, но предоставляет разные поднаборы данных и / или информацию агрегации, не разрушая исходный набор данных.
В нашем примере кода выше, сразу после того, как мы применили первый фильтр, мы выполняем работу по агрегации, чтобы выяснить, сколько существует отдельных авторов и сколько книг они написали:
$filter2 = $filter1 ->groupBy(function($row) { return $row['author']; }) ->select( function(ITraversable $filter1) { return ['author' => $filter1->first()['author'], 'count' => $filter1->count()]; } );
Функция groupBy
знакома, но функция select
нуждается в немного большей проработке.
Для типичной group by
агрегации мы ожидаем вернуть по крайней мере два значения из выполнения: одно — это ключ, используемый для группировки ( author
), и информация о агрегации ( count
), связанная с этим ключом.
Таким образом, вышеприведенный оператор внутри function(ITraversable $filter1)
выбирает значение author
из первой записи в каждой группе, а также счетчик для этой группы. Это именно то, что нам нужно.
Результат отображается в нижней части страницы, как показано выше: в общей сложности 9 отфильтрованных книг у нас есть 2 книги для автора 0/1/23 и 1 книга для автора 4.
Если мы хотим показать итоговую цену в этой группе, мы можем дополнительно расширить оператор select
function:
->select( function(ITraversable $filter1) { return ['author' => $filter1->first()['author'], 'count' => $filter1->count(), 'sum'=>$filter1->sum( function($row) { return $row['price']; } ) ]; } )
Анонимные функции внедряются и возвращают правильные результаты.
Затем мы можем orderByAscending
другой orderByAscending
с оператором select
чтобы упорядочить набор данных по sum
:
->orderByAscending(function($row){return $row['sum'];});
Это очень удобно и последовательно.
Некоторые могут утверждать, что в приведенных выше groupBy
и select
используемый синтаксис может показаться недостаточно интуитивным. Почему бы, скажем, не использовать ниже более простой синтаксис?
groupBy(function(return $row['author'];), $filter1->count());
Ну, автор ответил, что такую группировку сделать нельзя. И в текущей версии PINQ (2.1) мой подход самый лучший. Тем не менее, автор намекнул в своем ответе мне, что в следующем основном выпуске PINQ (3.0) будет введен значительно улучшенный синтаксис для такой распространенной задачи, то есть для группировки по ключу и получения соответствующей информации агрегации. Давайте следить за этим.
Вывод и движение вперед
В этой статье мы кратко рассказали об основных функциях PINQ и о том, как использовать их в веб-приложении.
PINQ находится в стадии разработки, и поэтому его документация еще не полностью готова. Почти всем функциям не хватает основательной и практической демонстрации. Я надеюсь, что эта статья может помочь в этом смысле, но я также надеюсь, что автор PINQ также приложит некоторые усилия в документации.
В дальнейшем я попытаюсь проиллюстрировать, как использовать его для имитации своего рода «граненой» возможности поиска.
Не стесняйтесь комментировать и дать нам свои мысли!