0,3
рейтинг
11 июля 2012 в 20:59

Разработка → LINQ для PHP. Часть 1. Я его слепила из того, что было, а потом, что было, то и полюбила

Сказ о том, как LINQ на PHP портировали. Сравнение ныне существующих библиотек с табличками, но без графиков — прилагается.

Картинка код для привлечения внимания (картинок не дождётесь!)
echo Phinq::create($people)
  ->groupBy(function($person) { return $person->residence->region; })
  ->select(function($grouping) {
    $obj = new stdClass();
    $obj->people = $grouping;
    $obj->region = $grouping->getKey();
    return $obj;
  })->orderBy(function($obj) { return $obj->people->count(); }, true)
  ->aggregate(function($current, $next) {
    $count = $next->people->count();
    return $current . sprintf(
      "%d %s (%s) live in the %s region\n",
      $count,
      $count === 1 ? 'person' : 'people',
      $next->people->aggregate(function($current, $next) {
        if ($current !== null) {
          $current .= ', ';
        }
        return $current . sprintf('%s [%s]', $next->name, $next->residence->code);
      }),
      $next->region
    );
  });
Кто видел C# или любой функциональный язык — при виде этого шедевра закатит глаза (если они предварительно не вылетят из орбит). И, наверное, будет прав. Но можно ещё вот так:
$lowNums =
	from('$n')->in($numbers)->
	where('$n < 5')->
	store($digits)->into('digits')->
	select('$digits[$n]');
Только что вы увидели двух зверей из зоопарка библиотек, портирующих LINQ на PHP. LINQ — это вообще-то Language Integrated Query, то есть SQL-подобные запросы, интегрированные в язык. В C# LINQ полагается на синтаксические деревья и имеет две формы записи, но, так как в PHP подобных фенечек ближайшее тысячелетие ждать не приходится, будем считать, что LINQ — это исключительно библиотечка с SQL-подобными методами.

Библиотек, портирующих LINQ на PHP, достаточно много. Ещё бы хоть одна его реально портировала… Но об этом позже, а пока в алфавитном порядке рассмотрим все имеющиеся альтернативы. Чтение предполагается либо последовательное, либо с конца.

LINQ for PHP

Писалось человеком, который готов переделать любой язык в свой любимый (так случилось, что для него — C#), как бы странно его код ни выглядел. Первые сомнения прокрадываются в душу при виде LinqSamples.php:
class Console
{
	public static function WriteLine()
	{
		$args = func_get_args();
		$string = array_shift($args);
		foreach ($args as $i => $value)
		{
			if (is_bool($value))
			{
				$value = $value ? 'True' : 'False';
			}
			$string = str_replace('{'.$i.'}', $value, $string);
		}
		echo $string.'<br />';
	}
...
Вы предположите, что при таком подходе портированный LINQ будет сложно отличить от оригинала? И будете правы. Было (C#):
from n in new int[] { 1, 2, 3 } where n % 2 == 0 select n * 2;
Стало (PHP):
from('$n')->in(array(1,2,3))->where('$n % 2 == 0')->select('$n *2');
Выглядит симпатично.

Лирическое отступление. Почему строчки? Код из них в <моём любимом IDE> не подсвечиваются же! А как же рефакторинг? А как же очепятки? А никак. Подсветки нет, рефакторинга нет, очепятки правят пиром. Синтаксис замыканий (closures) в PHP настолько многословен (сравните x => x + 1 с function ($x) { return $x + 1; }), что практически все разработчики портов LINQ изобретают свои «строчковые лямбды». Где-то можно использовать замыкания, где-то — похапэшные «указатели на функции» (строки 'strlen' и массивы array($object, 'methodName')), где-то — «лямбды», где-то — и то, и другое, и третье. В LINQ for PHP доступны все варианты.

Посмотрим сорцы библиотеки. «Мясо» скрыто в методе LinqForPhp_Objects_Sequence::doIteration: при вызове getIterator() последовательно выполняются все операции, набравшиеся в массиве operations. Операции выполняются последовательно, без цепочной «ленивости», то есть вызов where()->any() приведёт к фильтрации всей последовательности. После первого вызова результат кэшируется.

У автора были позывы добавить комментарии PHPDoc, но надолго его не хватило: документирована в лучшем случае пятая часть кода. Да и синтаксис PHPDoc автор ниасилил — ни у одного param имя аргумента не указано.

Реализованы стандартные методы: Aggregate, All, Cast, DefaultIfEmpty, Except, FirstOrDefault, GroupBy, OrderBy, Reverse, Single, Take и т.д. Всего порядка 50 штук. Есть конвертация в List, Dictionary — порты дотнетовых коллекций, в которых часть методов по неизвестной причине бросает NotImplementedException, а offsetExists реализован как array_key_exists($index) (вчитайтесь). Ответ на вопрос, на кой икс нужны эти коллекции, оставляю читателям.

Что не было у человека IDE, очень хорошо чувствуется — PhpStorm моментально высвечивает откровенные баги. Тестов в штуках: ноль целых ноль десятых.

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

Phinq

Писалось человеком, который верит в странное светлое будущее. У всех аргументов, принимающих функции, стоит ограничение на тип Closure; у сравнивалок (comparer) — интерфейсы EqualityComparer и т.п.; у коллекций — array. Выглядит это примерно так:
		public function groupJoin(array $collectionToJoinOn, Closure $innerKeySelector, Closure $outerKeySelector, Closure $resultSelector, EqualityComparer $comparer = null)
На первый взгляд логично, но тут вспоминаешь, что… Кроме замыканий есть родные похапэшные «указатели на функции» в виде строк и массивов [объект, имя метода], и они внезапно идут лесом. В интерфейсе EqualityComparer есть только один метод equals (получения хэшей — нет: это забота внутренностей родных похапэшных ассоциативных массивов). И ради каждого сравнения изволь создавать новый класс, функцию не передашь. В groupJoin логично передавать результаты выполнения запросов Phinq, а тип у них совсем не array. И интерфейсы SPL типа Iterator и IteratorAggregate внезапно тоже не являются массивами. Пока остаёшься в рамках примеров с числами и from-where-select — всё хорошо. Как сталкиваешься с суровой реальностью — становится тяжко.

У всех методов обнаруживается документация PHPDoc. Честно написанная самостоятельно для кажого метода. Ну, почти честно — местами чувствуется копипаста, которая приводит к вранью:
/**
 * Correlates elements into groupings of the two collections based on matching keys
 *
 * This is basically an outer join.
 *
 * @param array $collectionToJoinOn
 * @param Closure $innerKeySelector Takes one argument, the element's value, and returns the join key for that object
 * @param Closure $outerKeySelector Takes one argument, the element's value, and returns the join key for that object
 * @param Closure $resultSelector Takes two arguments, the matching elements from each collection, and returns a single value
 * @param EqualityComparer $comparer
 * @return Phinq
 */
public function groupJoin(...
Вы поняли, что делает функция? А что принимает и возвращает resultSelector? Если вы поняли, то вы не читали документирующий комментарий выше, а просто когда-то пользовались GroupJoin в C#.

Реализованы стандартные методы, всего штук 50, но как-то лениво реализованы. Single почему-то выкидывает исключение, если в коллекции единственный элемент — null. У массивов почему-то выбрасывается информация о ключах. Итераторы конвертируются в массивы сразу при создании объекта Phinq (тоже без информации о ключах, разумеется). Единственная возможность получить что-то с ключами на выходе — это конвертировать в коллекцию Dictionary, которая имеет реализацию offsetGet и offsetSet с ценой O(N). Слава богам, в отличие от предыдущей библиотеки, хотя бы List не реализован (метода toList, соответственно, нет).

Вся ленивость вычислений, как и в случае с предыдущей библиотекой, сводится к кэшированию результата.

Что же в библиотеке хорошего? Есть тесты. Ура. Поверхностные, конечно. В лучшем случае по паре штук на метод. Безо всяких граничных случаев. Но тесты — есть. Это хорошо.

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

Phinq 2.0

Писалось человеком, который разочаровался в светлом будущем: привет строковым лямбдам, пока ограничениям на аргументы-функции. Хотя EqualityComparer с единственным методом всё ещё с нами. Сверху приправим конвертацией в SQL для построения запросов SQL. Парсинг PHP силами PHP с исключениями вида «Phinq requires the unary boolean operator ('!') to be followed by an expression encased in parentheses». Писать будем долго...

...И, разумеется, не допишем. На переписывание старого Phinq на новый лад автор забил гвоздь. Ветка /branches/2.0 в SVN на далёком сервере — единственное упоминание светлого начинания и печального конца.

Итог: ничего нет, проходим мимо. К использованию не рекомендуется.

PHPLinq

Писалось человеком, который верит, что кому-то нужен ещё один DAL, и который туманно представляет себе возможности оригинального LINQ. В его понимании функции where, orderBy, select, skip, take и остальные можно вызывать в любом порядке, который не имеет значения, потому что порядок их применения задан намертво (и, кстати, нигде не документирован). В его понимании замыкания — это никому не нужная новомодная фенечка. В его понимании single и first — это совершенно одно и то же. В его понимании тесты — это скрипты PHP с вызовом print_r. В его понимании документирующие комментарии — это скопированное название метода и перечисление типов аргументов. В его понимании документация к проекту — это диаграмма классов в формате для VisualStudio.

Я, конечно, могу рассказать про поддержку LINQ to Objects, LINQ to ZendDb, LINQ to Azure, LINQ to MS SQL, LINQ to MySQL, LINQ to SQLite. Но кому нужно всё это разнообразие, если эта библиотека — маловменяемый код, не имеющий отношения ни к функциональному программированию в общем, ни к LINQ в частности, без документации, без тестов?

Итог: эпическое количество LINQ to *, эпическое отсутствие всего остального. К использованию не рекомендуется.

(Внимательный читатель, наверное, уже начинает задаваться вопросом: когда будет что-то рекомендуемое-то? Когда? Ждём, надеемся… верим. И читаем дальше.)

Plinq

Писалось человеком-минималистом, которому очень хотелось иметь LINQ в PHP, но которому было очень лень кодить. Поэтому поддерживается немногим больше 20 методов, среди которых нет ни ThenBy, ни Aggregate. Ленивых вычислений нет вообще. Строчковых «лямбд» нет — только замыкания, только хардкор (учитывая отсутствие ограничений на тип аргументов и текущие баги в PHP, строчковые «указатели на функции» тоже должны работать). Передача замыканий в функции-агрегаторы — обязательно. В большинстве своём методы Plinq — это обёртки над стандартными функциями, плюс конвертация между массивами и итераторами туда-сюда.

Что хорошего в библиотеке? Есть подобие тестов — по паре assert'ов на функцию. Выглядит это примерно так:
function TestOrderBy(&$testArray)
{
    $p = new Plinq($testArray);
    $result = $p->OrderByDescending(function($k, $v){ return $v['int']; });
    assert('key($result) == "key_999"');

    $p = new Plinq($testArray);
    $result = $p->OrderByDescending(function($k, $v){ return $v['date']; });
    assert('key($result) == "key_999"');

    $p = new Plinq($testArray);
    $result = $p->OrderBy(function($k, $v){ return $v['string']; });
    assert('key($result) == "key_0"');
}
(Про $testArray не спрашивайте — приводить здесь не буду. Скажу только, что это такая однострочная фиговина длиной в 138057 символов, которая заставляет мою IDE поскрипывать шестерёнками. Природу массива не изучал.)

Есть что-то, отдалённо напоминающее документирующие комментарии. Из них можно узнать, что функция Diff «Finds different items» (и ведь не скажешь, что соврал).

Итог: полезных ископаемых нет, воды нет, растительности нет, населена… да ничем не населена. К использованию не рекомендуется.

Всё. Кино окончено. Порты LINQ на PHP кончились.

...Ну ладно, ладно, чтобы не завершать на столь печальной ноте, упомянем библиотеку, которая и не порт LINQ вовсе. Но на безрыбье и щука — рыба.

Underscore.php

Писалось человеком, которому очень понравилась Underscore.js (спасибо, ваш К.О.).

Что такое Underscore.js? Это библиотека, которая реализует всякую функциональщину в JavaScript. Вам что-то говорят имена map, filter, reduce, flatten, times? Если нет, то придётся запомнить, что map — это select, reduce — это aggregate, times — это repeat и т.д. В целом сильно напоминает LINQ, только им не является. Ленивых вычислений нет.

Underscore.php — это, соответственно, порт Underscore.js на PHP. Библиотеки настолько родственны, что даже номера версий синхронизированы.

Документирующих комментариев нет вообще. Есть обычные, краткие, в наличии над каждым методом. Неудобно, конечно, но жить можно. Ещё есть документация на сайте, ощутимо более вменяемая.

Тесты есть: как скопированные из Underscore.js, так и дополнительные.

Качество кода хромает. Статические функции как статические не помечены. Коллбэки вызываются как $f(), то есть про массивы-указатели на функции можно забыть (есть в PHP такая недоработочка). «Лямбдовые» строчки не поддерживаются.

Ваш код будет выглядеть примерно так:
$numbers = __($numbers)->chain()
                       ->select(function($n) { return $n % 2 === 0; })
                       ->reject(function($n) { return $n % 4 === 0; })
                       ->sortBy(function($n) { return -$n; })
                       ->value();

Итог: другие названия методов есть, документирующих комментариев нет, тесты есть, лямбд нет. Рекомендуется к использованию, но только при острых позывах к функциональному программированию.

Окончательный итог

Табличка:



Итог: ждите вторую часть статьи, «Сами с усами».

Баги

Пока суть да дело, можете проголосовать за баги и фичи, которые сделают разработку в подобных библиотеках немного менее кошмарной.

PHP

  1. Iterator::key() разрешено возвращать только числа и строки.
    1. 45684 A request for foreach to be key-type agnostic
  2. Была фича с укорачиванием синтаксиса замыканий, причём прилагались патчи, анализ и прочее — оформивший фичу разработчик постарался на славу. Но фичу закрыли с результатом «нафиг надо». :-(

PHPStorm IDE

  1. Код PHP внутри строк
    • WI-3477 Inject PHP language inside assert('literal'), eval and similar
    • WI-2377 No autocompletion for php variables inside string with injected language
  2. Анализ PHP кода
    • WI-11110 Undefined method: Undefined method wrongly reported when using closures
  3. Комментарии PHPDoc
    • WI-8270 Error in PhpDoc quick documentation if {link} used twice in a line

Ссылки


P.S. Подскажите, пожалуйста, куда можно запостить аналогичную статью на английском.
Александр Прохоров @Athari
карма
106,0
рейтинг 0,3
Программист C#
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (55)

  • +3
    Было бы интересно увидеть цифры про производительность
    • +1
      Если вас беспокоит производительность, но несколько простых советов:
      • Не используйте строковых лямбд, только Closure. Каждая лямбда — это по сути create_function (eval).
      • Если используете строковые «указатели», то выберите библиотеку вообще без поддержки лямбд. Если есть поддержка, то на каждом «указателе» будет вызываться strpos.
      • Не используйте библиотек с ленивыми вычислениями. Кодить нужно будет аккуратнее, у каких-то операций увеличиться цена, но не будет лишних вызовов функций, у которых цена далека от нулевой.
      • Если вам всё ещё не хватает скорости, то LINQ не для вас.
      • Если вам всё ещё мало скорости, то пора смотреть в сторону C#/Java/C++.

      В целом, библиотека типа Underscore.php не должна сильно сказываться на скорости. Это по большей части обёртка на стандартными функциями, оверхед небольшой.
  • +1
    Хотелось бы узнать на сколько данные библеотеки лучше/быстрее/функциональнее, чем более известные решения, к примеру, Zend_Db_Select?
    • 0
      Эм. Zend_Db_Select, как я понял из документации, работает с БД. Предложенная реализация LINQ работает с объектами в памяти. Как их можно сравнивать, мне не ясно.
      • 0
        А если указать Zend_Db_Adapter_Array? *мечты… мечты*
        • 0
          не Указать, а Написать :( Пятница-Тринадница
    • +1
      В текущем состоянии для замены существующих DAL и ORM эти библиотеки непригодны, ИМХО. Они представляют интерес только в виде LINQ to objects. Возможно, стоило явно упомянуть это в статье.

      В то, что эти библиотеки дорастут до генерации качественного SQL, как оригинальный .NET LINQ, лично мне верится с трудом. Во-первых, PHP не предоставляет синтаксических деревьев, что заставляет парсить PHP вручную, а это уже закат солнца вручную. Что разрабы PHP предоставят синтаксические деревья наружу в PHP — ну не верю, не в этой реальности. Во-вторых, решение задачи генеарации SQL по синтаксическому дереву в лоб приводит к совершенно невменяемым и нечитаемым запросам. Нужно очень много оптимизаций, рассмотрения частных случаев для каждого движка баз — объём практически неподъёмный. Это делается не в одиночку, а серьёзными командами.

      Так или иначе, этим никто не занимался. Единственная поделка с доступом к базам — это PHPLinq, от которого LINQ и не пахнет, это просто альтернативная запись SQL запросов, DAL. PHPLinq не пользовался, поэтому сказать об удобстве ничего не могу.
  • 0
    А толку-то. Это LINQ2Objecs. Причём без нормальной поддержки лямбд смотрится убого и чужеродно, даже реализации на C++0x выглядят органичнее. А адекватный LINQ2SQL без поддержки со стороны компилятора/интерпретатора чего-то вроде деревьев выражений всё равно не реализовать.
  • +2
    Мдааа, такой код «синтаксическим сахаром» не назовёшь. В отличие от LINQ
    • +1
      Вот вы минусуете а человек дело говорит. Смысл LINQ именно в синтаксисе, он чужеродный, но прекрасно встраивается в язык, он визуально приятный, в отличии от этого набора методов.
      • 0
        Если для вас LINQ — это исключительно «from a in b where c select d», то у вас очень и очень ограниченные представления о LINQ. Например, я и в C# использую функциональную запись, а не «запросы». LINQ — это НЕ синтаксический сахар.
        • –1
          Да, для меня LINQ именно красивый и функциональный синтаксический сахар, я привык работать с реляционными бд и мне очень понравился он именно с этой стороны. То что он может так же и с обьектами работать и другими наборами данных, это только плюс. А вот о моих представлениях я бы на вашем месте не рассуждал так открыто, можете ошибаться прилюдно.
          • +1
            Смысл LINQ и правда в синтаксисе, ведь вместо него можно использовать обычные foreach...if...else.

            Но! LINQ — не «сахар» вокруг for...if...else. Это совершенно самобытная часть языка/фреймворка, которая меняет сам стиль написания кода с императивного на функциональный.
      • +1
        Смысл LINQ именно в синтаксисе

        Ну не скажи, я например использую только синтаксис методов. Синтаксис запросов действительно немного чуждо выглядит, плюс в нем доступны не все возможности LINQ.
  • +1
    А зачем? Я как бывший .NET-чик, конечно понимаю что LINQ это круто, модно и бла бла бла. Но вот сомневаюсь, что бы он был действительно нужен в PHP. С моей точки зрения LINQ в PHP это избыточная функциональность, которая привносит дополнительную вычислительную нагрузку в PHP скрипты. И опять таки вопрос зачем? Для того что бы было удобно программисту и все? Я например люблю что бы быстрее работало и при этом потребляло меньше ресурсов. По этому считаю что лучше уж пусть будет на несколько строчек длиннее код, зато работать будет лучше :)
    • +9
      Язык LINQ мне кажется очень выразительным. По-моему, «where()->select()» не идёт ни в какое сравнение с «foreach — if — a[]». Первое анализируется человеком за секунду, во второе надо вчитываться.

      Я, как дотнетчик и похапэшник, не понимаю обсуждения скорости работы скриптов PHP. PHP с точки зрения скорости работы — это курам на смех. Точка. Если нужна скорость разработки — используется PHP, Ruby и т.п. Нагрузку побеждаем железом. Если нужна скорость работы приложения — используется C#, Java, в особо тяжёлых случаях C++. Нагрузку побеждаем и железом, и силой мыли.

      Нет в дотнете и джаве ничего такого, что замедляло бы скорость разработки настолько, что экономия на паре вызовов функций в PHP была бы оправдана. (Что фейсбук, вконтакт и википедия написаны на PHP и остаются им верны — это их половые трудности. Пересесть с иглы — это дорого.)

      В общем, каждой задаче — свой инструмент. На читаемости кода лучше не экономить.
      • +1
        PHP с точки зрения скорости работы — это курам на смех. Точка. Если нужна скорость разработки — используется PHP

        Прошу уточнить
        • +5
          PHP, Ruby и т.п. — низкая скорость выполнения кода, высокая скорость разработки
          C#, Java и т.п. — высокая скорость выполнения кода, средняя скорость разработки
          C++, C и т.п. — максимальная скорость выполнения кода, низкая скорость разработки

          Все отношения — средние по больнице. Зависит от того, что разрабатывается, кем разрабатывается и т.п.
          • –1
            Да ладно Вам.
            Если поставить кэшер опкода, то PHP тоже быстро выполняется.
            А если еще все лежит на SSD, код написан грамотно, то единственное место, в которое можно упереться — работа с базой данных. Да, тут PHP не так уж и быстр, соглашусь.
            • +2
              Даже со всеми кэшерами PHP в разы медленнее компилируемых языков. Или я отстал от жизни, и появился компилятор? (Не, ну есть фейсбуковый ХипХоп, но он накладывает ограничения на немало вкусностей PHP.)

              Да и даже с компиляцией… Динамическая типизация весьма нехило сказывается на скорости относительно строгой типизации. И когда единственная коллекция для всех случаев жизни — это array.

              В общем, PHP медленный, что называется, by design. Можно сколько угодно ускорять, но C#, Java, C++ он не догонит никогда.
              • 0
                Не спорю на счет компилируемых языков, но на счет коллекций вот есть SPL. Да и чем array так плох?
                • 0
                  Есть на свете типизированные массивы, битовые поля, односвязные списки, двусвязные списки, хэш-таблицы… Как вы думаете, насколько эффективен один тип коллекции, который используется для всех остальных, оптимизированных для разных случаев?

                  SPL — это, конечно, прогресс, но пока небольшой.
                  • 0
                    Минус SPL в том, что его очень редко используют почему-то. Хотя буду честен, я его тоже недавно открыл :)
                    А так все перечисленное уже есть.
                    • 0
                      Минус SPL в том, что его видимо писали те же люди, что и саму корку PHP, из того, что навсегда запомнил при работе с ним — не доверяй документации, я очень злился когда узнал что RecursiveDirectoryIterator так и не умеет обходить директории рекурсивно.
                      • 0
                        Документация там вообще-то какая-то… никакая. Если древние функции ещё кое-как документированы, а самое важное можно найти в комментариях, то в SPL на каждый метод в лучшем случае жалкий однострочный комментарий.
              • –2
                Вы куда смотрите?!
                Кэшер опкода, если Вы не в курсе, это что-то вроде .pyc для php -_- (т.е. компилируются один раз, а потом просто выполняется байткод).

                Динамическая типизация?
                При грамотном использовании isset, empty и === вместо ==, то все шикарно…

                P.S. шарп, джава, плюсы немного из другой серии. Хотя шарп еще ладно, но последние два, имхо, не для вебдева, а чего-то более серьезного.
                • 0
                  На данный момент весь почти весь ентерпрайз — ява и веб. Сейчас уже мало кто пишет громозские монолитные приложения, куда ни глянь — несколько вебсервисов свзяанных друг с другом. Глянье хотя бы на Spring и посмотрите чем в будующем станет symfony.
                • +2
                  В .NET добавили тип dynamic (по названию ясно, что это такое). И вот почему-то код с этим dynamic работает ну очень ощутимо медленнее, чем с обычными типами. Наверное, у программистов в Microsoft руки кривые, сравнивать переменные не умеют…

                  «Строгого типизированного» сравнения больше-меньше в PHP, к слову, не существует. А при повсеместном использовании === ваш код обрастёт кастованием переменных. Это уже не похапэ получается…

                  Джава не для вебдева? Гхм. Вы только в людных местах такое не говорите, камнями забросают. :)
                  • 0
                    но последние два, имхо, не для вебдева, а чего-то более серьезного

                    • 0
                      Гхм. Вебдев — это несерьёзно? Такое точно на людях говорить не надо. :)
          • +1
            Да, и я бы не сказал, что вебдев на C# — средняя скорость разработки, в то время как PHP — высокая. Есть MVC, который позволяет разрабатывать с потрясной скоростью.

            А вот рельсам и правда не помешало бы скорости выполнения (особенно ощутимо после ASP.NET MVC). Но с точки зрения разработки — самая приятная штука.
        • 0
          hint:… скорости работы… !=… скорость разработки…
    • +2
      Так то оно так, но везде можно увидеть потерю производительности в угоду удобству. В легком коде сложнее сделать ошибку, легкий код проще поддерживать, легкий код быстрее писать. В контексте PHP минусов конечно многовато, но почему бы и нет для проектов, где производительность не особо нужна, но надо побыстрее все написать. Ну или для сишарпнутых, подрабатывающих пхпшным фрилансом :D
  • +1
    Я правильно понимаю, что собственно выражений-то нигде нет, так что прощай все применение LINQ к БД?
    • 0
      Есть попытки парсить строчки PHP вручную: в ныне покойном Phinq 2.0 и в ныне криво написанном PHPLinq. Со вторым можно поиграться, но он не LINQ, а DAL скорее. Для продакшена ни один из них не пригоден. Про перспективы этой затеи я отписался выше: habrahabr.ru/post/147612/#comment_4977062

      Вкратце: индейская народная изба в силу неповоротливости и узкого взгляда на мир у разрабов PHP.
  • +2
    echo Phinq::create($people)
      ->groupBy(function($person) { return $person->residence->region; })
      ->select(function($grouping) {
        $obj = new stdClass();
        $obj->people = $grouping;
        $obj->region = $grouping->getKey();
        return $obj;
      })->orderBy(function($obj) { return $obj->people->count(); }, true)
      ->aggregate(function($current, $next) {
        $count = $next->people->count();
        return $current . sprintf(
          "%d %s (%s) live in the %s region\n",
          $count,
          $count === 1 ? 'person' : 'people',
          $next->people->aggregate(function($current, $next) {
            if ($current !== null) {
              $current .= ', ';
            }
            return $current . sprintf('%s [%s]', $next->name, $next->residence->code);
          }),
          $next->region
        );
      });
    

    Язык LINQ мне кажется очень выразительным.

    Я бы не хотел бы поддерживать такой код, уже лучше чистый SQL + PDO драйвер, а ещё лучше noSQL решение поддерживать.
    • 0
      Не надо выдирать из контекста. :) Это пример с использованием замыканий, синтаксис которых в PHP ужасен. «Лямбдовые» запросы, которые поддерживаются многими библиотеками, позволяют писать запросы примерно так:

      from($categories)
          ->orderBy('$v["name"]')
          ->groupJoin(
              from($products)
                  ->where('$v["quantity"] > 0')
                  ->orderByDescending('$v["quantity"]')
                  ->thenBy('$v["name"]'),
              '$v["id"]', '$v["catId"]', 'array("name" => $v["name"], "products" => $e)'
          );
      

      К сожалению, цена такой красоты — семь вызовов create_function. Но если библиотеки LINQ получат достаточное распространение, то есть надежда, что разрабы PHP в конце концов добавят краткий синтаксис замыканий.
    • +1
      Ну, вообще-то, автор этот код привел как пример, при котором любой, кто знаком с LINQ «закатит глаза».
      А про выразительность — вообще из другого контекста, это про LINQ сам по себе (из C#).

      Зачем вы смешали эти две вещи в кучу?
  • +1
    Один вопрос — зачем?
    • 0
      1. Выразительность кода

      2. Неестественная тяга к функциональщине
      • 0
        1. Но в итоге получаем еще большую невыразительность, разве нет?
        Мне кажется, что чтобы такие вещи принести в PHP, нужно это все писать расширением, а не пытаться эффективно реализовать на самом языке.
        • +1
          Где невыразительность в этом коде?

          from($categories)
              ->orderBy('$v["name"]')
              ->groupJoin(
                  from($products)
                      ->where('$v["quantity"] > 0')
                      ->orderByDescending('$v["quantity"]')
                      ->thenBy('$v["name"]'),
                  '$v["id"]', '$v["catId"]', 'array("name" => $v["name"], "products" => $e)'
              );
          

          Читаемость у него точно такая же, как у дотнетового LINQ (если использовать функциональную запись, а не запросовую). Ну да, строчки. Но здесь среды разработки могут подтянуться. В PHPStorm для этого надо сделать всего ничего — сделать PHP «внедряемым» в строчки, как и все остальные языки (не знаю, что им помешало сделать так сразу). В конце статьи ссылки на фичи.

          Если реализовать расширением, то только скорость будет чуть выше, а возможности те же самые. С помощью расширения не добавить в язык поддержку синтаксических деревьев, это можно сделать только на ядрёном уровне. Да и если сделать расширение, то количество программистов, которые смогут применить библиотеку у себя в проекте, сократится раз эдак в 50.
          • +1
            Про невыразительность — да, строчки. Или многословность замыканий как на КДПВ.
            А так, даже не знаю. Возможно я уже очень привык к обычной записи через foreach и прочие array_filter и новый подход тяжелее воспринимается, просто потому что не видно старых конструкций. А может я просто слишком консервативный в этом смысле.

            В обычном php мне было бы видно сразу что происходит с данными, с какими именно частями и как они там оказались. И я даже могу это построчно отдебажить и проверить на каждом уровне. А тут, во-первых это новый синтаксис в котором прям вот сходу не поймешь что происходит и зачем. Лезем в доку, видим 50 методов… ух. Да, оно конечно выглядит прикольно, вроде так классно, но в вашем примере откуда-то взялась $v в строчках, непонятно как дебажить это дело. И вообще меня это пугает :)

            Про расширение я не имел ввиду скорость, хотя это тоже плюс. А разве для ядерного уровня нельзя написать расширение? Тот же xdebug (zend_extension же) или опкод кешеры они не там работают? Вроде есть еще runkit, который позволяет заменять встроенные функции на что хочется. Так что я тут не уверен, что с помощью расширения нельзя привнести короткий синтаксис или синтаксические деревья. Ну и количество программистов — спорно. memcache/memcached расширение же ставят и ничего, не умирают. Не говоря уже о банальных php_mysql и прочем.

            Я смотрю со своей колокольни, функциональщиной не интересовался, так что могу чего-то недопонимать в этой магии.
            • +1
              Если вы никогда не имели ничего общего с около-функциональным программированием, то выглядить код странновато будет, полагаю. Но это дело привычки.

              Дебажить… Дебажить это весьма нетривиально. Там по сути eval'ы — как их дебажить? :) Внутри библиотеки код не сказать, что очевидный (впрочем, во внутренности .NET LINQ тоже лазать не принято, хотя благодаря поддержке yield код там в разы более читаемый).

              Если уж совсем никак не разобраться, что происходит, то можно переписать запрос с помощью замыканий и подебажить их. И/или можно разбить запрос на части. Дебаг .NET LINQ так и происходит по сути.
  • 0
    > PHP на LINQ портировали
    • 0
      Ой. Исправлено.
  • +1
    Athari слов на ветер не бросает. Ждем обзора YaLinqo.
    • 0
      Таки спалили. :D
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Не могу предложить ничего лучше, чем справка по LINQ в MSDN. Правда там другие языки. И другой синтаксис запросов («запросовый», а не функциональный; первый в PHP невозможен без ядрёной поддержки). Надо бы написать хорошую вступительную статью по LINQ для тех, кто не пользовался .NET LINQ, но на это нужно дофига времени, которое никто не оплачивает.

      Вкратце: работа с данными. Всякий раз, когда вы пишете

      $array = array();
      for ($elements as $element) {
        if ($element['foo'] == 'bar') {
          $array[] = $element['baz'];
        }
      }
      return $array;
      

      вы просто вспоминаете, что то же самое можно записать по-другому, короче и выразительнее:

      return from($elements)->where('$v["foo"] == "bar"')->select('$v["baz"]')->toArray();
      

      В LINQ очень много методов, которые заменяют вложенные друг в друга десять раз for-if-for-if.
      • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        > «И другой синтаксис запросов»

        Ну почему же другой. LINQ — это и то, и другое. Чем больше нравится пользоваться — дело личных предпочтений. Мне, например, куда больше нравится запись в виде extension-методов к IEnumerable/IQueryable через точку.

        И никогда не нравилось «from c in collection» — неинтуитивно, сбивает с толку. Читабельно было бы «for c from collection ...» или что-то в этом духе.
        • 0
          Я о том, что в справке в первую очередь рассказывается о запросовой записи.

          В шарпе «неестественный» порядок сделан в угоду IntelliSense — так редактор может выводить максимум подсказок в процессе ввода запроса.
          • 0
            Я, разумеется, понимаю, что такая запись не просто так, но выглядит странно, на мой взгляд. Ну я и не переживаю — все равно никогда ей не пользуюсь.

            Окей, про справку понял :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.