SkillAgentSearch skills...

YaLinqo

Yet Another LINQ to Objects for PHP [Simplified BSD]

Install / Use

/learn @Athari/YaLinqo

README

YaLinqo: Yet Another LINQ to Objects for PHP

Coveralls Coverage Scrutinizer Code Quality Packagist Downloads Packagist Version GitHub License

Features

  • The most complete port of .NET LINQ to PHP, with many additional methods.
  • Lazy evaluation, error messages and other behavior of original LINQ.
  • Detailed PHPDoc and online reference based on PHPDoc for all methods. Articles are adapted from original LINQ documentation from MSDN.
  • 100% unit test coverage.
  • Best performance among full-featured LINQ ports (YaLinqo, Ginq, Pinq), at least 2x faster than the closest competitor, see performance tests.
  • Callback functions can be specified as arrow functions (fn($v) => $v), first-class callables (strnatcmp(...)) or any other PHP callables.
  • Keys are as important as values. Most callback functions receive both values and keys; transformations can be applied to both values and keys; keys are never lost during transformations, if possible.
  • SPL interfaces Iterator, IteratorAggregate etc. are used throughout the code and can be used interchangeably with Enumerable.
  • Redundant collection classes are avoided, native PHP arrays are used everywhere.
  • Composer support (package on Packagist).
  • No external dependencies.

Implemented methods

Some methods had to be renamed, because their names are reserved keywords. Original methods names are given in parentheses.

  • Generation: cycle, emptyEnum (empty), from, generate, toInfinity, toNegativeInfinity, matches, returnEnum (return), range, rangeDown, rangeTo, repeat, split;
  • Projection and filtering: cast, ofType, select, selectMany, where;
  • Ordering: orderBy, orderByDescending, orderByDir, thenBy, thenByDescending, thenByDir;
  • Joining and grouping: groupJoin, join, groupBy;
  • Aggregation: aggregate, aggregateOrDefault, average, count, max, maxBy, min, minBy, sum;
  • Set: all, any, append, concat, contains, distinct, except, intersect, prepend, union;
  • Pagination: elementAt, elementAtOrDefault, first, firstOrDefault, firstOrFallback, last, lastOrDefault, lastOrFallback, single, singleOrDefault, singleOrFallback, indexOf, lastIndexOf, findIndex, findLastIndex, skip, skipWhile, take, takeWhile;
  • Conversion: toArray, toArrayDeep, toList, toListDeep, toDictionary, toJSON, toLookup, toKeys, toValues, toObject, toString;
  • Actions: call (do), each (forEach), write, writeLine.

In total, more than 80 methods.

Usage

Add to composer.json:

{
    "require": {
        "athari/yalinqo": "^3.0"
    }
}

Add to your PHP script:

require_once 'vendor/autoloader.php';
use \YaLinqo\Enumerable;

// 'from' can be called as a static method or via a global function shortcut
Enumerable::from([1, 2, 3]);
from([1, 2, 3]);

Example

Process sample data:

// Data
$products = [
    [ 'name' => 'Keyboard',    'catId' => 'hw', 'quantity' =>  10, 'id' => 1 ],
    [ 'name' => 'Mouse',       'catId' => 'hw', 'quantity' =>  20, 'id' => 2 ],
    [ 'name' => 'Monitor',     'catId' => 'hw', 'quantity' =>   0, 'id' => 3 ],
    [ 'name' => 'Joystick',    'catId' => 'hw', 'quantity' =>  15, 'id' => 4 ],
    [ 'name' => 'CPU',         'catId' => 'hw', 'quantity' =>  15, 'id' => 5 ],
    [ 'name' => 'Motherboard', 'catId' => 'hw', 'quantity' =>  11, 'id' => 6 ],
    [ 'name' => 'Windows',     'catId' => 'os', 'quantity' => 666, 'id' => 7 ],
    [ 'name' => 'Linux',       'catId' => 'os', 'quantity' => 666, 'id' => 8 ],
    [ 'name' => 'Mac',         'catId' => 'os', 'quantity' => 666, 'id' => 9 ],
];
$categories = [
    [ 'name' => 'Hardware',          'id' => 'hw' ],
    [ 'name' => 'Operating systems', 'id' => 'os' ],
];

// Put products with non-zero quantity into matching categories;
// sort categories by name;
// sort products within categories by quantity descending, then by name.
$result = from($categories)
    ->orderBy(fn($cat) => $cat['name'])
    ->groupJoin(
        from($products)
            ->where(fn($prod) => $prod['quantity'] > 0)
            ->orderByDescending(fn($prod) => $prod['quantity'])
            ->thenBy(fn($prod) => $prod['name'], 'strnatcasecmp'),
        fn($cat) => $cat['id'],
        fn($prod) => $prod['catId'],
        fn($cat, $prods) => [
            'name' => $cat['name'],
            'products' => $prods
        ]
    );

// More verbose syntax with parameter names (PHP 8.0+)
// and first-class callables (PHP 8.1+):
$result = Enumerable::from($categories)
    ->orderBy(keySelector: fn($cat) => $cat['name'])
    ->groupJoin(
        inner: from($products)
            ->where(predicate: fn($prod) => $prod['quantity'] > 0)
            ->orderByDescending(keySelector: fn($prod) => $prod['quantity'])
            ->thenBy(keySelector: fn($prod) => $prod['name'], comparer: strnatcasecmp(...)),
        outerKeySelector: fn($cat) => $cat['id'],
        innerKeySelector: fn($prod) => $prod['catId'],
        resultSelectorValue: fn($cat, $prods) => [
            'name' => $cat['name'],
            'products' => $prods
        ]
    );

print_r($result->toArrayDeep());

Output (compacted):

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 )
        )
    )
)

Versions

<!--suppress HtmlDeprecatedAttribute --> <table> <tr> <th>Version</th> <th>Status</th> <th>PHP</th> <th>Notes</th> <tr> <td colspan=4><h3><b>1.x</b> (2012) <tr> <td valign=top>1.0−1.1 <td valign=top>legacy <td valign=top>5.3−7.4 <td><ul> <li>Manually implemented iterators <tr> <td colspan=4><h3><b>2.x</b> (2014) <tr> <td valign=top>2.0−2.4 <td valign=top>legacy <td valign=top>5.5−7.4 <td><ul> <li>Rewrite using PHP 5.5 generators <li>Causes deprecation warnings in PHP 7.2+ due to use of <code>create_function</code> <tr> <td valign=top>2.5 <td valign=top>maintenance <td valign=top>5.5+ <td><ul> <li>Switched from <code>create_function</code> to <code>eval</code> for string lambdas <li>May cause security analysis warnings due to use of <code>eval</code> <tr> <td colspan=4><h3><b>3.x</b> (2018) <tr> <td valign=top>3.0 <td valign=top>maintenance <td valign=top>7.0+ <td><ul> <li>Abandoned rewrite with performance improvements <li>Released 7 years later with most of the performance-related changes dropped <li>May cause security analysis warnings due to use of <code>eval</code> <tr> <td colspan=4><h3><b>4.x</b> (2025) <tr> <td valign=top>4.0 <td valign=top>planned <td valign=top>8.0+(?) <td><ul> <li>Strong types everywhere, string lambdas nuked from existence </table>

Breaking changes

Version 1.x → 2.x

  • Minimum supported PHP version is 5.5.
  • Collections Dictionary and Lookup were replaced with standard arrays.

Version 2.x → 3.x

  • Minimum supported PHP version is 7.0.
  • Type hints were added to parameters of some functions (ofType, range, rangeDown, rangeTo, toInfinity, toNegativeInfinity, matches, split). There may be edge cases if you rely on passing incorrect types of arguments.

Legacy information

Legacy features

  • (Versions 1.0−2.5) Callback functions can be specified as “string lambdas” using various syntaxes:
    • '"$k = $v"' (implicit $v and $k arguments, implicit return)
    • '$v ==> $v + 1' (like a modern arrow function, but without fn and with a longer arrow)
    • '($v, $k) ==> $v + $k' (explicit arguments, implicit return)
    • '($v, $k) ==> { return $v + $k; }' (explicit arguments, explicit return within a block)

[!NOTE]

Before arrow functions were added in PHP 7.4, the choice was between the ridiculously verbose anonymous function syntax (function ($value) { return $value['key']; }) and rolling your own lambda syntax (like $v ==> $v["key"]). This is why “string lambdas” were a necessity at the time.

[!CAUTION]

When using legacy versions of YaLinqo and PHP:

  1. You MUST NOT[^1] use user-provided strings to construct string lambdas. This directly opens you to passing to user-provided strings to eval, which is literally the worst thing you can do security-wise.
  2. You SHOULD NOT[^1] dynamically construct string lambdas in general, even if it seems convenient.
View on GitHub
GitHub Stars451
CategoryDevelopment
Updated14d ago
Forks40

Languages

PHP

Security Score

100/100

Audited on Mar 15, 2026

No findings