We Need Some Closure
Heard of anonymous functions, Closures, or lambda functions? Seen some php that looks kinda like jQuery? Anonymous functions confused the crap outta me at first. Let's try to add these concepts to your programming toolbelt.
Mary had a little lambda
Anonymous functions are nothing more than functions you can assign to a variable. Consider the following example.
function add($x, $y)
{
return $x + $y;
}
$subtract = function($x, $y) {
return $x - $y;
};
echo add(2, 3); // "5"
echo $subtract(8, 2); // "6"
The first function declaration should look familiar. Nothing fancy going on here. The second function is the interesting one. $subtract
is what's called a lamda function. It is a function that has no name. If we assign it to a variable, we can invoke it just like a normal function, passing parameters in via the paranthesis. Crazy name, not-so-crazy to learn!
Time for Closure
So what the heck is a Closure? It's a type of lambda function that is aware of its surroundings. Now that we know what a Closure is let's move on...just kidding! Let's look at an example.
$multiplier = 10;
$multiply = function($x) use ($multiplier) {
return $x * $multiplier;
};
echo $multiply(1); // "10"
echo $multiply(4); // "40"
echo $multiply(10); // "100"
A Closure is an anonymous function that you can pass variables to remember and use later. In the above example, the Closure is remembering the $multiplier
variable. It uses it on all future invocations.
I refer to all anonymous functions as Closures. So for the remainder of this post, I'll use the term Closure interchangeably with lambda function and anonymous function.
While these examples are extremely simple and not very useful, it introduces a very powerful feature in php. Let's continue on to see how we can make these ideas a little more useful for us.
When are these things useful?
These are neat but it's difficult to figure out how these can be useful at first glance. Let's set up a scenario to utilize Closures.
Let's pretend we have a list of objects that we need to make sure all pass some sort of validation. Fairly common scenario, right? First, we'll define some requirements for this validation class:
- Can add objects to be validated
- Can add custom validation rules
- Can validate all objects at once using custom validation rules
To satisfy these requirements, we could code the following class.
class Validator
{
/**
* Objects to be validated
*
* @var array
*/
protected $objects = [];
/**
* Validation rules to use
*
* @var array
*/
protected $rules = [];
/**
* Errors generated for the validation attempt
*
* @var array
*/
protected $errors = [];
/**
* Add an object to be validated
*
* @param $obj
* @return void
*/
public function addObject($obj)
{
$this->objects[] = $obj;
}
/**
* Add a validation rule
*
* @param callable $rule
*/
public function addRule(Closure $rule)
{
$this->rules[] = $rule;
}
/**
* Validate all objects in the validator
*
* @return bool
*/
public function validate()
{
//empy the errors array
$this->errors = [];
//loop through all objects to be validated
foreach ($this->objects as $object) {
//loop through all rules to check against the object
foreach ($this->rules as $rule) {
//run the rule against the current object
$rule($object);
}
}
//if there are 0 errors, the validation passes and returns true, returns false otherwise
return count($this->errors) == 0;
}
/**
* Add an error to the validator
*
* @param string $key
* @param string $error
*/
public function addError($key, $error)
{
$this->errors[$key][] = $error;
}
/**
* Get all errors generated
*
* @return array
*/
public function getErrors()
{
return $this->errors;
}
}
Well that looks cool and all, but there seems to be something missing. The validate()
method isn't adding any errors when checking the rules, but it's relying on the count of the errors to determine if the validation passes. This is clearly broken...or is it? Let's actually use our Validator
and see it in action.
//create our validator object
$validator = new Validator;
//define a rule
$validPhone = function($person) use ($validator) {
//strip all non-integer characters
$phone = preg_replace("/[^0-9]/", "", $person->phone);
if (strlen($phone) !== 10) {
$validator->addError($person->name, "Invalid phone number.");
}
};
//add the rule to the validator
$validator->addRule($validPhone);
//define and add a valid email rule in one go
$validator->addRule(function($person) use ($validator) {
if ( ! filter_var($person->email, FILTER_VALIDATE_EMAIL)) {
$validator->addError($person->name, "Invalid email.");
}
});
//define and add a valid name rule in one go
$validator->addRule(function($person) use ($validator) {
if (strlen(trim($person->name)) == 0) {
$validator->addError($person->name, "No name.");
}
});
//let's create some objects to validate
$cory = new stdClass;
$cory->name = "Cory";
$cory->phone = "123-456-7890";
$cory->email = "cory@";
$shawn = new stdClass;
$shawn->name = "Shawn";
$shawn->phone = "456-45-2";
$shawn->email = "shawn";
$topanga = new stdClass;
$topanga->name = "Topanga";
$topanga->phone = "789-012-3456";
$topanga->email = "[email protected]";
//add the objects to be validated
$validator->addObject($cory);
$validator->addObject($shawn);
$validator->addObject($topanga);
In the above code, we're defining three rules: has valid phone, has valid email, and has name.
When defining the first rule, we're assigning it to the $hasPhone
variable and then adding it the validator with the addRule()
method. Our Closure takes one argument, a $person
object. It uses the $validator
object. By using the same Validator
instance, the Closure has access to it every time it gets invoked. In this example, we're using it to add errors.
The other rules, valid phone and valid email, aren't being assigned to variables. We're skipping that part and defining them right in the addRule()
method of the Validator
. Both ways are completely valid. jQuery advocates heavy use of this technique. It's the same idea.
Now that we've set up our Validator, we can run it and see what we get.
// run the validation
$isValid = $validator->validate(); //false
//dump the results
var_dump($validator->getErrors());
/*
array (size=2)
'Cory' =>
array (size=1)
0 => string 'Invalid email.' (length=14)
'Shawn' =>
array (size=2)
0 => string 'Invalid phone number.' (length=21)
1 => string 'Invalid email.' (length=14)
*/
As expected, our errors are assigned to the appropriate names. Awesome!
Why not create a class?
It's ideal to create classes that you can reuse multiple times in various places. However, sometimes you just know that some code will only ever run once and it's not worth creating a whole class or multiple classes to fill the requirement. Closures fit the bill perfectly. All Closures are objects in php. They allow us to adhere to object-oriented principles with extreme flexibility without requiring classes to be created for absolutely everything.
We could have tackled this another way. We could have defined a RuleInterface
with check($object)
and getError()
methods. Then, we could have created classes that implement this interface for each rule. In this case, that'd be three separate classes. We could then create those rule objects and pass them to a Validator
object that would check the rules against the objects being validated. If the rule check fails, get the error from the rule object and add it to the Validator
's errors.
Symfony 2 actually uses this strategy for validating form input. It makes sense for common rules that will be used multiple times.
Our contrived example makes a little more sense to use Closures because we're validating "person" objects and maybe we never need to run this same combination of rules against a list of people anywhere else in our application.
Containers
Nowadays, it's very common to see Closures being used for containers. These containers' purpose is to create objects and inject their dependencies so we don't have to do that every single time. Consider the following example.
$container = [];
$container['EmailTemplate'] = function(){
return new EmailTemplate;
};
$container['DataSource'] = function(){
return new MySQLDataSource;
};
$container['NewsLetterSender'] = function() use ($container) {
//used to created emails
$template = $container['EmailTemplate']();
//used to track stats about the sending
$dataSource = $container['DataSource']();
return new NewsLetterSender($dataSource, $template);
};
$newsLetterSender = $container['NewsLetterSender']();
//versus the more verbose and less flexible
$newsLetterSender = new NewsLetterSender(new EmailTemplate, new MySQLDataSource);
As you can see, the $container
's only responsibility is to create and assemble new objects. This concept is known as Inversion of Control. It's so popular because it's an elegant way to encourage dependency injection. While both of those concepts are out of the scope of this post, they are worth mentioning because you will most likely see this idea used elsewhere.
To Closure this post
Anonymous functions are a sweet feature in php. Although a little hard to conceptualize their use, they can be quite handy. I hope I've been able to shed a little light on these unique functions. I highly encourage you to toy around with them. If you're just starting to learn php, you're bound to see them used more and more.
**Hint!** Instead of a $this->errors instance variable, change it to $this->sorted and instead of addError($key, $error), change it to sortObject($category, $object).
Categories: PHP