PHP Closures in Simple English

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

  1. Stored in a variable
  2. Passed as a argument in another function.
  3. 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();