An intriguing use of lambda functions

March 10, 2010

I’ve been working hard on Goodsie.com lately trying to bring it to launch. It’s been great being in on a new PHP project from (near) the beginning, as it frees up a number of things.

One of those, is the fact that I can be using PHP 5.3 and all the new features that come with PHP 5.3. While I’ve used my fair share of the short-cut ternary already (?:), the bigger win for me, are the Lambda functions with scoping (anonymous functions).

I found a very specific use out of the blue of Lambda functions that I have now used and I see as a great use-case. Which is specifically passing functions/logic from your Controller to your View.

In the case of Goodsie, I’m using PHP for my templating language and as usual I’m trying to remove as much logic from my View as possible, while still allowing the view to be malleable.

The specific case I had, was a subview that was generating some pagination code for me. You know, the standard ‘previous, page 1, page 1, next’ section of links. The basic HTML template I had, looked looked similar to:

<div class="pagination">
    <a href="<?= $baseurl . '/page:' . ($page - 1) ?>">&larr; Previous</a>
    Page <?= $page ?> of <?= $total ?>
    <a href="<?= $baseurl . '/page:' . ($page + 1) ?>">Next &rarr;</a>
</div>

Rather straight forward, but I quickly ran into a problem. The way it worked, as you see, is that you passed in a base URL, and the page number you are currently on, and it generated appropriate forward/back links. (Ok, there was also some other logic where it determined if you needed the prev/next links at all, but I’ve removed that for clarity)

But I then had a case, where I wanted to reuse this subview in an ajax situation. Where instead of straight URL’s being passed in, I might want to pass in a javascript function, and have that function be called with the page number as a parameter. That would be nice as I could use it in both situations. What pagination looked like, could completely change, and still work on both cases. Perhaps we’d want to give a full list of all possible pages. Or show a couple forward/back, etc. The view could handle all of that without a change to the controller.

But therein lied the problem. When using a URL based pagination, I wanted to concat the page number onto the end of the URL. But when using javascript, it wasn’t pure concatenation, it instead needed to wrap the page number with the function call. Oh the pain a simple ) could cause me.

I started writing code, where I ended up with tons of switch statements and logic inside of the view. I’d have to pass in two different possible values, a URL or a javascript function. The view at every point where it would output a link, would need to see which version was being used, and from that decide what type of output to create. In short, it was a mess.

But then the solution dawned upon me. A lambda function would work admirably here. So what I did, is inside of my controller I created a function on the fly, that would generate the appropriate type of link that I was wanting. It looks something like:

if ($jsfunc) {
    $url = function ($p) use ($jsfunc) { return "javascript:{$jsfunc}({$p})"; };
} elseif ($baseurl) {
    $url = function ($p) use ($baseurl) { return "{$baseurl}/page:{$p}"; };
}

Now I could simply rewrite my original template, to use this lambda function $url to generate it’s URLs.

<div class="pagination">
    <a href="<?= $url($page - 1) ?>">&larr; Previous</a>
    Page <?= $page ?> of <?= $total ?>
    <a href="<?= $url($page - 1) ?>">Next &rarr;</a>
</div>

Now not only would this work for my specific situation, but ANY controller could reuse this pagination subview and define exactly how it wanted it’s URLs to be formed. Now, the view could completely change around how the pagination section is displayed, show as many, or as few pages as it wants to, and all that without ever touching the controller.

This is one simple example, but I’ve become enamored of this approach. Using lambda functions in this way, you are able to have complicated logic represented inside of your view, but encapsulated/created by the controller. Also of note is the fact that the view is managing to use the $jsfunc and $baseurl values, but without actually having to be granted access to them. This allows for another level of encapsulation, as I exposed one function, instead of 2 separate variables. In the future if other data points start being needed to determine what a URL should be, the view never needs know that, as the controller will continue to update the function on it’s behalf.