📜 ⬆️ ⬇️

LINQ for PHP. Part 1. I blinded him from what was, and then what was, then fell in love

A tale about how LINQ ported to PHP. Comparison of currently existing libraries with tablets, but without graphs - is attached.

Picture code to attract attention (do not wait for pictures!)
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 ); }); 
Who saw C # or any functional language - at the sight of this masterpiece roll their eyes (if they do not previously fly out of orbit). And, probably, will be right. But you can still like this:
 $lowNums = from('$n')->in($numbers)-> where('$n < 5')-> store($digits)->into('digits')-> select('$digits[$n]'); 
You have just seen two animals from the zoo of libraries porting LINQ to PHP. LINQ is actually Language Integrated Query, that is, SQL-like queries integrated into the language. In C #, LINQ relies on syntax trees and has two forms of writing, but since PHP does not have to wait for similar baubles for the next millennium, we assume that LINQ is only a library with SQL-like methods.

There are quite a few libraries porting LINQ to PHP. If only one of them actually ported it ... But more on that later, but for now, in alphabetical order, consider all the available alternatives. Reading is assumed either sequential or from the end.
')

LINQ for PHP

It was written by a person who is ready to remake any language into his favorite (it so happens that for him is C #), however strange his code may look. The first doubts sneak into the soul at the sight of 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 />'; } ... 
You assume that with this approach it will be difficult to distinguish a ported LINQ from the original? And you will be right. It was (C #):
 from n in new int[] { 1, 2, 3 } where n % 2 == 0 select n * 2; 
It became (PHP):
 from('$n')->in(array(1,2,3))->where('$n % 2 == 0')->select('$n *2'); 
Looks pretty.

Lyrical digression. Why lines? The code from them in <my favorite IDE> is not highlighted the same! What about refactoring? But what about ochepyatki? No way. There is no light, there is no refactoring, ochepyatki rule the feast. The syntax of closures in PHP is so verbose (compare x => x + 1 with function ($x) { return $x + 1; } ) that almost all LINQ port developers invent their “string lambdas”. Somewhere you can use closures, somewhere - pock-up "pointers to functions" (strings 'strlen' and arrays array($object, 'methodName') ), somewhere - "lambdas", somewhere - and and another, and third. All options are available in LINQ for PHP.

Let's look at the library. “Meat” is hidden in the LinqForPhp_Objects_Sequence :: doIteration method: when you call getIterator (), all operations collected in the operations array are executed sequentially. The operations are performed sequentially, without chain laziness, that is, the where () -> any () call will filter the entire sequence. After the first call, the result is cached.

The author had the urge to add PHPDoc comments, but it was not long enough: the fifth part of the code is documented at best. And the PHPDoc author syntax niasilil has no syntax - no param has an argument name.

Implemented standard methods: Aggregate, All, Cast, DefaultIfEmpty, Except, FirstOrDefault, GroupBy, OrderBy, Reverse, Single, Take, etc. Total about 50 pieces. There is a conversion to the List, Dictionary - ports of pre-Internet collections, in which part of the methods for some unknown reason throws NotImplementedException, and offsetExists is implemented as array_key_exists($index) (get a grasp). The answer to the question, why the X needs these collections, I leave it to the readers.

What the IDE didn’t have, it feels very good - PhpStorm instantly highlights outright bugs. Tests in pieces: zero point zero.

The result: a curious syntax, no lazy computations, no tests, no documentation, there are some fucked collections, a lot of bugs. Not recommended for use.

Phinq

It was written by a person who believes in a strange bright future. All arguments accepting functions are limited to Closure; comparers - EqualityComparer interfaces, etc .; collections have an array. It looks like this:
  public function groupJoin(array $collectionToJoinOn, Closure $innerKeySelector, Closure $outerKeySelector, Closure $resultSelector, EqualityComparer $comparer = null) 
At first glance, it is logical, but then you recall that ... In addition to closures, there are native pokapeshnye "function pointers" in the form of strings and arrays [object, method name], and they suddenly go to the forest. In the EqualityComparer interface, there is only one equals method (getting hashes is not: this is the care of the internals of native popping associative arrays). And for the sake of every comparison, if you please, create a new class; In groupJoin, it is logical to transfer the results of executing queries to Phinq, and their type is not at all array. Both SPL interfaces such as Iterator and IteratorAggregate are also suddenly not arrays. As long as you stay within the examples with numbers and from-where-select, everything is fine. As you face the harsh reality - it becomes hard.

All methods have PHPDoc documentation. Honestly written independently for each method. Well, almost honestly - in some places a copy-paste is felt, which leads to a lie:
 /** * 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(... 
Did you understand what the function does? What does resultSelector accept and return? If you understood, you did not read the documenting comment above, but simply used GroupJoin in C # once.

Standard methods are implemented, there are 50 pieces in total, but they are somehow lazily implemented. Single for some reason throws an exception if the only item in the collection is null. Arrays for some reason, information is thrown about the keys. Iterators are converted into arrays immediately when creating a Phinq object (also without information about the keys, of course). The only way to get something with keys on output is to convert it into a Dictionary collection, which has an implementation of offsetGet and offsetSet with an O (N) price. Thank Gods, unlike the previous library, at least the List is not implemented (there is no method toList, respectively).

All the laziness of the calculations, as in the case of the previous library, is reduced to caching the result.

What is good in the library? There are tests. Hooray. Superficial, of course. At best, a couple of pieces per method. Without any boundary cases. But there are tests. It's good.

The bottom line: there are brutal restrictions on the types of arguments, there are no keys, there are superficial tests, there are few superficial documents, there are few bugs, there is a damned collection. Not recommended for use.

Phinq 2.0

It was written by a person who was disappointed in a bright future: hello to string lambdas, while restrictions on function arguments. Although EqualityComparer with the only method is still with us. From above we will season with conversion in SQL for creation of queries of SQL. PHP parsing by PHP with exceptions like "Phinq requires the unary boolean operator ('!'). We will write for a long time ...

... And, of course, we will not add. On rewriting the old Phinq in a new way, the author hammered a nail. A branch of /branches/2.0 in SVN on a distant server is the only mention of a bright beginning and a sad end.

The result: there is nothing, we pass by. Not recommended for use.

PHPLinq

It was written by a person who believes that someone needs another DAL, and who vaguely imagines the possibilities of the original LINQ. In its understanding, the functions where, orderBy, select, skip, take and the rest can be called in any order that does not matter, because the order of their use is given tightly (and, by the way, is not documented anywhere). In his understanding of the closure - this is no one needed newfangled bauble. In his understanding of single and first - this is exactly the same thing. In its understanding, tests are PHP scripts with a call to print_r. In its understanding, documenting comments are a copied method name and an enumeration of argument types. In its understanding, project documentation is a class diagram in the format for VisualStudio.

Of course, I can tell you about support for LINQ to Objects, LINQ to ZendDb, LINQ to Azure, LINQ to MS SQL, LINQ to MySQL, LINQ to SQLite. But who needs all this diversity, if this library is low-grade code that has no relation to functional programming in general, nor to LINQ in particular, without documentation, without tests?

The bottom line: an epic LINQ to * number, an epic lack of everything else. Not recommended for use.

(The attentive reader, probably, is already beginning to wonder: when will there be something recommended? When? We are waiting, we hope ... we believe. And we read further.)

Plinq

It was written by a minimalist man who really wanted to have LINQ in PHP, but who was very lazy about coding. Therefore, a little more than 20 methods are supported, among which there is neither ThenBy or Aggregate. There are no lazy calculations at all. There are no line lambdas - only closures, only hardcore (given the lack of restrictions on the type of arguments and the current bugs in PHP, the string "function pointers" should also work). Passing closures to aggregator functions is a must. Most of the Plinq methods are wrappers over standard functions, plus conversion between arrays and iterators back and forth.

What is good in the library? There is a semblance of tests - on a pair of assertions per function. It looks like this:
 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"'); } 
(Do not ask about $ testArray - I will not give here. I can only say that it is such a one-line figovin with a length of 138057 characters, which causes my IDE to creak with gears. I did not study the nature of the array.)

There is something that remotely resembles documenting comments. From them you can learn that the Diff function “Finds different items” (and you cannot say that you lied).

Bottom line: there are no minerals, no water, no vegetation, no populated ... no populated area. Not recommended for use.

Everything. The movie is over. LINQ ports in PHP are over.

... Well, okay, okay, so as not to end on such a sad note, we mention the library, which is not a LINQ port at all. But on bezrybe and pike - fish.

Underscore.php

It was written by a person who really liked Underscore.js (thanks, your KO).

What is Underscore.js? This is a library that implements any functional in JavaScript. Do the names map, filter, reduce, flatten, times tell you something? If not, you'll have to remember that map is select, reduce is aggregate, times is repeat, and so on. In general, strongly resembles LINQ, only it is not. There are no lazy calculations.

Underscore.php is, accordingly, the Underscore.js port in PHP. Libraries are so related that even version numbers are synchronized.

There are no documenting comments at all. There are the usual, brief, available over each method. Uncomfortable, of course, but you can live. There is also documentation on the site, significantly more sane.

There are tests: both copied from Underscore.js, and additional.

The quality of the code is lame. Static functions are not marked as static. Callbacks are called as $ f (), that is, you can forget about arrays-pointers to functions (there is such a flaw in PHP). "Lambda" lines are not supported.

Your code will look something like this:
 $numbers = __($numbers)->chain() ->select(function($n) { return $n % 2 === 0; }) ->reject(function($n) { return $n % 4 === 0; }) ->sortBy(function($n) { return -$n; }) ->value(); 

The result: there are other method names, there are no documenting comments, there are tests, there are no lambdas. Recommended for use, but only for acute desires for functional programming.

The final result

Plate:



Bottom line: wait for the second part of the article, "Themselves with a mustache."

Bugs

While the point is, you can vote for the bugs and features that will make the development of such libraries a little less nightmarish.

Php

  1. Iterator :: key () is allowed to return only numbers and strings.
    1. 45684 A request for key-type agnostic
  2. There was a feature with shortening the syntax of closures, with patches attached, analysis, etc. - the developer who issued the feature tried its best. But the feature was closed with the result "nafig necessary." :-(

PHPStorm IDE

  1. PHP code inside lines
    • 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 code analysis
    • WI-11110 Undefined method: Undefined method wrongly reported when using closures
  3. PHPDoc Comments
    • WI-8270 Error in PhpDoc quick documentation if used in a line

Links


PS Tell me, please, where can I post a similar article in English.

Source: https://habr.com/ru/post/147612/


All Articles