📜 ⬆️ ⬇️

LINQ for PHP. Part 2. If the mountain does not go to Mohammed, Mohammed goes to the mountain

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:


Code example:
')
//     ,    , //   .      ,   . 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)' ); 

Implemented methods


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:

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:


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'; //     use \YaLinqo\Enumerable; //     use \YaLinqo\Enumerable as E; //   //      from,     Enumerable —   Enumerable::from(array(1, 2, 3)); from(array(1, 2, 3)); 

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:


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

  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

Link

Download Yet Another LINQ to Objects for PHP from GitHub

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

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


All Articles