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!

Lithium - Diving into the core, Adaptable

Bringing us to another installment of “Lithium – Diving into the core” is the Adaptable object. Adaptable gives us the the ability to provide an interaction interface without hard wiring the concrete implementation to perform said interaction.

While this may be a bit long winded, it’s really worth it as Adaptable is one of the most useful classes in the entire library. So grab some coffee, maybe pop open the repository to follow along and kick your feet up.

A great example of the Adaptable object is the Session object. A session, at it’s most basic, is a means to persist information about the current user across requests. As such it implements a read and write method, along with some others, to do its job. The beauty of Adaptable is that it actually allows the class to leave the heavy lifting to its adapters; in this case Cookie, Memory or Php

Implementation

Comparing the Adaptable and Session object you’ll notice that it’s the job of the adapter to actually implement the interface it will end up providing. For Session this is pretty straight forward: read, write, delete, clear and check — all things that make sense when working with a session.

When creating your own Adaptable subclass you’ll need to determine the methods that make sense when dealing with the problem in question. Here’s a couple examples off the top of my head to help illustrate this point:

  • Bank deposit, charge and balance — Credit card, debit card or check, these are all applicable actions.
  • Music Player play, stop, status, songs — iTunes, Winamp, VLC, etc. These are universally recognized actions.
  • SocialSite getPosts, addPost, message — Facebook, Twitter anyone?

The thing to take away from this is that the adapter must be able to implement all of the methods for the class that uses it. The goal is to let someone swap adapters at anytime and not to have the code notice anything different.

Configuration

Continuing with the Session object lets take a look at how it’s configured in app/config/bootstrap/session.php

Session::config(array(
    'cookie' => array('adapter' => 'Cookie', 'name' => $name),
    'default' => array('adapter' => 'Php', 'session.name' => $name)
));

Here we’re defining two configurations: cookie and default. One uses the Cookie adapter and the other Php. This tells the Session object which adapter to utilize when we use a specific configuration.

You’ll notice that there is some extra, specific configuration bits. This is normally ignored by the Session object (aside from “strategies”, more on that in a bit) and instead used to help configure the specific adapter.

Why would you need to pass options to an adapter? Well when writing your own adapters you may require certain things: API keys, URLs, API version, etc. The configuration is the place for those. It is not, however, the place to store data that should otherwise be passed in as an option when calling a method in the adapter.

Anatomy of a request

Alright, now lets get our hands dirty. To do the Adaptable object justice we’ll need to look at an actual method in the Session object. Bear with me, here.

public static function read($key = null, array $options = array()) {
    $defaults = array('name' => null, 'strategies' => true);
    $options += $defaults;
    $method = ($name = $options['name']) ? static::adapter($name)->read($key, $options) : null;
    $settings = static::_config($name);

    if (!$method) {
        foreach (array_keys(static::$_configurations) as $name) {
            if ($method = static::adapter($name)->read($key, $options)) {
                break;
            }
        }
        if (!$method || !$name) {
            return null;
        }
    }
    $filters = $settings['filters'] ?: array();
    $result = static::_filter(__FUNCTION__, compact('key', 'options'), $method, $filters);

    if ($options['strategies']) {
        $options += array('key' => $key, 'mode' => 'LIFO', 'class' => __CLASS__);
        return static::applyStrategies(__FUNCTION__, $name, $result, $options);
    }
    return $result;
}

(I’m going to gloss over certain areas that aren’t fundamentally important to this discussion — I recommend you open and read the Session.php file to dig in further)

Now if you didn’t look closely you may have missed it. The key here is:

$method = ($name = $options['name']) ? static::adapter($name)->read($key, $options) : null;

If you’ve told it which configuration to use it will call the read method on that adapter. The if block below it is triggered if you don’t tell it which configuration to use so that it can iterate over all of its adapters and try the read method on each.

That’s it!

Since each adapter knows if it’s capable of responding and will if it can, the Session object just needs to loop over and ask each to read from its concrete implementation; Php, Memory, Cookie, whatever. How they actually get their information is immaterial to the Session object itself.

The static::_filter call is your run of the mill trigger for any filters that may be waiting to listen for or manipulate the data. A neat thing to point out is that if there are filters attached, they don’t need to be attached to the concrete implementation (Php::read for instance) but instead can filter the Session class directly.

Now, about those other bits…

Strategies

Strategies are, in a way, Lithium provided filters that can operate on an action (Session::read in this case) regardless of what the concrete implementation may be.

Similar to adapter objects they may have their own configuration and should implement the same methods that the “base” class does when it needs to operate. A perfect example of this is the Encrypt strategy.

Like our adapter it implements read and write but unlike them it doesn’t care about check, delete, or any of the others. This is because there really isn’t any encryption or decryption to be done when you just want to delete an entry. The strategy should implement all the methods it needs to perform its task.

On the other hand, the Hmac strategy, which maintains a signature to confirm the validity of its data does need to tack into delete since the signature will change when data is removed.

Both Encrypt and Hmac need to store extra data away to do their jobs. For instance Hmac needs to write away the signature for future requests to confirm there has been no tampering.

$class::write('__signature', $signature, array('strategies' => false) + $options);

This is the line from the Hmac strategy to store away the signature its compiled and you’ll notice that it uses the write method to tuck its signature away. Again, because the $class (which is Session in this case) merely delegates off to an adapter, Hmac doesn’t care who that may be. To make sure other strategies don’t interfere with its operation or to cause an infinite loop, it tells Session not to trigger any strategies when performing its write

A quick note to bring things full circle. From our little SocialSite example above, it’s pretty easy to think up some strategies that could be used for all implementations:

  • UrlShortener When posting a message find any URLs and shorten them with a service.
  • Shortener Attempt to shorten a message by replacing common words with acronyms.
  • LinkReplacer When reading messages, find anything that looks like a URL and wrap it up in the appropriate HTML.

(These are just meant as crude examples)

Notes

  • A strategy should, more or less, be completely transparent to the operation and removing or adding one should not stop the class from performing its duties.
  • An adapter must be able to perform all the methods available to the application from the abstract object (Session for example)
  • An adapter should be interchangeable without impacting the application that may be using it aside from possibly requiring certain configuration.

Closing

whew — Hopefully this gives you a better grasp of the Adaptable object and its children and their purpose in Lithium. Now, your homework is to go crack open the Session.php and Cache.php files to try and apply your new knowledge to better understand them.

Extra credit for those who go read the strategies.

Lithium - Diving into the core, Environment

The Environment class is a core, static object which will inspect and determine the environment based on certain criteria from the current request. One of the neatest bits of functionality it gives us is that it will automatically configure any Adaptable class based on the current environment. This lets us do things like:

Cache::config(array(
    'userData' => array(
        'development' => array('adapter' => 'File'),
        'production' => array('adapter' => 'Memcache')
    )
));

Now, depending on the environment, when we attempt to use the Cache class against the “userData” configuration it will automatically use the appropriate environment.

Determining environment

While this is all well and good, how does the Environment class determine the current environment in the first place? Looking at the class we don’t see anything which is automatically triggered to actually determine which environment we’re in. That’s because there is none. It needs to be told about the current request.

If you’re using the framework repository as your basis (which you should be!) then the file you want to look at is app/config/bootstrap/action.php

Dispatcher::applyFilter('run', function($self, $params, $chain) {
    Environment::set($params['request']);

    foreach (array_reverse(Libraries::get()) as $name => $config) {
        if ($name === 'lithium') {
            continue;
        }
        $file = "{$config['path']}/config/routes.php";
        file_exists($file) ? include $file : null;
    }
    return $chain->next($self, $params, $chain);
});

The line in question is Environment::set($params['request']) which grabs the Request object from the Dispatcher and throws it at the class. From there the class will trigger the _detector method and figure out the current environment.

One thing to notice, and it’s a big one, is that your Environment object will not know where it “is” until this filter executes which means waiting for the Dispatcher::run method to fire. This also means that using Environment anywhere in your bootstrap, without being wrapped in a filter that comes after Dispatcher::run, will not give you the desired results.

Environment detector

When Environment::set gets the Request object it turns around and fires it at its Environment::_detector method which handles the dirty work of determining the environment and returning the name.

Inside _detector you’ll see that it checks for a static member variable called $_detector (more on that later) before returning its own, built-in, closure to handle determining the environment.

Custom detector

Now that we’ve got that out of the way, let’s see how we can define our own detector for the Environment class to use. This will let us create new environments or add criteria for choosing an environment.

Remember earlier when we mentioned the static member variable, $_detector? Well Lithium lets us override the built-in closure for determining the environment with our own closure by setting this variable. Here’s how…

Environment::is(function($request) {

    /* Determine which environment and return as string */

    // Always be sure to fall back onto "production"    
    return 'production';
});

This will store away our closure into the object for use when it receives the Request object (like, during the Dispatcher::run filter above) — always, always, always be sure that your closure returns a value of some sort. The value can be any string you want but it will become the name of the current environment.

Since the call to Environment::set is responsible for setting our current environment you’ll need to set your custom detector before it is triggered, otherwise it won’t end up being used.

Another note is to remember that this call is destructive and not additive. You won’t receive any of the built-in checks if you add your own detector.

Arbitrary environment specific storage

This is actually the easiest part and is fairly well covered in the documentation itself but while we’re here, a quick example.

Environment::set('production', array('debug' => false));
Environment::set('development', array('debug' => true));

Later, performing Environment::get('debug') will give us the corresponding value depending on the environment that was detected. The get method also allows us to use a convenient dot notation for getting at nested values.

Closing

Hopefully this has been a fairly clear outline of the awesome Environment class and a bit of its inner workings. If you have any questions, feel free to ask!

Read the rest of this post »

Leaning on CakePHP

There is a ton of information that CakePHP sets up for each request. Looking at the Controller object API, you can see that a sizable chunk is devoted to maintaining the data about the current request (as it should be) but what, if anything, are you doing with that information? Here's some tidbits I've ran into while developing a plugin that you can use to start "leaning on CakePHP" in your application.

Read the rest of this post »