As you can see from my previous article
comparing LINQ libraries for PHP , there are many libraries and few qualities: lazy calculations are not implemented in any library, tests are in half the cases, callback types are limited, and sometimes it is not known what stands out for LINQ . So I wrote my library. Meet:
YaLinqo - Yet Another LINQ to Objects for PHP
Opportunities:
- The most complete .NET LINQ port in PHP, with many additional methods. Some methods are missing, but work is underway. In total, more than 70 methods have been implemented.
- Lazy calculations, text exceptions and more, as in the original LINQ.
- Detailed PHPDoc documentation for each method. The text of articles is adapted from MSDN.
- 100% coverage with unit tests.
- Callbacks can be specified by closures, “function pointers” in the form of strings and arrays, and string “lambdas” with support for multiple syntaxes.
- Keys are given as much attention as they are to values: transformations can be applied to both; most callbacks accept both as input; keys, if possible, are not lost during conversions.
- Minimal invention of bicycles: Iterator, IteratorAggregate, etc. are used for iteration (and they can be used along with Enumerable); exceptions are used whenever possible by native pokapeshnye, etc.
Code example:
')
Implemented methods
- Generation: cycle, emptyEnum (empty), from, generate, toInfinity, toNegativeInfinity, matches, returnEnum (return), range, rangeDown, rangeTo, repeat, split;
- Projection, filtering: ofType, select, selectMany, where;
- Grouping, connection: groupJoin, join, groupBy;
- Aggregation: aggregate, aggregateOrDefault, average, count, max, maxBy, min, minBy, sum;
- Sets: all, any, contains;
- Pajination: elementAt, elementAtOrDefault, first, firstOrDefault, firstOrFallback, last, lastOrDefault, lastOrFallback, single, singleOrDefault, singleOrFallback, indexOf, lastIndexOf, findIndex, findLastIndex, skip, skiphour, yourIndexOf, findIndex, findLastIndex, skip, skip, welcome, skiphour, yourIndexOf, findIndex
- Convert: toArray, toArrayDeep, toList, toListDeep, toDictionary, toJSON, toLookup, toKeys, toValues, toObject, toString;
- Actions: call (do), each (forEach), write, writeLine.
Example
Now consider the above example in more detail. In fact, there are several options for recording this query: using closures and using string lambda. Lambda also has two syntaxes: you can use the default variable names (v and k for the value and the key, respectively), you can set meaningful ones.
Initial data (either from the database, or from some JSON service, or “iron” constants, or some other source):
$products = array( array('name' => 'Keyboard', 'catId' => 'hw', 'quantity' => 10, 'id' => 1), array('name' => 'Mouse', 'catId' => 'hw', 'quantity' => 20, 'id' => 2), array('name' => 'Monitor', 'catId' => 'hw', 'quantity' => 0, 'id' => 3), array('name' => 'Joystick', 'catId' => 'hw', 'quantity' => 15, 'id' => 4), array('name' => 'CPU', 'catId' => 'hw', 'quantity' => 15, 'id' => 5), array('name' => 'Motherboard', 'catId' => 'hw', 'quantity' => 11, 'id' => 6), array('name' => 'Windows', 'catId' => 'os', 'quantity' => 666, 'id' => 7), array('name' => 'Linux', 'catId' => 'os', 'quantity' => 666, 'id' => 8), array('name' => 'Mac', 'catId' => 'os', 'quantity' => 666, 'id' => 9), ); $categories = array( array('name' => 'Hardware', 'id' => 'hw'), array('name' => 'Operating systems', 'id' => 'os'), );
Actually, the task: to filter products with a non-zero amount, put in the appropriate categories. Sort products first by decrease in quantity, then by name. Categories sort by name. You should get the following (reformatted for short):
Array ( [hw] => Array ( [name] => Hardware [products] => Array ( [0] => Array ( [name] => Mouse [catId] => hw [quantity] => 20 [id] => 2 ) [1] => Array ( [name] => CPU [catId] => hw [quantity] => 15 [id] => 5 ) [2] => Array ( [name] => Joystick [catId] => hw [quantity] => 15 [id] => 4 ) [3] => Array ( [name] => Motherboard [catId] => hw [quantity] => 11 [id] => 6 ) [4] => Array ( [name] => Keyboard [catId] => hw [quantity] => 10 [id] => 1 ) ) ) [os] => Array ( [name] => Operating systems [products] => Array ( [0] => Array ( [name] => Linux [catId] => os [quantity] => 666 [id] => 8 ) [1] => Array ( [name] => Mac [catId] => os [quantity] => 666 [id] => 9 ) [2] => Array ( [name] => Windows [catId] => os [quantity] => 666 [id] => 7 ) ) ) )
Below is an example using closures from PHP 5.3. The longest entry, but the best support in a variety of IDEs.
from($categories) ->orderBy(function ($cat) { return $cat['name']; }) ->groupJoin( from($products) ->where(function ($prod) { return $prod["quantity"] > 0; }) ->orderByDescending(function ($prod) { return $prod["quantity"]; }) ->thenBy(function ($prod) { return $prod["name"]; }), function ($cat) { return $cat["id"]; }, function ($prod) { return $prod["catId"]; }, function ($cat, $prods) { return array("name" => $cat["name"], "products" => $prods); } );
Record using string lambda. To the left of the operator "==>" the names of the arguments, to the right - the return value.
from($categories) ->orderBy('$cat ==> $cat["name"]') ->groupJoin( from($products) ->where('$prod ==> $prod["quantity"] > 0') ->orderByDescending('$prod ==> $prod["quantity"]') ->thenBy('$prod ==> $prod["name"]'), '$cat ==> $cat["id"]', '$prod ==> $prod["catId"]', '($cat, $prods) ==> array("name" => $cat["name"], "products" => $prods)' );
Finally, the briefest record. If there is no operator "==>", then the default variable names are used: v for value, k for key, a and b for compared values, etc.
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)' );
(Doubtful) architectural solutions
Just because one-to-one original LINQ can not be copied: different languages, different features, different features. Therefore, often had to make a choice. How good or bad is to judge you. Discussion is welcome.
Keys
Let's start with the most dubious: the keys are declared an important part of the data. The reason: they are clearly present in native pohapeshnyh iterators, they are important in arrays, they are important when converting to JSON. In general, keys are commonly used in PHP, so I would not like to ignore them, as in some other libraries.
However, in the original LINQ there are no keys for the sequences, so you have to increase the number of arguments both for callbacks (now they can all work with the key, if possible), and for the LINQ methods themselves: resultSelector turns into resultSelectorValue + resultSelectorKey. However, in most cases, the developer does not need to think about it: callbacks can be passed with a smaller number of arguments, and all LINQ methods have default values ​​for the arguments of type resultSelectorKey.
Another problem with keys arises from the need to keep them everywhere. This means that by default, the elements will retain the same keys when sorting. PHP usually lists arrays in the order of adding elements, so conversion to an array should not be a problem, but it is not enough.
If you do not need information about the keys, then there are two simple ways to get rid of them:
- In the final operation, instead of toArray / toArrayDeep, call toList / toListDeep.
- Calling the toValues ​​method is equivalent to array_values, but is lazy, like select.
Order of arguments
The decision on the order of arguments in functions and callbacks is second in doubt. They always go in order from (theoretically) most used to least used. In callbacks, therefore, the first place is usually the value, and then the key, because value is almost always important, but the key is not. However, the order of the arguments may now be harder to remember. For example, in select, first there is a selection of a value, and in toDictionary, a selection of a key.
However, we, pohapeshnikam, no stranger to this disgrace - the whole language is speckled with a completely random order of arguments (the same needle and haystack).
Item Indexes
A non-obvious solution for those who used the original LINQ: methods such as indexOf, elementAt work with keys, rather than the numerical position of the element in the map. If you need a position, first call toValues ​​- the keys will become consecutive: 0, 1, 2, 3, etc. Also, there are no overloads for select type methods with callbacks that take the position of an element. Similarly, use toValues.
Arguments lambda
In the linq.js library, which I inspired when writing, all callback arguments are called $, $$, $$$, $$$$. In PHP, this does not happen. You can make a string conversion, but I would like to leave the code valid, even if it is inside a line. There is no need to call the arguments empty $ a, $ b, $ c. Therefore, it was decided to use names corresponding to the content:
- Normally v for value, k for key
- If there are several values, then v1 and v2
- If the value is a sequence, then e
- For battery during aggregation - a
- For comparison methods - a and b
- (Maybe something else forgot)
Disadvantage: you need to know the names. However, with detailed documentation this should not be a problem.
“Questionable” collections
There is no List class, the toList method returns the same as toArray, only with consecutive keys (0, 1, 2, etc.)
Dictionary class is. Originally conceived solely as a base for Lookup, but now it has become a separate full-fledged collection. Unlike ordinary arrays, the keys can be objects (possibly in the original LINQ). But in fact, in LINQ itself, key objects are far from being supported everywhere, because PHP does not allow using key objects in foreach. You can rewrite all the cycles, but how much the game is worth the candle is a question.
Class Lookup is. Returns a list of values ​​by key (or an empty array if there is no key).
Both collections support the toArray method, which returns an internal array.
MSDN Documentation
All methods copied help from MSDN, then adapted to the realities of the port. Somewhere descriptions are stolen from other projects. Somewhere - written independently. If you find errors - report.
In general, the certificate turned out very solid. Some methods are not sickly such articles.
Method Names
Some words in PHP are brazenly swallowed by the language itself, and in all registers. Even empty cannot be used as a method name. Therefore, where there are conflicts, the methods are renamed (in the list of methods at the beginning of the article, the original method names are given in brackets). In particular, run / forEach became call / each.
Exception Names
PHP has no built-in exceptions that exist in .NET. However, I tried to avoid creating unnecessary classes. So, instead of InvalidOperationException, UnexpectedValueException is used. In the end, the operation becomes invalid with unexpected values.
Stable sorting
Sort unstable. That is, when sorting the array
[[0,1], [1,0], [0,2]]
by the first element of the nested arrays, no one guarantees that
[0,1]
and
[0,2]
will follow each other in that order. The result may be both
[[0,1], [0,2], [1,0]]
, and
[[0,2], [0,1], [1,0]]
.
Why? Because in PHP there are no functions for stable sorting, and usort is used inside the library. Theoretically, the sorting can be made stable as in the original LINQ, but is it necessary? For stability, everyone will have to pay runtime and consumable memory. I decided that since we follow the “PHP paths”, then the instability should be the same as in PHP itself.
Other
Coverage unit tests - almost 100%.
License - BSD simplified (two-point).
Requirements - PHP 5.3.
Using:
require_once __DIR__ . '/lib/Linq.php';
Comparison with other libraries
For clarity, added to the table and more libraries for JavaScript. Their comparison will be in a separate article.

Legend as in Wikipedia, but with an additional value:
- red - in any gate
- yellow - the third grade is not a marriage
- green is the most
- blue - drop dead
I apologize for the English in the table. In Russian, too long worked.
thanks to me
He worked for free, no one will give money. If you feel an attack of generosity, you can simply vote for these features in PHP and PHPStorm. Perhaps they will notice and use the library will be more pleasant.
Php
- Iterator :: key () is allowed to return only numbers and strings.
- 45684 A request for key-type agnostic
- 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
- 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
- PHP code analysis
- WI-11110 Undefined method: Undefined method wrongly reported when using closures
- PHPDoc Comments
- WI-8270 Error in PhpDoc quick documentation if used in a line
Link
Download Yet Another LINQ to Objects for PHP from GitHubPS Tell me, please, where can I post a similar article in English.