LINQ

LINQ (Language Integrated Query) is a library from Microsoft for the .NET Framework. It is unique because it brought many nice features from the functional programming paradigm into the imperative object oriented world. The central element of LINQ, usually called LINQ-to-objects, is nothing but a set of higher order functions that operate on enumerations of objects, in a lazy fashion. Oxygen ports this part into PHP.

In the following example (in C# code), if a is an enumeration of integers, then the following code will return the sum of the squares of the first three even members:

// C# code a.Where( x => x % 2 == 0 ) .Take(3) .Select( x => x ^ 2 ) .Sum();

What is unique in the above code is that it says directly what we want as a result, and not how to produce it. It is simple: from the enumeration find the even numbers, take the first 3, square them, and add them. If we wanted to do the same thing in imperative fashion, that would look like this:

// PHP code $sum = 0; $count = 0; foreach ($a as $i) { if ( $i % 2 == 0 ) { $sum += $i * $i; if (++$count == 3) break; } }

The above code is typical and it is a mess. What we want to achieve is mixed with instructions of how to achieve it. In an analogy, it's like walking into a coffee shop and, instead of ordering a decaf caramel macchiato, say something like "well, first empty a pot, heat up the milk, and while there is more milk add it to the pot, then... (etc)".

Oxygen brings LINQ into PHP, by using native PHP constructs. Therefore, instead of enumerations, it uses the internal Traversable interface (Iterator and IteratorAggregate) and instead of lambda functions, it uses PHP's closures. So, the above example becomes like this:

from($a)->Where( function($x){ return $x % 2 == 0; } ) ->Take(3) ->Select( function($x){ return $x * $x; }) ->Sum();

The from() function wraps the traversable object into a LinqIterator which offers all the extra methods. The code is longer from the C# version and not quite as nice, but it is because PHP has choosen a rather verbose way to represent lambda functions. However, it makes the code much cleaner and provides a standard way to handle object enumerations.

Another feature unique in PHP, is that traversable objects always contain pairs of keys and values and the iteration is always done on both the value and the key. For this reason, all lambda functions passed to a LinqIterator traversable accept the key as an optional second argument. As a result, the following code is valid:

$a = array( 'a' => 1, 'b' => 2 ); echo from($a)->Select( function($value,$key){ return $key.'='.$value; } )->Implode(', '); // output: a=1, b=2

LINQ Methods

The rest of this chapter contains a brief presentation of the the methods of LINQ.

Select

Select is the projection method, equivalent to PHP's array_map. For example, the following code selects all titles from an array containing book objects:

$titles = from($books)->Select( function($book){ return $book->Title; } );

SelectMany

SelectMany is a projection method for sub-collections. The following code selects all chapters of all books in one joined traversable.

$chapters = from($books)->SelectMany( function($book){ return $book->Chapters; } );

Where

Where is the filtering method, equivalent to PHP's array_filter. The following code takes only the books by Tolkien:

$tolkien_books = from($books)->Select( function($book){ return $book->Author == 'Tolkien'; } );

There are also the shortcut methods WhereNotNull() and WhereKeyNotNull() which take no argument.

Take and Skip

Take and Skip take an integer argument and can be used to iterate just portions of the collection.

from($books) ->Skip(20) ->Take(10);

OrderBy

OrderBy and OrderByDesc iterate the traversable sorted by the passed hashing function. The hashing function has to return either a string or a number. To sort which more than one criteria, use the methods ThenBy and ThenByDesc.

$books_sorted_by_author = from($books) ->OrderBy( function($book){ return $book->Author; } ); $books_sorted_by_author_and_title = from($books) ->OrderBy( function($book){ return $book->Author; } ) ->ThenBy( function($book){ return $book->Title; } );

GroupBy

GroupBy returns a traversable of traversables grouped by the passed hashing function. The hashing function has to return either a string or a number. The following code returns a list of books for each author.

from($books) ->GroupBy(function($book){ return $book->Author; }) ->Select(function($author_books,$author){ return $author.': '.$author_books->Implode(', '); });

Unique

Unique returns a traversable containing only unique values. However, the values have to be strings or numbers. If this is not the case, if is also possible to pass a hashing function that produce a string or a number.

$all_authors = from($books) ->Select(function($book){ return $book->Author; }) ->Unique();

Union

Union joins together two traversables.

$history_and_fiction_books = from($books) ->Where( function($book){ return $book->Genre == 'History'; } ) ->Union( from($books)->Where( function($book){ return $book->Genre == 'Fiction'; } ) );

GetFirst and GetLast

These functions return the first and the last element of the traversable or null if there are no elements. There are also the similar functions GetFirstOr and GetLastOr that accept a default value, and the function GetFirstKey and GetLastKey that return the keys instead of the elements.

from($books)->GetFirst();

Aggregate functions

Aggregate functions reduce the traversable to a single value. There are the functions Count, Sum, Max, Min, Implode, Exists and ForAll.

// returns a number $number_of_books = from($books)->Count(); // returns a boolean $has_history_books = from($books)->Exists( function($book){ return $book->Genre == 'History'; }); // returns a string $all_titles_string = from(books)->Select( function($book){ return $book->Title; } )->Implode(', ');