Статьи

PINQ — запрос ваших наборов данных — введение

Возможно, вы слышали о 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 и повторной выборке данных. Это имеет некоторые недостатки, однако:

  1. Критерии оператора SQL, то есть части «где» и / или «группировать по», могут стать очень сложными после построения SQL.
  2. Поскольку оператор SQL будет очень динамичным, он не может быть оптимизирован и усложнит индексирование.
  3. Это создаст огромные издержки при передаче оператора 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 вещи:

  1. Получите данные, полученные из нашего класса pinqDemo\Demo
  2. Примените первый фильтр. В нашем случае мы применяем ценовой диапазон к нашим исходным данным.
  3. Примените другую операцию (группирование) к данным, сгенерированным на шаге 2.
  4. Отобразите данные, сгенерированные в шагах 2 и 3

Прежде всего, если мы собираемся использовать 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:

  1. Мы работаем не с БД-соединением или «Entity Manager», а непосредственно с набором данных
  2. Критерии фильтрации (и другие функции) выражаются не в 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 также приложит некоторые усилия в документации.

В дальнейшем я попытаюсь проиллюстрировать, как использовать его для имитации своего рода «граненой» возможности поиска.

Не стесняйтесь комментировать и дать нам свои мысли!