Lithium - Diving into the core, Collection
Taking us into another episode of “Lithium – Diving into the core” is the Collection object which is, possibly, one of the most undervalued classes in the entire library. With a little bit of luck you’ll come out of this post with an inkling of just how integral this bit of code is to everything you do in the library.
Purpose
The Collection object is, you guessed it, a means of storing a collection of information. While you would usually delegate such responsibility to an array, by using an object we get a lot of extra functionality without sacrificing its array nature… yes, array nature.
SPL
The Standard PHP Library, or SPL, is a PHP 5.0+ extension which allows you to do a lot of really neat things in PHP, not the least of which is the ability to treat an object as though it were an array.
Let that sink in for a second. You can treat an object as though it were an array. Makes sense actually, why shouldn’t you be able to iterate over an object in the same manner you could an array? Well, with SPL you can.
This is useful to note because the Collection object and all of its children implement just such functionality. This lets us reuse the entirety of PHP’s array functions on the object along with the power of, well, objects.
(If you haven’t taken the time to check out the SPL and all it has to offer, I’d really recommend doing that. You’ll learn some neat things in the process)
Usage
For the most part you probably won’t ever have to use the Collection object directly, many parts in the library that return data will give you one automatically.
$posts = Posts::all(); foreach ($posts as $post) { /* Work with the record */ } var_dump(is_array($posts)); // Nope var_dump(is_object($posts)); // Yep!
One of the most common places you’ll run into the Collection object is when dealing with a model’s recordset. Note that running is_array against it will return false — this is because even though we can treat it like an array, it isn’t one. If you have any checks in your views or such that use is_array you may want to think about changing those out for something like this:
if (is_array($posts) or is_a($posts, '\ArrayAccess')) { /* We can treat it like an array */ }
What we’re doing here is allowing the $posts variable to be either a full-fledged array or an object which implements ArrayAccess which is the interface that provides the ability to iterate an object as an array.
Methods
Because of this whole array access functionality the object has to keep a handful of methods around to tell PHP how to properly access it as one. You can safely ignore these as they’re mostly for PHP.
Not to fear though, the Collection object still has a couple of tricks up its sleeves that are worth noting.
Manipulation
$posts = Posts::all(); /** * Note that since `$posts` is a Collection object full * of record objects, in our closure we will get the actual * record object! */ $myPosts = $posts->find(function($post) { return $post->author == 'Joe'; });
Here’s the find method for example. It wraps around the array_filter method with the provided callback and, when it’s all done with its filtering, will hand you back a brand new Collection object with the values you wanted.
This is really useful for retrieving a specific subset of records that you’ve already retrieved, that way you can save a hit against your database.
The class also has map, a wrapper around the array_map function that, like array_filter, will return you a new Collection instance with the updated values.
Last but not least is the sort method which does exactly what you think it does. The first argument for the method is a callable argument which the instance will use to sort itself.
Formatting
While the Collection object is a great place to store information, it knows that you may need to, at some point, change its data out into another format. For that it provides the to method.
$posts = Posts::all()->to('array'); var_dump(is_object($posts)); // Nope. var_dump(is_array($posts)); // Sure is.
Collection comes with one formatting built in, array, which will simply return you an array of its data. This is handy when you absolutely need to have an array of your data.
We’re not done there yet though. The formatting in Collection is a sort of “dumb” object in that it delegates the actual process of formatting somewhere else, this means we can register our own. From the source:
Collection::formats('json', function($collection, $options) { return json_encode($collection->to('array')); });
Now if we did $posts->to('json'); we’d get a lovely string of JSON data which represents our original data. Pretty nice, huh?
Caveats, notes
Sometimes you’ll think you’re working with a Collection instance but are instead dealing with an array. This is usually indicative of a call to Collection::data or Collection::to('array') somewhere in execution. Watch out for these.
While it’s tempting to put all of the possible ways you could format your data into Collection::formats, don’t go overboard and forget that it is still the primary job of your view to handle any appropriate translation of data into the requested format.
Closing
Again, hopefully this has been helpful to illustrate the functionality of the Collection object and the lithium library as a whole. As always feel free to comment here, on Twitter, IRC, etc, if you have any questions or comments!