Learn what are PHP closures in really simple English that everyone understand. This article covers everything including use cases too.
Closure is a PHP function without a name. If you see any function in PHP without a name then it is a closure. Closures can be
- Stored in a variable
- Passed as a argument in another function.
- Set as a return value of a function.
Storing a closure in a variable
You can store a closure in a variable since PHP closures does not have a name.
$greet = function($name){
return "Hello, $name";
}
echo $greet('bob');
Did you notice that using a variable as a function. Normally variables store integers, strings, arrays, etc. not functions. With closures you can store a function in a variable.
Passing a closure as a Argument
You can pass a closure as a Argument in another function. Mostly called as callback function. You may have used this feature very often.
Task: create a function that accept two parameters as name and callback function. Write a callback function to add greetings to your name. ex: hello, bob. If callback is not provided just return the name.
function sayMyName($name, $callback=null){
if(is_callable($callback)){
return $callback($name);
}
return $name;
}
echo sayMyName("bob", function($name){
echo "hello, $name";
});
Task: create a callback function or a closure to process numbers of a integer array and return its values doubled. Use closure as the argument of the function.
function processArray(array $array, callable $callback)
{
$result = [];
foreach($array as $item)
{
$result[] = $callback($item);
}
return $result;
}
$numberAry = [1,2,3,4,5,6];
$doubledArray = processArray($numberAry, function($number)
{
return $number * $number;
});
print_r($doubledArray);
Set closure as the return value of a function
Here define a closure to accept one parameter called $name and add add greeting message for the return value of a function.
function addGreetings()
{
return function($name){
return "hello, $name";
};
}
$greetings = addGreetings();
echo $greetings('bob');
Make sure to add semi colon to returning function or closure to end the statement. Normally sem colon (;) is not added at the end of a function. Here function is used as a statement.
Questions
Task 1
Use a closure with array_map function. Instead of using a normal PHP function for callback to create a new array use a closure. Result of the element of the array is the square of the corresponding element in the original array?
Solution:
$numAry = [2,4,6,8,10];
print_r (array_map(function($number){
return $number * $number;
}, $numAry));
Task 2
Create a immediately invoked closure to add two numbers. Such as inputting 3 and 4 to get 7 as the result.
Step 1: create a closure to accept two numbers and return the sum
function($x, $y){
return $x+$y;
}
Step 3: In PHP you can immediately invoke this function by wrapping the function with parenthesis “()” and immediately opening another set of parenthesis right after that.
echo (function($x, $y){
return $x+$y;
})(4,3);
Task 3
Define a middleware closure in PHP to perform actions both before and after processing a request.
solution:
$middleware = function($request, $next)
{
//perform action before the request
$request = $request." -pre request";
//Pass the request to next handle like controller, middleware, etc.
$request = $next($request);
//Perform action after the request like before sending response to user.
return $request = $request." - post request";
};
//defining the next handler
$nextHandler = function($request){
return $request." - next handler data";
};
echo $middleware("initial request", $nextHandler);
//output - initial request -pre request - next handler data - post request
Task 4
Implement a basic service container in PHP using closures and bind and resolve services dynamically. In other word use closures for dependency injection.
Solution – This is for those who didn’t have a clue about service containers and dependency injection practices.
- Service container stores list of services or classes with business logic to do something.
- Service container should be able to deliver initialized object of the defined service or class when requested.
- Place where the service container is used to create a service like controller does not know how the service is created and its dependencies.
- Services are created or objects are initialized only when needed. Creating all the services without a need will max out resource usage.
- This is called dependency injection. Where required services or initialized object classes are injected in the required class like controller class by a service container or dependency injector only when needed.
step 1: Create a service container ( just a normal PHP class. ) to hold all the service classes. It should need a array variable to hold service classes, method to add a service to container and lastly a method to create the service (initialize the class or object).
class ServiceContainer{
//stores all the service class names as the key.
//stores how to initialize logic in a closure as the value
protected $services = [];
/**
* Register services with the container.
* $name - name of the service
* $closure - define how to create the service
*/
public function bind(string $serviceName, Closure $closure){
$this->services[$serviceName] = $closure;
}
/**
* get the requested closure function and execute it.
*/
public function make(string $serviceName){
//() used at the end to call the return closure and pass the
//$this variable to it. So it can uses any variable or resources
//used in the service container if needed.
return $this->services[$serviceName]($this);
}
}
Step 2: create a sample service class. Here there is nothing unordinary. Just a simple PHP class with a method.
class ExampleService{
public function firstMethod(){
echo "Hello, I am the first method of the example service";
}
}
step 3: Initialize the service container and add above example service class to it.
//Intializing the service container class.
$serviceContaier = new ServiceContainer();
//Binding our example service class to service container.
$serviceContaier->bind('exampleService', function(){
//Here define how to create an instance of the service class. Which is isolated
//from the place where it uses this instance.
return new ExampleService();
});
step 4: create a service object using the service container and use created service object.
//Get a service class object from the service container.
$exampleService = $serviceContaier->make('exampleService');
//Using the initialized or created service object by service container.
echo $exampleService->firstMethod();
Below shows full example.
<?php
class ServiceContainer{
//stores all the service class names as the key.
//stores how to initialize logic in a closure as the value
protected $services = [];
/**
* Register services with the container.
* $name - name of the service
* $closure - define how to create the service
*/
public function bind(string $serviceName, Closure $closure){
$this->services[$serviceName] = $closure;
}
/**
* get the requested closure function and execute it.
*/
public function make(string $serviceName){
//() used at the end to call the return closure and pass the
//$this variable to it. So it can uses any variable or resources
//used in the service container if needed.
return $this->services[$serviceName]($this);
}
}
//Example service class.
class ExampleService{
public function firstMethod(){
echo "Hello, I am the first method of the example service";
}
}
//Intializing the service container class.
$serviceContaier = new ServiceContainer();
//Binding our example service class to service container.
$serviceContaier->bind('exampleService', function(){
return new ExampleService();
});
//Get a service class object from the service container.
$exampleService = $serviceContaier->make('exampleService');
//Using the initialized or created service object by service container.
echo $exampleService->firstMethod();