Functions as First Class Citizens in PHP - Functional PHP (2017)

Functional PHP (2017)

Chapter 1. Functions as First Class Citizens in PHP

Functional programming, as its name suggests, revolves around functions. In order to apply functional techniques effectively, a language has to support functions as the first class citizen, or also first functions.

This means that functions are considered like any other value. They can be created and passed around as parameters to other functions and they can be used as return value. Luckily, PHP is such a language. This chapter will demonstrate the various way functions can be created and used.

In this chapter, we will cover the following topics:

· Declaring function and methods

· Scalar type hints

· Anonymous functions

· Closures

· Using objects as functions

· Higher Order Functions

· The callable type hint

Before we begin

As the first release of PHP 7 happened in December 2015, it will be the version that will be used for the examples in this book.

However, since it's a fairly new version, each time we use a new feature, it will be clearly outlined and explained. Also, since not everyone is able to migrate right away, changes needed to run the code on PHP 5 will be proposed whenever possible.

The latest version available at the time of writing is 7.0.9. All code and examples are validated using this version.

Coding standards

Examples in this book will respect PSR-2 (PHP Standard Recommendation2) and its parent recommendation, PSR-1, for their coding style, as should most of the libraries presented. For people not familiar with them, here are the most important parts:

· Classes are in a namespace and use CamelCase with the first letter capitalized

· Methods use CamelCase without the first letter capitalized

· Constants are written with all letters in capital

· Braces for classes and methods are on a new line, other braces are on the same line

Also, although not being defined in PSR-2, the following choices were made:

· Function names are in snake_case

· Parameters, variables, and property names are in snake_case

· Properties are private whenever possible

Autoloading and Composer

The examples will also assume the presence of a PSR-4 compatible autoloader.

As we will use Composer dependency manager to install the presented libraries, we recommend using it as the autoloader.

Functions and methods

Although this book is not designed for PHP beginners, we will quickly cover some basis in order to be sure we share a common vocabulary.

In PHP, you usually declare a function using the function keyword:

<?php

function my_function($parameter, $second_parameter)

{

// [...]

}

A function declared inside a class is called a method. It differs from a traditional function as it can access the object properties, have visibility modifiers, and can be declared static. As we will try to write code as pure as possible, our properties will be of private type:

<?php

class SomeClass

{

private $some_property;

// a public function

public function some_function()

{

// [...]

}

// a protected static function

static protected function other_function()

{

// [...]

}

}

PHP 7 scalar type hints

You were already able to declare type hints for classes, callables, and arrays in PHP 5. PHP 7 introduces the idea of scalar type hints. This means you can now say that you want a string, an int, a float, or a bool data type, both for parameters and return types. The syntax is roughly similar to what can be found in other languages.

Contrary to class type hints, you can also choose between two modes: the strict mode and the non-strict mode, the latter being the default. This means PHP will try to cast the values to the desired type. The casts will happen silently if there is no loss of information, otherwise, a warning will be raised. This can lead to the same strange results you can have with string to numbers conversion or true and false values.

Here are some examples of such casts:

<?php

function add(float $a, int $b): float {

return $a + $b;

}

echo add(3.5, 1);

// 4.5

echo add(3, 1);

// 4

echo add("3.5", 1);

// 4.5

echo add(3.5, 1.2); // 1.2 gets casted to 1

// 4.5

echo add("1 week", 1); // "1 week" gets casted to 1.0

// PHP Notice: A non well formed numeric value encountered

// 2

echo add("some string", 1);

// Uncaught TypeError Argument 1 passed to add() must be of the type float, string given

function test_bool(bool $a): string {

return $a ? 'true' : 'false';

}

echo test_bool(true);

// true

echo test_bool(false);

// false

echo test_bool("");

// false

echo test_bool("some string");

// true

echo test_bool(0);

// false

echo test_bool(1);

// true

echo test_bool([]);

// Uncaught TypeError: Argument 1 passed to test_bool() must be of the type Boolean

If you want to avoid issues with casting, you can opt-in for the strict mode. This way PHP will raise an error each time the values do not exactly conform to the desired type. In order to do so, the declare(strict_types=1) directive must be added to the very first line of your file. Nothing must precede it.

The only cast that PHP allows itself is from int to float by adding .0 as there is absolutely no risk of data loss.

Here are the same examples as before, but with strict mode activated:

<?php

declare(strict_types=1);

function add(float $a, int $b): float {

return $a + $b;

}

echo add(3.5, 1);

// 4.5

echo add(3, 1);

// 4

echo add("3.5", 1);

// Uncaught TypeError: Argument 1 passed to add() must be of the type float, string given

echo add(3.5, 1.2); // 1.2 gets casted to 1

// Uncaught TypeError: Argument 2 passed to add() must be of the type integer, float given

echo add("1 week", 1); // "1 week" gets casted to 1.0

// Uncaught TypeError: Argument 1 passed to add() must be of the type float, string given

echo add("some string", 1);

// Uncaught TypeError: Argument 1 passed to add() must be of the type float, string given

function test_bool(bool $a): string {

return $a ? 'true' : 'false';

}

echo test_bool(true);

// true

echo test_bool(false);

// false

echo test_bool("");

// Uncaught TypeError: Argument 1 passed to test_bool() must be of the type boolean, string given

echo test_bool(0);

// Uncaught TypeError: Argument 1 passed to test_bool() must be of the type boolean, integer given

echo test_bool([]);

// Uncaught TypeError: Argument 1 passed to test_bool() must be of the type boolean, array given

Although not demonstrated here, the same casting rules apply for return types. Depending on the mode, PHP will happily perform the same casting and display the same warning and errors as for parameters hints.

Also, another subtlety is that the mode that is applied is the one being declared at the top of the file where the function call is made. This means that when you call a function that was declared in another file, the mode this file was in is not taken into account. Only the directive at the top of the current file matters.

Concerning errors raised about types, we will see in Chapter 3, Functional basis in PHP how exception and error handling was changed in PHP 7 and how you can use it to catch those.

From now on, every time it makes sense, our examples will use scalar type hints to make the code more robust and readable.

Imposing types can be seen as cumbersome and will probably lead to a few irritations when you start using them, but in the long run, I can assure you that it will save you from some nasty bugs. All checks that can be done by the interpreter are something you don't need to test yourself.

It also makes your function easier to understand and reason with. The person looking at your code won't have to ask themselves what could a value be, they know with certitude what kind of data they have to pass as parameters and what they will get back. The result is that the cognitive burden is lessened and you can use your time thinking of solving issues instead of keeping in mind menial details about your code.

Anonymous functions

You were probably well aware of the syntax we saw to declare functions. What you may not know is that a function does not necessarily need to have a name.

Anonymous functions can be assigned to variables, used as callbacks and have parameters.

In the PHP documentation, the term anonymous function is used interchangeably with the term closure. As we will see in the following code snippet, an anonymous function is even an instance of the Closureclass, which we will discuss. According to the academic literature both concepts, although similar, are a bit different. The first usage of the term closure was in 1964 by Peter Landin in The mechanical evaluation of expressions. In the paper, a closure is described as having an environment part and a control part. The functions we will declare in this section won't have any environment, so they won't be, strictly speaking, closures.

In order to avoid confusion when reading other work, this book will use the term anonymous function to describe a function without a name, as presented in this section:

<?php

$add = function(float $a, float $b): float {

return $a + $b;

};

// since this is an assignment, you have to finish the statement with a semicolon

The previous code snippet declared an anonymous function and assigned it to a variable so that we can reuse it later either as a parameter to another function or call it directly:

$add(5, 10);

$sum = array_reduce([1, 2, 3, 4, 5], $add, 0);

You can also declare an anonymous function directly as a parameter if you don't plan to reuse it:

<?php

$uppercase = array_map(function(string $s): string {

return strtoupper($s);

}, ['hello', 'world']);

Or you can return a function as you would return any kind of value:

<?php

function return_new_function()

{

return function($a, $b, $c) { /* [...] */};

}

Closures

As we saw earlier, the academicals description of a closure is a function that has access to some outside environment. Throughout this book, we will keep to this semantics, despite PHP calling both anonymous functions and closure using the later term.

You may be familiar with JavaScript closures, where you can simply use any variable from the outside scope without doing anything particular. In PHP, you need to use the use keyword to import an existing variable into the scope of an anonymous function:

<?php

$some_variable = 'value';

$my_closure = function() use($some_variable)

{

// [...]

};

PHP closures use an early-binding approach. This means that the variable inside the closure will have the value that the variable had at the closure creation. If you change the variable afterward, the change will not be seen from inside the closure:

<?php

$s = 'orange';

$my_closure = function() use($s) { echo $s; };

$my_closure(); // display 'orange'

$a = 'banana';

$my_closure(); // still display 'orange'

You could pass the variable by reference so that changes to the variable are propagated inside the closure, but since this is a book on functional programming where we try to use immutable data structures and avoid having state, figuring how to do it is left as an exercise to the reader.

Be aware that when you pass objects to a closure, any modification done to properties in the object will be accessible inside the closure. PHP does not make a copy of objects when passed to the closure.

Closures inside of classes

If you declare any anonymous function inside a class, it will automatically get access to the instance reference via the usual $this variable. To stay coherent about the vocabulary, the function will automatically become a closure:

<?php

class ClosureInsideClass

{

public function testing()

{

return function() {

var_dump($this);

};

}

}

$object = new ClosureInsideClass();

$test = $object->testing();

$test();

If you want to avoid this automatic binding, you can declare a static anonymous function:

<?php

class ClosureInsideClass

{

public function testing()

{

return (static function() {

// no access to $this here, the following line

// will result in an error.

var_dump($this);

});

}

};

$object = new ClosureInsideClass();

$test = $object->testing();

$test();

Using objects as functions

Sometimes, you might want to split your function into smaller parts, but without those parts being accessible to everyone. When this is the case, you can leverage the __invoke magic method on any object that let you use an instance as a function and hide that helper function as private methods inside your object:

<?php

class ObjectAsFunction

{

private function helper(int $a, int $b): int

{

return $a + $b;

}

public function __invoke(int $a, int $b): int

{

return $this->helper($a, $b);

}

}

$instance = new ObjectAsFunction();

echo $instance(5, 10);

The __invoke method will be called with any parameters you pass to the instance. If you want, you can also add a constructor to your object and use any methods and properties that it contains. Just try to keep it as pure as possible, because as soon as you use mutable properties, your function will be harder to understand.

The Closure class

All anonymous functions are in fact an instance of the Closure class. However, as stated in the documentation (http://php.net/manual/en/class.closure.php), this class does not use the aforementioned __invokemethod; it's a special case in the PHP interpreter:

Besides the methods listed here, this class also has an __invoke method. This is for consistency with other classes that implement calling magic, as this method is not used for calling the function.

This method on the class allows you to change to which object the $this variable will be bound inside the closure. You can even bind an object to a closure created outside of the class.

If you start using the features of the Closure class, keep in mind that the call method was just recently added in PHP 7.

Higher-order functions

PHP functions can take functions as parameters and return functions as return values. A function that does either of those is called a higher-order function. It is as simple as that.

In fact, if you read the following code samples, you will quickly see that we have already created multiple higher-order functions. You will also discover, without much surprise, that most of the functional techniques you will learn revolve around higher-order functions.

What is a callable?

A callable is a type hint that can be used to enforce that the parameter of a function is something that can be called, like a function. Beginning with PHP 7, it can also be used as a type hint for the return value:

<?php

function test_callable(callable $callback) : callable {

$callback();

return function() {

// [...]

};

}

However, what you cannot enforce with the type hint is the number and type of arguments your callable should have. But it is already great to guarantee to have something you can call.

A callable can take multiple forms:

· A string for named functions

· An array for class methods or static functions

· A variable for anonymous functions or closures

· An object with a __invoke method

Let's see how we can use all these possibilities. Let's start with calling a simple function by name:

$callback = 'strtoupper';

$callback('Hello World !');

We can also do the same for functions inside of classes. Let's declare an A class with some functions and use an array to call it.

class A {

static function hello($name) { return "Hello $name !\n"; }

function __invoke($name) { return self::hello($name); }

}

// array with class name and static method name

$callback = ['A', 'hello'];

$callback('World');

Using a string will only work for the static method, as other methods will need an object to use as their context. In the case of a static method, you can also use a simple string directly, this will, however, only work starting with PHP 7; the previous version didn't support this syntax:

$callback = 'A::hello';

$callback('World');

You can call a method on a class instance as easily:

$a = new A();

$callback = [$a, 'hello'];

$callback('World');

Since our A class has an __invoke method, we can use it as a callable directly:

$callback = $a;

$callback('World');

You can also use any variable to which an anonymous function is assigned as a callable:

$callback = function(string s) {

return "Hello $s !\n";

}

$callback('World');

PHP also provides you with two helpers to call functions in the form of call_user_func_array and call_user_func. They take a callable as a parameter and you can also pass parameters. For the first helper, you pass an array with all the parameters; for the second one, you pass them separately:

call_user_func_array($callback, ['World']);

A final word of caution, if you are using the callable type hint: any string that contains a function name that has been declared is considered valid; this can lead to some unexpected behavior sometimes.

A somewhat contrived example would be a test suite where you check that some functions only accept valid callables by passing it some strings and catching the resulting exception. At some point, you introduce a library and this test is now failing, although both should be unrelated. What is happening is that the library in question declares a function with the exact name that your string contained. Now, the function exists and no exception is raised anymore.

Summary

In this chapter, we discovered how you can create new anonymous functions and closures. You are also now familiar with the various ways you can pass those around. We also learned about the new PHP 7 scalar type hints that help us to make our program more robust, and the callable type hint so we can enforce having a valid function as a parameter or return value.

For anyone who has been using PHP for some time already, there was probably really nothing new in this chapter. But we now share a common ground that will help us dive into the functional world.

With the basics about functions in PHP covered, we will learn more about the fundamental concepts pertaining to functional programming in the next chapter. We will see that your functions have to respect certain rules in order to be truly useful in a functional code base.