4. Functions

Overview

By the end of this chapter, you will be able to work with built-in functions; create user-defined functions; and write anonymous functions.

Introduction

When writing software, we often run into situations where we need to do a specific task in different places within the application that we are building. Without thinking about it, it can be easy to fall into the habit of rewriting the same code over and over again, causing code repetition and making it harder to debug errors when they show up. However, as with all other programming languages, PHP gives you the ability to structure reusable code in what is known as a function, which is also sometimes referred to as a method. These two terms will be used interchangeably throughout this chapter.

Think of a function as a reusable set of instructions or statements. After writing it once, you can call it as many times as you like. Functions bundle logic that should otherwise be kept inseparably together.

Grouping and isolating a set of instructions inside a function comes with a number of benefits. The most obvious one is the option to reuse it: once you have written your function, you never need to rewrite or reinvent this particular set of instructions again. Functions also improve consistency – this means that each time you call your function, you can be sure the same set of instructions will be applied.

Another less obvious benefit is that your code becomes much more readable, especially when you name your functions so that it is clear what they do.

Another good thing about a function is that it encloses local variables within its scope, so that they do not pollute the global scope. We will discuss scope in more detail later.

Here's an example of a simple function:

// simplest callable is a function

function foo()

{

}

Here's a function that has been written to calculate the average of the values passed to this function:

// function that calculates the average of values that you pass to it

function average()

{

    $count = func_num_args();

    $total = 0;

    foreach (func_get_args() as $number) {

        $total += $number;

    }

    return $total / $count;

}

Note that this is not production-ready code. The function does not check anything about its inputs and does not prevent error conditions, such as division by zero, if you do not pass any arguments. The function-average.php file contains a more elaborate example of the same function for you to refer to on the GitHub repository.

A function is callable. However, note that not all callables are functions. Functions can call other functions, functions can pass functions around to other functions to be called by them, and functions can create functions. Confused? Read on and look at the examples and you will see that it is not complicated at all.

What is a Callable?

Simply put, a callable is a part of your code that you can "call". When we say that you can "call" something, we mean that you can tell the program to execute it.

A callable can be written with parentheses after it, for example, functionName().

As previously described, a function is a type of callable, so a function can be called (that is, you can tell your program to execute it).

As an example, consider the following user-defined function:

function howManyTimesDidWeTellYou(int $numberOfTimes): string

{

    return "You told me $numberOfTimes times";

}

Do not worry about the details of the function right now—we will get into the nitty-gritty of it later. This function could be defined anywhere in your code, but let's assume that it is defined in a script called how-many-times-did-we-tell-you.php.

The contents of the script would then look like this:

<?php

declare(strict_types=1);

function howManyTimesDidWeTellYou(int $numberOfTimes): string

{

    return "You told me {$numberOfTimes} times";

}

The function takes a single parameter, $numberOfTimes, which must be of the int (integer) type, and it returns a string. The int type hint and the string return type are optional. We will discuss parameters and returning values later in the chapter. Now, function howManyTimesDidWeTellYou(int $numberOfTimes): string is just the function declaration: it defines the function. The script itself does nothing yet.

In order to enable the function to actually do something, we need to call it from our code. It is perfectly valid to continue in the same script file and call the function that we just defined as follows:

howManyTimesDidWeTellYou(1);

If you open a Terminal and execute the script, you will see no output. Why not? The reason is that while the function does return a string, it does not print any output. To generate output, you need to echo the return value of the function as follows:

echo howManyTimesDidWeTellYou(1);

Now you will see output if you execute the script.

Execute the script by calling it from the command line in the directory where the script lives. You can simply type the following text and press Enter:

php how-many-times-did-we-tell-you.php

The output is as follows:

You told me 1 times

You will immediately spot a problem with this output: it is grammatically incorrect. And what if we were to pass a negative integer? Then, the output would be even logically incorrect. Our function is not production-ready at this point.

A more elaborate example of the function and how it can be called is to be found in the how-many-times-did-we-tell-you.php file.

Note that it is possible to print text from within functions by using echo inside a function. However, this makes the function less reusable as it will generate output as soon as it is called. In some cases, you might want to delay output. For example, you may be collecting and combining strings before you output them, or you may want to store the string in a database and don't want to display it at this stage. Although printing directly from within a function is generally considered bad practice, you will see it a lot in systems such as WordPress. Printing from a function can be convenient in a context where generating output is the most important task.

Exercise 4.1: Using Built-in Functions

This exercise is about string manipulation. PHP has many built-in string manipulation functions. The one we will be using here is substr(). Like most other built-in functions, the behavior of substr() can be tweaked by passing various parameters:

  1. Create a new directory called Chapter04. Then, inside it, create a folder named exercises.
  2. Create a file called hello.php in the Chapter04/exercises directory.
  3. Write the opening script tag:

    <?php

    The opening tag tells the parser that, from this point onward, what we write is PHP.

  4. Write the instruction that extracts and prints "Hello" from "Hello World" using substr():

    echo substr('Hello World', 0, 5);

    The echo command prints the result of the statement that comes after it. The statement calls the substr function with three arguments: the literal string, Hello World, and the literal integers 0 and 5. What this says is "give me the five characters of the input string starting from 0". In PHP, you can think of a string as almost like an array, where each character in that string is an element. Like in many other programming languages, array indices start at zero instead of one. If you count the characters, you will see that H e l l o are the first five characters of the Hello World input string. They are returned from the function as a new string of five characters.

  5. Optionally, on the next line, echo a newline, only for clarity of the output:

    echo PHP_EOL;

    PHP_EOL is a predefined constant that outputs a newline in the correct format for the operating system you are on. Using this constant makes your code more portable between different operating systems.

  6. Open a Terminal and go to the Chapter04/exercises directory where your hello.php script is placed and execute the file using the following code:

    php hello.php

    Observe that Hello and a newline are printed in the Terminal; this is what the output looks in the Terminal:

    Figure 4.1: Printing the output to the Terminal

    Figure 4.1: Printing the output to the Terminal

    Note

    Don't worry if your path differs from that of the screenshot as this will depend on your system settings.

  7. Now change the code to the following:

    echo substr('Hello World', 5);

    Run the script again and notice the output is now World (notice the space at the start). What happened is that the substring is now taken from position 5 (the sixth character, the space), to the end of the string.

  8. Change the code to:

    echo substr('Hello World', -4, 3);

    Run the script and notice the output will be orl. What happens is that now the start is negative and counted backward from the end of the string. The length is 3 and taken toward the end of the string from the start:

    Figure 4.2: Printing the sliced string

    Figure 4.2: Printing the sliced string

    In the preceding screenshot, you can see the output from step 8. The output looks this way because I used a scratch file in PhpStorm. I added a new scratch file and just quickly pasted the code into it and ran it using the green play button in PhpStorm. Scratch files are a way of quickly testing some code in files while the files are not added to your project.

  9. Change the statement to the following:

    echo substr('ideeën', -3);

    Note

    Ideeën is a Dutch word that means "ideas." However, for this example, we need the ë character, so we can't just type "ideas."

    Run the script again and notice that the output is ën. If you have been paying attention so far, you should have expected the output to be eën: it is three characters long, counted from start = -3, and counted backward from the end of the string until the end of the string. So, why is the output two characters long in this case and not three? The explanation is that ë is a multibyte character. If you need to check whether a string is UTF-8-encoding, you can use an additional built-in function called mb_detect_encoding, passing the string as the first parameter and UTF-8 as the second parameter. The substr method just counts bytes and does not account for characters that are multiple bytes in length. Now, there is a solution for that: mb_substr. In fact, for many string manipulation functions, there are sister functions that are prefixed with mb_ to indicate that they support multibyte characters. If you always use the mb_ versions of these methods, you will get the expected results.

  10. Change the statement to the following:

    echo mb_substr('ideeën', -3);

    Run the script once more and notice that now you get the expected output of eën:

Figure 4.3: Printing the output of the sliced strings

Figure 4.3: Printing the output of the sliced strings

Remember to always use the mb_* versions of string manipulation functions.

In this section, we were introduced to callables and started to get a glimpse of the built-in functions that are available to us. Next, we are going to dive a little deeper into the types of callables.

Types of Callables

There are several types of callables:

  • Functions, such as mb_strtoupper.
  • Anonymous functions or closures.
  • Variables that hold the name of a function.
  • An array with two elements, where the first element is the object and the second element is the name of the function you wish to call that exists within the object written as a string. An example of this can be found in the callables.php document.
  • An object that has the __invoke magic method defined.

The __invoke method is a magic function that can be attached to classes that when initialized to a variable will make that assigned variable into a callable function. Here's a simple example of the __invoke method:

<?php

// Defining a typical object, take note of the method that we defined

class Dog {

    public function __invoke(){

    echo "Bark";

    }

}

// Initialize a new instance of the dog object

$sparky = new Dog();

// Here's where the magic happens, we can now call this

$sparky();

The output is as follows:

Bark

In the preceding example, we declared a $sparky object and executed this object as a function by calling it $sparky(). This function, in turn, invoked its primary action and printed the result.

To verify whether something is a callable, you can pass it to the built-in is_callable function. This function will return true if its first argument is a callable and false if not. The is_callable function actually takes up to three arguments that tweak the behavior of is_callable.

Try out the following example:

// simplest callable is a function

function foo()

{

}

echo is_callable('foo') ? '"foo" is callable' : '"foo" is NOT a callable', PHP_EOL;

// an anonymous function is also a callable

if (true === is_callable(function () {})) {

    echo 'anonymous function is a callable';

} else {

    echo 'anonymous function is NOT a callable';

}

You can explore more examples in the callables.php script on the GitHub repository.

Language Constructs

Language constructs are things that the PHP parser already knows about. Some language constructs act like functions, while others are used to build control statements such as if and while. Language constructs that act like functions look very much like built-in functions in the way they are used. If you want to print a string, you can choose to use echo, which is a language construct; or print, which is also a language construct. There are small differences between echo and print, and echo is the most commonly used. When comparing the two, echo doesn't have a return value and has the option of multiple parameters, whereas print returns a value that can be used in an expression and allows only one parameter. echo is the most flexible of the two and is a tiny bit faster. Language constructs can be used with or without parentheses. In contrast, callables are always used with parentheses:

// echo is a language construct

echo 'hello world'; // echo does not return a value

// print is also a language construct

print('hello world'); // print returns 1

Both statements print hello world. A language construct has a more efficient underlying implementation in C than a function and thus will execute faster. You can use parentheses with echo and print, but it is not mandatory.

Introduction to Built-In Functions

PHP comes with many built-in functions, such as strtoupper, which changes the case of an input string to uppercase:

echo strtoupper('Foo');

// output: FOO

PHP natively comes with a ton of functions. By adding extensions to PHP, you add even more built-in functions and classes to it. Built-in functions are precompiled in C as this is the language that PHP and its extensions are written in.

Note

How to add an extension will differ depending on which operating system you are on. So, when searching for it, always add the name of your operating system to your search and be sure to consult the most recent results first, as they are more likely to outline the correct procedure for installing or compiling extensions into PHP.

There is hardly anything more frustrating than spending days on writing some functionality, only to discover toward the end that there is a built-in function that does the same thing five times faster. So, before writing functionality yourself, try to google or search https://packt.live/2OxT91A for built-in functions. If you are using an IDE, built-in functions will be suggested by autocomplete as soon as you start typing in a PHP document. PHP is often called a glue language: it is used to tie different systems together. Therefore, there is a wealth of functions that talk to databases, file resources, network resources, external libraries, and so on.

If you are using a function that is provided by an extension that is not installed or compiled with your PHP version, you will get an error. For example, calling gd_info() when GD is not installed results in Fatal error: Uncaught Error: Call to undefined function gd_info(). By the way, GD is a library used for image manipulation.

Note

On a side note, in many real-life projects, we handle multibyte strings. When handling multibyte strings, you should be using the multibyte-safe string manipulation functions. Instead of strtoupper, you would be using mb_strtoupper.

Finding Built-In Functions

To find out which version of PHP you are currently using, open up a Terminal, type the following command, and then hit Enter:

php -v

To find out what extensions are installed on your system, type the following command and hit Enter:

php -m

This will list all extensions currently installed and enabled in your PHP installation. You can also list the extensions using the built-in get_loaded_extensions PHP function.

To make use of that, write a file called list-extensions.php with the following content:

<?php

print_r(get_loaded_extensions());

Execute the file from the command line as follows:

php list-extensions.php

Note that if you do this, you will have used two built-in functions: print_r and get_loaded_extensions. The print_r() function prints its first argument in human-readable form. You can use the second argument, a true Boolean value, to return the output instead of printing it on the screen. That way, you can write it to a log file, for example, or pass it on to another function.

The output should look like the following screenshot (note that the extensions may vary on your system):

Figure 4.4: Listing the extensions

Figure 4.4: Listing the extensions

Another function that you may find useful while exploring the built-in functions and the extensions is get_extension_funcs ( string $module_name ) : array, which you can use to list the functions that an extension provides. Often, it will be easier to find the functions in the documentation of the extension.

Here is the top part of the output:

print_r(get_extension_funcs('gd'));

The output is as follows:

Figure 4.5: Listing the top extensions

Figure 4.5: Listing the top extensions

Note

You can find more information about built-in functions at https://packt.live/2oiJPEl.

Parameters and Return Values

Parameters are the variables written within the function declaration. Arguments are the values that you pass as these parameters. Return values are the values that the function returns when it has completely executed. In the previous example, get_loaded_extensions was called without any arguments: there was nothing between the braces after get_loaded_extensions.

The return value of get_loaded_extensions() is an array of extensions loaded into PHP – extensions that are installed and enabled. That return value was used as an argument to print_r, which returned a user-friendly string describing its input. To clarify this, the list-extensions.php script could be rewritten as follows:

<?php

// get_loaded_extensions is called without arguments

// the array returned from it is stored in the variable $extensions

$extensions = get_loaded_extensions();

// the variable $extensions is then offered as the first argument to print_r

// print_r prints the array in a human readable form

print_r($extensions);

Passing Parameters by Reference

Parameters that are objects are always passed by reference. We will go into further detail about objects in Chapter 5, Object Oriented Programming, but to give you a little bit of context, think of an object as a container that contains scoped variables and functions. This means that an address in memory where that object exists is passed into the function so the function can find the actual object internally when it needs it. If the function modifies the referenced object, then the original object that is held in memory will reflect those changes. If you want a copy of the object to work on instead, you need to clone the object with the clone keyword before working on it. You can think of a clone as a copier that will make an exact copy of the object you want to duplicate.

An example of the use of the clone keyword can be found here:

$document = new Document();

$clonedDocument = clone $document;

If the modified copy is required outside the function, you can choose to return it from the function. In the following example, $document becomes a variable that contains an object reference to a DomDocument object:

$document = new DomDocument();

With scalar variable parameters, it is the programmer of the function who decides whether a parameter is passed by reference or as a copy of the original value. Note that only variables can be passed by reference.

A scalar variable is a variable that holds a scalar value, such as $a in the following example:

$a = 10;

As opposed to just 10, which is an integer value, scalars can be numbers, strings, or arrays.

If you pass a literal scalar value to a function that expects a reference, you will get an error stating that only variables can be passed by reference. This is because the PHP parser holds no references to scalars – they are just themselves. It is only when you assign a scalar to a variable that a reference to that variable will exist internally.

Passing Scalar Variables by Reference

PHP has many functions that work on arrays. They differ a lot as to whether they take the array as a reference or not.

Take the following array:

$fruits = [

    'Pear',

    'Orange',

    'Apple',

    'Banana',

];

The built-in sort() function will sort the preceding fruits in alphabetical order. The array is passed by reference. So, after calling sort($fruits);, the original array will be in alphabetical order:

sort($fruits);

print_r($fruits);

The output should be as follows:

Array

(

    [0] => Apple

    [1] => Banana

    [2] => Orange

    [3] => Pear

)

As opposed to passing by reference, array_reverse works on a copy of the array passed into it and returns it with its elements in reverse order:

$reversedFruits = array_reverse($fruits);

// the original $fruits is still in the original order

print_r($reversedFruits);

The output is as follows:

Array

(

    [0] => Banana

    [1] => Apple

    [2] => Orange

    [3] => Pear

)

For more elaborate examples, you can refer to array-pass-by-reference.php and array-pass-a-copy.php, which are available on GitHub.

Another example that you see in real-life code is preg_match(). This function matches an occurrence of a pattern in a string and stores it in the optional &$matches parameter, which is passed by reference. This means that you have to declare a $matches variable before you call the function or even while you are calling it. After the function has run, the previously empty $matches array will be filled with the match. The pattern is a regular expression. Regular expressions deserve their own chapter, but the essence is that a regular expression defines a pattern that the parser can then recognize in a string and return as a match. The preg_match() function returns 1 if the pattern exists in the string and matches, if provided, will contain the actual match:

<?php

$text = "We would like to see if any spaces followed by three word characters are in this text";

// i is a modifier, that makes the pattern case-insensitive

$pattern = "/sw{3}/i";

// empty matches array, passed by reference

$matches = [];

// now call the function

preg_match($pattern, $text, $matches);

print_r($matches);

The output is as follows:

(

    [0] => wou

)

As you can see, the first occurrence that is found is the single match stored in $matches. If you want all of the spaces followed by three word characters, you should use preg_match_all().

To demonstrate how simply changing the preg_match function to preg_match_all can return all instances of the matches, we will change the following line:

preg_match($pattern, $text, $matches);

...

We will replace it with the following code:

preg_match_all($pattern, $text, $matches);

...

This will result in returning all of the sections that match our defined pattern.

The output is as follows:

(

    [0] => Array

        (

            [0] => wou

            [1] => lik

            [2] => see

            [3] => any

            [4] => spa

            [5] => fol

            [6] => thr

            [7] => wor

            [8] => cha

            [9] => are

            [10] => thi

            [11] => tex

        )

)

Note

To learn more about regex, take a look at: https://packt.live/33n2y0n.

Optional Parameters

You will have noticed that we have used print_r() in a lot of examples to display a user-friendly representation of variables that would otherwise not make much sense. Let's take the following array:

$values = [

    'foo',

    'bar',

];

Using echo $values; would just print Array on the screen, while print_r($values); prints a human-readable format for us to view:

Array

(

    [0] => foo

    [1] => bar

)

Now, suppose that you would like to send information about $values to somewhere other than the screen. The reason for this could be that you want to send information about an error, or that you would like to keep a log of what is going on in your application. In the message that you send, you would like to include information about the contents of $values. If you were to use print_r for that, the output would not appear in your message but would be written to the screen instead. That is not what you want. Now the optional second parameter of print_r comes into play. If you pass that second argument with your function call and make it true, the output will not be printed directly, but instead be returned from the function:

$output = print_r($values, true);

The $output variable now contains the following:

"Array

(

    [0] => foo

    [1] => bar

)"

This can be used later to compose a message to be sent anywhere you need.

Exercise 4.2: Working with print_r()

In this exercise, we will use the print_r() function to print different shapes in a human-readable format. To do this, we will execute the following steps:

  1. Let's start by creating a new file in your project directory and calling it print_r.php.
  2. Next, we are going to open our PHP script with the opening tag and define a $shapes variable with three different shapes:

    <?php

         $shapes = [

                 'circle',

                 'rectangle',

                 'triangle'

         ];

  3. On the next line, let's echo out the contents of $values:

    echo $shapes;

  4. Let's go ahead and open the project directory in the Terminal and run it:

    php print_r.php

    You'll see that all that is printed is the following:

    Array

    This is because echo isn't designed to show array contents. However, this is where print_r() now comes into play.

  5. Let's replace echo with print_r:

    print_r($shapes);

    We'll run the script using the following command:

    php print_r.php

    Now we can see the values of the array as follows:

Figure 4.6: Printing the values of an array

Figure 4.6: Printing the values of an array

A Varying Number of Parameters

Functions can accept a varying number of parameters. Take, for example, printf, which is used to print a string of text from a predefined formatted string, filling out placeholders with values:

$format = 'You have used the maximum amount of %d credits you are allowed to spend in a %s. You will have to wait %d days before new credits become available.';

printf($format, 1000, 'month', 9);

This will print the following:

You have used the maximum amount of 1000 credits you are allowed to spend in a month. You will have to wait 9 days before new credits become available.

While $format is a required parameter, the remaining parameters are optional and variable in number. The important takeaway here is that you can pass as many as you like.

The number of parameters must match the number of placeholders in the string, but that is specific to printf. When allowing a varying number of parameters, it is up to the designer of the function to decide whether or not to validate the number of parameters against certain restrictions.

There is also the sprintf function, which acts almost the same way; however, instead of printing the resulting text, it returns it from the function so that you can use the output later.

You might have noticed that the placeholders are different: %d and %s. This can be used as a simple validation: %d expects a number, while %s accepts anything that can be cast to a string.

Flag Parameters

In earlier examples, we used the sort() function with just one parameter: the array we want to be sorted for us. The function accepts a second parameter. In this case, the second parameter is also defined as a flag, which means only values of certain predefined constants, called flags, are accepted. The flag determines the way in which sort() behaves. If you want to use multiple flags, then you can simply use the pipe (|) symbol between each flag.

Let's now take a slightly different input array:

$fruits = [

    'Pear',

    'orange', // notice orange is all lowercase

    'Apple',

    'Banana',

];

// sort with flags combined with bitwise OR operator

sort($fruits, SORT_FLAG_CASE | SORT_NATURAL);

print_r($fruits);

The output is as follows:

Array

(

    [0] => Apple

    [1] => Banana

    [2] => orange

    [3] => Pear

)

The array is now sorted alphabetically as expected. Without the flags, sorting would be case-sensitive and orange would come last, because it is lowercase. The same result can be achieved using natcasesort($fruits). See the example in array-use-sort-with-flags.php on GitHub.

In general, it is a good idea, when using a function, to consult the documentation about extended possibilities by using extra arguments. Often, a function does not exactly do what you want but can be made to do it by passing extra arguments.

Exercise 4.3: Using Built-In Functions with Arrays

In this exercise, we will see how the PHP built-in functions work with arrays:

  1. Create a file called array-functions.php in the exercises directory of Chapter04.
  2. Type the opening tag and the statement that creates the array named $signal, which contains the different colors in a traffic signal:

    <?php

    $signal = ['red', 'amber', 'green'];

  3. Display the array of integers in a human-readable format:

    print_r($signal);

  4. Execute the script using the following command:

    php array-functions.php

    The output is as follows:

    Figure 4.7: Printing the array of traffic signal colors

    Figure 4.7: Printing the array of traffic signal colors

    Notice in the preceding output how the array elements are indexed with colors and the first element is at index 0 and the third element at index 2. These are the default indices when you do not declare your own indices.

  5. Use the array_reverse function to reverse the array:

    $reversed = array_reverse($signal);

    The array_reverse() method will reverse the order of the array elements and return the result as a new array while leaving the original array unchanged.

  6. Print the reversed array:

    print_r($reversed);

    Execute the php array-functions.php command.

    The output looks like the following screenshot:

    Figure 4.8: Printing the reverse array

    Figure 4.8: Printing the reverse array

    Notice how element 3 is now the first element at index 0 of the array and element 1 is the last. At index 2, although the array is reversed, the indices stay at the same positions as in the original array.

  7. Add the following code again to print the original array:

    print_r($signal);

    The output is as follows:

    Figure 4.9: Printing the array

    Figure 4.9: Printing the array

    This is to demonstrate that the original array will not be changed by array_reverse.

  8. Open a Terminal and go to the directory where you just typed the array-functions.php script. Run the script and hit Enter:

    php array-functions.php

    Observe that three arrays are displayed. The output on the screen will look like the following screenshot when the array contains only three integers:

    Figure 4.10: Printing the three arrays

    Figure 4.10: Printing the three arrays

    The first array displays your array with integers, the second is your array with integers in reverse order, and the third is the unchanged original array with the integers in regular order, as you entered them.

  9. Change the statement that reverses the array to the following:

    $reversed = array_reverse($signal, $preserve_keys = true);

    What we did here has not always been possible in PHP, but it is possible today: we assign true to the $preserve_keys variable and, at the same time, we pass it as the second argument to array_reverse. The advantage of doing this is self-documenting the operations we are doing, and we can reuse the variable later if we need to. However, in general, this type of assignment can be easily overlooked and, if you do not need the variable later, it is probably better to just pass true. You might use this type of assignment depending on what you are building.

    Look carefully at the output when you run the script again:

    Figure 4.11: Printing the three arrays again

    Figure 4.11: Printing the three arrays again

    When you inspect the output, specifically the array in the middle, you will notice that the keys have been preserved in the reversed array. It is true that element 3 is now the first element in the array, but note that index 2 is now the first index as well. So, $integers[2] still contains the value of 3 and $integers[0] still holds the value of 1.

  10. Now let's declare another $streets array with the names of a few streets:

    $streets = [

        'walbrook',

        'Moorgate',//Starts with an uppercase

        'crosswall',

        'lothbury',

    ];

  11. Now let's sort the array with flags combined with the bitwise OR operator:

    sort($streets, SORT_STRING | SORT_FLAG_CASE );

    print_r($streets);

    The output is as follows:

    Figure 4.12: Printing the array in alphabetical order

    Figure 4.12: Printing the array in alphabetical order

    In this case, the sort() function sorts the string case-insensitively.

  12. If we sort the array using the bitwise AND operator, we will see that the street names starting with uppercase letters move to the top of the array and rest of the street names print in alphabetical order:

    sort($streets, SORT_STRING & SORT_FLAG_CASE );

    print_r($streets);

    The output is as follows:

Figure 4.13: Printing the words that start with uppercase letters at the top of the array

Figure 4.13: Printing the words that start with uppercase letters at the top of the array

In this exercise, you have seen one of the many powerful array manipulation functions of PHP at work. You learned that this function—array_reverse – returns a new array rather than modifying the original. You may deduce that the input argument, your array, is not passed by reference, because, otherwise, the original would have been changed by the reversion. You also learned that the second argument to this function – boolean $preserve_keys – if true does change the behavior of the function so that the elements stay at the same indices as before the reversion. You may deduce from this that the default value of the second argument is false. We then explored how to use the sort function to arrange the elements of an array in a specific order.

Introduction to User-Defined Functions

A user-defined function is a function that either you or another user has written and is not built into PHP itself. Built-in functions are generally faster than user-defined functions that do the same thing, as they are already compiled from C. Always look for built-in functions before you try to write your own!

Naming Functions

Naming things is difficult. Try to choose names for your functions that are descriptive but not overly long. Very long function names are unreadable. When you read the name, you should ideally be able to guess what the function does. The rules for naming identifiers in PHP apply here. Function names are case-insensitive; however, by convention, you do not call a function with casing that is different from how it was defined. Speaking of conventions, you are free to design the casing any way you like, but two flavors generally prevail: snake_case() or camelCase(). In all cases, do what your team agrees upon – consistency is far more important than any personal preference, no matter how strong. If you are free to choose your coding convention, then, by all means, stick to the PSR-1 standard as recommended by PHP-FIG (https://packt.live/2IBLprS). Although it refers to functions as methods (as in class methods), you may safely assume that this also applies to (global) functions, which this chapter is about. This means that if you are free to choose, you can choose camelCase() for functions.

Do not redeclare a built-in function (that is, do not write a function with the same name as a built-in function in the root namespace). Instead, give your own function a unique name, or put it in its own namespace. The best practice is to never use the name of an existing function for your own function, not even within your own namespace, to avoid confusion.

Documenting Your Functions

You may add a comment above a function, which is called a DocBlock. This contains annotations, prefixed with the @ symbol. It also contains a description of what the function does. Ideally, it also describes why the function is there:

 /**

 * Determines the output directory where your files will

 * go, based on where the system temp directory is. It will use /tmp as

 * the default path to the system temp directory.

 *

 * @param string $systemTempDirectory

 * @return string

 */

function determineOutputDirectory(string $systemTempDirectory = '/tmp'): string {

    // … code goes here

}

Namespaced Functions

Namespaces are a way to organize code so that name clashes are less likely. They were introduced around the time when different PHP libraries proposed classes called Date. If different libraries do this, you cannot use both libraries at the same time, because the second time a Date class is loaded, PHP will complain that you cannot redeclare the Date class, since it has already been loaded.

To solve this problem, we use namespaces. If two different vendors of libraries use their vendor name for the namespace and create their Date class within that namespace, the names are far less likely to clash.

You can think of a namespace as some kind of prefix. Say that You and Me are both vendors and we both want to introduce a Date class. Instead of naming the classes MeDate and YouDate, we create them in files that live in a Me directory and in a You directory. The class file will simply be called Date.php for both vendors. Inside your Date.php file, you will write the namespace as the very first statement (after the strict types declaration, if any):

<?php

namespace You;

class Date{}

We will write a Date.php file that starts as follows:

<?php

namespace Me;

class Date{}

Now, because the classes live in their own namespace, they have a so-called Fully Qualified Name (FQN). The FQNs are YouDate and MeDate. Notice that the names are different. You will learn more about namespaces in Chapter 5, Object-Oriented Programming, because they matter to objects more than functions.

Namespaced functions are rare, but they are possible. To write a function in a namespace, declare the namespace at the top of the file where you define the function:

<?php

namespace Chapter04;

function foo(){

    return 'I was called';

}

// call it, inside the same namespace:

foo();

And then call it in another file in the root namespace (no namespace):

<?php

require_once __DIR__ . '/chapter04-foo.php;

// call your function

Chapter04foo();

We could import the Chapter04 namespace near the top of the unit test with a use statement:

use Chapter04;

// later on in the test, or any other namespace, even the root namespace.

foo(); // will work, because we "use" Chapter04.

Pure Functions

Pure functions do not have side effects. Not-so-pure functions will have side effects. So, what is a side effect? Well, when a function has a side effect, it changes something that exists outside of the scope of a function.

Scope

You can think of scope as a "fence" within which a variable or function can operate. Within the function scope are its input, its output, and everything that is made available inside the function body. Functions can pull things out of the global scope into their own scope and alter them, thus causing side effects. To keep things simple, it is best when functions do not have side effects. It makes fault finding and unit testing easier when functions do not try to alter the environment in which they live, but, instead, just focus on their own responsibility.

Variables declared outside the function live in the global scope and are available within the function. Variables declared within the function body are not available outside the function scope, unless extra work is done.

In the following examples, two ways are used to demonstrate how variables from the global scope can be used inside a function:

<?php

// we are in global scope here

$count = 0;

function countMe(){

    // we enter function scope here

    // $count is pulled from global scope using the keyword global

    global $count;

    $count++;

}

countMe();

countMe();

echo $count;

The output is as follows:

2

After the function was called twice, $count will have the value of 2, which is essentially a count of how many times the function was called during a single script run. After the next run, the $count variable will be 2 again, because the value is not preserved between script runs and also because it is initialized at 0 each time the script runs. Regardless, values are not preserved between script runs, unless you persist them explicitly in a file or some form of cache or some other form of persistence layer.

In general, it is better for functions not to have side effects and not to meddle with global scope.

The $GLOBALS Superglobal Array

Global variables are always available inside the special $GLOBALS superglobal array. So, instead of using the global keyword, we could have incremented $GLOBALS['count']; in the previous example.

Exercise 4.4: Using the $GLOBALS array

In this exercise, you will change the function in count-me-with-GLOBALS.php so that it no longer uses the global keyword but uses the $GLOBALS superglobal array instead:

  1. Take another look at the function used in the previous example:

    <?php

    // we are in global scope here

    $count = 0;

    function countMe(){

        // we enter function scope here

        // $count is pulled from global scope using the keyword global

        global $count;

        $count++;

    }

  2. Remove the contents of the function body so that your function looks like this:

    function countMe()

    {

    }

    The function is now empty and does nothing.

  3. Add a new statement to the empty function body that increments count in the $GLOBALS array:

    function countMe()

    {

        $GLOBALS['count']++;

    }

    The function now does exactly the same as before, but with less code.

  4. Call the countMe() function twice. The script should now look like the script in count-me-with-GLOBALS.php:

count-me-with-GLOBALS.php

1  <?php

2  // declare global $count variable

3  $count = 0;

4  /**

5   * This function increments the global

6   * $count variable each time it is called.

7   */

8  function countMe()

9  {

10     $GLOBALS['count']++;

11 }

12 // call the function countMe once

13 countMe();

14 // and twice

15 countMe();

The output looks like the following screenshot when you run the script. The output is both a newline and a value of $count function:

Figure 4.14: Printing the count

Figure 4.14: Printing the count

The Single Responsibility Principle

A function is easier to use, more reliable when reused, and easier to test when it does only one thing – that is, when it has a single responsibility. When you need another task to be performed, do not add it to your existing function; just write another one instead.

The syntax of a function is as follows:

function [identifier] ([[typeHint][…] [&]$parameter1[…][= defaultValue]][, [&]$p2, ..$pn]])[: [?]returnType|void]

{

     // function body, any number of statements

     [global $someVariable [, $andAnother]] // bad idea, but possible

     [return something;]

}

Don't be put off by this apparently complex syntax definition. Functions are really easy to write, as the following examples in this chapter will show you.

However, let's now spend some time trying to break this syntax apart.

The function Keyword

The function keyword tells the PHP parser that what comes next is a function.

Identifier

The identifier represents the name of the function. Here, the general rules for identifiers in PHP will apply. The most important ones to remember are that it cannot start with a number and it cannot contain spaces. It can contain Unicode characters, although this is relatively uncommon. It is, however, quite common to define special, frequently used functions with underscores:

function __( $text, $domain = 'default' ) {

    return translate( $text, $domain );

}

This function is used to translate the text in WordPress templates. The idea behind it is that you will spot immediately that this function is something special and you won't be tempted to write a function with the same name yourself. It is also very easy to type, which is handy for frequently used functions. As you can see, it takes a required parameter, $text, to be translated. It also takes an optional $domain, in which the translation is defined, which is the default domain by default (a text domain in translations serves to separate different fields of interest that might have the same word for different things, so that these words can be translated differently if the other language has different words depending on the context). __ function is what we call a wrapper for the translate function. It passes its arguments on to the translate function and returns the return value of the translate function. It is faster to type and it takes up less space in templates, making them more readable.

Type Hints

In the function declaration, type hinting is used to specify the expected data type of an argument. Type hints for objects have existed since PHP 5.0 and for arrays since PHP 5.1. Type hints for scalars have existed since PHP 7.0. Nullable type hints have existed since PHP 7.1. A type hint for object has existed since PHP 7.2. Consider the following example:

function createOutOfCreditsWarning(int $maxCredits, string $period, int $waitDays): string

{

    $format = 'You have used the maximum amount of %d credits you are            

        allowed to spend in a %s. You will have to wait %d days before  

        new credits become available.';

    return sprintf($format, $maxCredits, $period, $waitDays);

}

In the preceding example, there are three type hints. The first one hints that $maxCredits should be an integer. The second one hints that $period should be a string, and the third one hints that $waitDays must be an integer.

If a type hint is prefixed with a question mark, as in ?int, this indicates that the argument must either be the hinted type or null. In this case, the hinted type is integer. This has been possible since PHP 7.1.

The Spread Operator (…) with Type Hints

The spread operator () is optional and indicates that the parameter has to be an array that only contains elements of the hinted type. Although it has existed since PHP 5.6, it is a rarely used yet very powerful and useful feature that makes your code more robust and reliable, with less code. There is no longer a need to check every element of a homogeneous array. When you define a parameter with such a type hint, you also need to call the function with the parameter prefixed with the spread operator.

The following is an example of a fictional function that I made up to demonstrate the use of the spread operator. The processDocuments function transforms XML documents using eXtensible Stylesheet Language Transformations (XSLT). While this is really interesting when you need to transform documents, it doesn't really matter for the demonstration of the spread operator.

The spread operator is the three dots before $xmlDocuments in the function signature. It means that $xmlDocuments must be an array that contains only objects of the DomDocument hinted type. A DomDocument hinted type is an object that can load and hold XML. It can be processed by an object of the XsltProcessor class, to transform the document into another document. XsltProcessor in PHP is very powerful and very performant. You can even use PHP functions inside your XSL style sheets. This nifty feature should be used with caution, however, because it will render your XSL style sheets useless to other processors as they do not know PHP.

The return type of the function is Generator. This is caused by the yield statement inside the foreach loop. The yield statement causes the function to return each value (a document, in our case) as soon as it becomes available. This means it is efficient with memory: it does not keep the objects in memory in an array to return them all at once, but instead returns them one by one immediately after creation. This makes a generator very performant on large sets while also using fewer memory resources:

function processDocuments(DomDocument … $xmlDocuments):Generator

{

    $xsltProcessor = new XsltProcessor();

    $xsltProcessor->loadStylesheet('style.xslt');

foreach($xmlDocuments as $document){

     yield $xsltProcessor->process($document);

    }

}

The preceding function may appear pretty confusing, but it is fairly simple. Let's start with the usage of the spread operator; this is used to signify that the parameter will be required as an array. Additionally, the parameters are type hinted as DomDocument objects, meaning that the parameters will be an array of DomDocument objects. Moving onto the function, we define a new instance of XsltProcessor and load in a style sheet for the processor. Note that this is a conceptual example and more information on XsltProcessor and style sheets can be found in the PHP documentation at https://packt.live/2OxT91A. Finally, we use a foreach loop to iterate through the array of documents and yield the results of the process method on each document. As document processing can be memory intensive, the use case for a generator is apparent if you can imagine passing a large array of documents to this function.

To call this function, use the following code:

// create two documents and load an XML file in each of them

$document1 = new DomDocument();

$document1->load($pathToXmlFile1);

$document2 = new DomDocument();

$document2->load($pathToXmlFile2);

// group the documents in an array

$documents = [$document1, $document2];

// feed the documents to our function

$processedDocuments = processDocuments(…$documents);

// because the result is a Generator, you could also loop over the

// result:

foreach(processDocuments(…$documents) as $transformedDocument) {

     // .. do something with it

}

Parameters in User-Defined Functions

When defining a function, you are allowed to define parameters for it. When you are defining a parameter, consider whether it is expected to always be of the same type or whether you can force the developer using your code to always pass the same type. For example, when integer values are expected, a type hint of int is a good idea. Even if a developer passes 2, which is a string, they can easily be educated to cast this to an integer before passing it to your function, using (int) "2". More realistically, 2 would be stored in a variable. So, now you have a type hint:

int

Next, you should come up with a good name for your parameter. Ideally, it should be descriptive, but not overly long. When you expect a DomDocument object, $domDocument, $xmlDocument, or simply $document can be fine names, while $doc might be a little too short and confusing to some people and just $d would be just bad:

int $offset

Does a default value make sense for $offset? In most cases, it will be 0, because we usually start a process at the beginning of something. So, 0 would make a great default value, in this case:

int $offset = 0

Now we have a parameter with a type hint of int and a default of 0. The parameter is now optional and should be defined after the parameters that are not optional.

If a parameter cannot be expected to always be of the same type, processing it in your function may be harder, because you might have to check its type in order to decide how you should treat it. This makes unit testing your function harder and it complicates fault finding if things go wrong, since your code will have several paths of execution, depending on the type of input.

When a parameter is prefixed with &, it means that if a scalar is passed, it will be passed by reference, instead of as a copy or literal. Objects are always passed by reference and, therefore, using & on an object parameter is redundant and does not change the behavior of the function.

Return Types in User-Defined Functions

Return types are written as a colon followed by the type name. Return types were introduced in PHP 7. They make your code more robust because you are more explicit about what you expect from your function, and this can be checked at compile time rather than failing at runtime when something goes wrong, possibly in production. If you use an IDE, it will warn you when a return type does not match what you actually return or expect from the function. This means you can correct the error before it hits your users.

In the preceding example, the processDocuments function has a return type of Generator. A Generator type generates values and makes them available as soon as possible. This can be very performant: you don't have to wait for all the values to become available before processing them further. You can start with further processing as soon as the first value comes out of the Generator type. The Generator type churns out a value each time the yield language construct is used.

yield was introduced in PHP 5. At the time of writing, we are at PHP 7.3 and there are still many developers who have never used yield or do not even know what it does. When you are processing arrays or records from a database, for example, and you need extreme performance, consider whether you have a use case for a Generator type.

You can use void as the return type to indicate that nothing is returned from the function.

Signature

The following part of the function declaration is called the signature:

([typeHint [&]$parameter1[= defaultValue], [&]$p2, …])[: returnType]

So, the signature of a function defines its parameters and the return type.

Returning a Value

A function may return a value or not. When the function does not return anything, not even null, the return type can be void as of PHP 7.1. Values are returned by typing return followed by what you want to return. This can be any valid expression or just a single variable or literal:

return true;

return 1 < $var;

return 42;

return $documents;

return; // return type will be "void" if specified

return null; // return type must be nullable if specified

Parameters and Arguments

Functions accept arguments. An argument is a literal, variable, object, or even callable that you pass into a function for the function to act upon. If a parameter is defined at the position of the argument, you can use the argument inside your function by using the name of the parameter. The number of parameters may be variable or fixed. PHP allows you to pass more parameters than the function signature defines. If you want dynamic parameters, PHP has two built-in functions that make this possible; you can get the number of parameters with func_num_args() and the parameters themselves with func_get_args(). To show these functions in action, we will take a look at an example.

Here's an example of using func_num_args(). In this example, we define a method that will have no predefined parameters/arguments. But using the built-in func_num_args function, we will be able to count how many parameters/arguments are passed:

function argCounter() {

   $numOfArgs = func_num_args();

    echo "You passed $numOfArgs arg(s)";

}

argCounter(1,2,3,4,5);

The output is as follows:

You passed 5 arg(s)

Now that we can count the number of arguments, we can combine that function with func_get_args() to loop through and see what was passed. Here's an example of using func_get_args():

function dynamicArgs(){

     $count = func_num_args();

     $arguments = func_get_args();

     if($count > 0){

           for($i = 0; $i < $count; $i++){

                echo "Argument $i: $arguments[$i]";

                echo PHP_EOL;

           }

     }

}

dynamicArgs(1,2,3,4,5);

The output is as follows:

Argument 0: 1

Argument 1: 2

Argument 2: 3

Argument 3: 4

Argument 4: 5

Optional Parameters

Parameters to functions are optional when they have default values defined for them:

function sayHello($name = 'John') {

    return "Hello $name";

}

This function defines a parameter, $name, with a default value of John. This means that when calling the function, you do not need to provide the $name parameter. We say that the $name parameter is optional. If you do not provide a $name parameter, John will be passed anyway for the $name parameter. Optional parameters should be defined at the very end in the function signature, because, otherwise, if any required parameters come after the optional ones, you would still have to provide the optional parameters when calling the function.

The example is in function-with-default-value.php. The various usages are documented in the TestSayHello.php unit test.

Parameters Passed by Reference to Our Function

Remember the countMe function? It used a global variable named $count to keep track of how many times the function was called. This could also have been accomplished by passing the $count variable by reference, which is also a slightly better practice than polluting the global scope from within your function:

<?php

function countMeByReference(int &$count): void

{

    $count++;

}

Use it further down in the same script, as follows:

$count = 0;

countMeByReference($count);

countMeByReference($count);

countMeByReference($count);

echo $count; // will print 3

Please note that calling methods in the same script as they are defined in is perfect for exercises and playing with code and also for simple scripts, but doing this is actually a violation of PSR-1. This is a coding convention that states that files either define functions (not causing side effects) or use them (causing side effects).

Default Values for Parameters

In the following example, we are demonstrating the use of default values. By defining a default value, you give the developer using the function the ability to use the function as is without having to pass their own value.

Consider the following example:

/**

* @param string $systemTempDirectory

* @return string

*/

function determineOutputDirectory(string $systemTempDirectory = '/tmp'): string

{

    return $systemTempDirectory . DIRECTORY_SEPARATOR . 'output';

}

Between the parentheses is the function signature, which consists of a single parameter, $systemTempDirectory, with a type hint of string and a default value of /tmp. This means that if you pass a directory with your function call, it must be a string. If you do not pass an argument, the default value will be used.

Exercise 4.5: Writing a Function that Adds Two Numbers

Now that you've read through some of the theory behind writing your own functions, let's make a start on actually writing some of our own. In this exercise, we will create a simple function that adds two numbers and prints its sum:

  1. Find the add.php file in Chapter04/exercises/.
  2. Start typing the following comment in the file and type the function template:

    <?php

    function add($param1, $param2): string

    {

    }

    You start with the function keyword; then the name of the function, add; the opening brace; the $param1 and $param2 parameters; the closing brace; the colon to announce the return type; the return type, string; and, finally, the function body, {}.

  3. Inside the function body, type a check to see whether the parameters are numeric values by using is_numeric(). This built-in function returns true if its argument represents a numeric value, even when its type is string. So, it will return true for 23 and 0.145 and 10E6, for example. The latter is a scientific notation of 1,000,000:

    if (false === is_numeric($param1)) {

        throw new DomainException('$param1 should be numeric.');

    }

    if (false === is_numeric($param2)) {

        throw new DomainException('$param2 should be numeric.');

    }

    We throw an exception when the value is not numeric and cannot be added. Don't worry about exceptions now; they will be explained in the next chapter.

  4. Now that you can be sure that both values are numeric and can be added without unexpected results, it is time to actually add them. Continue typing in the function body:

    $sum = $param1 + $param2;

  5. Now it is time to compose the requested message. On the next line, type the following:

    return "The sum of $param1 and $param2 is: $sum";

    What you see in action here is called string interpolation. It is a way of expressing that the values of $param1, $param2, and $sum will be expanded into the string sentence. They will also be automatically cast to a string.

  6. String interpolation, although really fast, is still a relatively costly operation for the PHP parser. If you need to maximize performance for a use case where every nanosecond counts, then it would be better for you to use string concatenation because it is faster. Here is the same line written using string concatenation:

    return 'The sum of ' . $param1 . ' and ' . $param2 ' . '  is: ' . $sum;

    The dot (.) is the string concatenation operator. It glues two strings together. Other types of values are cast to strings automatically before the concatenation happens.

  7. Now you can write the following after your function:

    echo add(1, 2);

  8. Add a newline for clarity of the output:

    echo PHP_EOL;

  9. Run the script from the exercises directory:

    php add.php

    The output is as follows:

Figure 4.15: Printing the sum

Figure 4.15: Printing the sum

In this exercise, you have learned how to validate and process the arguments to your function and how to format and return some output. You have also learned how to perform some very simple math with PHP.

Variable Functions

If you store a function name in a variable, you can call this variable as a function. Here's an example:

$callable = 'strtolower';

echo $callable('Foo'); // will print foo;

This is not limited to built-in functions. In fact, you can do the same thing with your own functions.

Anonymous Functions

These are functions without identifiers (refer to the following syntax). They can be passed into any function that accepts a callable as input. Consider the following example:

function(float $value): int{

    if (0 <= $value) {

        return -1; // this is called an early return

    }

    return 1;

}

The preceding is an anonymous function, also called a closure. It does not have a name, so it cannot be called by its name, but it can be passed into another function that does accept a callable as input.

If you want to call the anonymous function, there are two ways to achieve this:

echo (function(float $value): int{

    if (0 <= $value) {

        return 1;

    }

    return -1;

})(2.3);

In the preceding example, the function is created and called immediately with the 2.3 argument. The output that is returned will be 1, because 2.3 is greater than 0. Then echo prints the output. In this setup, the anonymous function can be called only once – there is no reference to it that would allow you to call it again.

In the next example, the function will be stored in a variable named $callable. You may name the variable whatever you like, as long as you stick to the rules for naming variables in PHP:

$callable = function(float $value): int{

    if (0 <= $value) {

        return 1;

    }

    return -1;

}; // here semicolon is added as we assign the function to $callable variable.

echo $callable(-11.4); // will print -1, because -11.4 is less than 0.

Using a Variable from Outside of Scope Inside an Anonymous Function

As stated previously in this chapter, you may need to use a variable that was defined outside of the scope of the function you are defining. In the following exercise, you will see an example of how we can make use of the use keyword to pass a variable to the anonymous function.

Exercise 4.6: Working with Anonymous Functions

In this exercise, we will declare an anonymous function and examine how it works:

  1. Create a new file named callable.php. Add your opening PHP tag as follows:

    <?php

  2. Then, define the initial variable that you want to use:

    $a = 15;

  3. Now define your callable function and pass your $a variable to it:

    $callable = function() use ($a) {

        return $a;

    };

  4. On the next line, let's assign a new value to $a:

    $a = 'different';

  5. To see what the current value of $a is, we will call $callable and print it to the screen:

    echo $callable();

  6. Lastly, add a new line for readability:

    echo PHP_EOL;

  7. We can now run this script in the command line using the following command:

    php callable.php

    The output is as follows:

    15

    So, what's happening here? First, we declare an anonymous function and store it in $callable. We say it should use $a by using the use keyword. Then, we change the value of $a to different, call our $callable function, and then echo the result. The result is 15, which is the initial value of $a. The reason for this is that when using use to import $a into the scope of the function, $a will be used exactly as it was at the time of function creation.

    Now what happens when we use $a as a reference? Let's take a look:

    <?

    $a = 15;

    $callable = function() use (&$a) {

        return $a;

    };

    $a = 'different';

    echo $callable(); // outputs 'different'

    // newline for readability

    echo PHP_EOL;

    Note that we prefixed $a with & this time. Now the output will be 'different'.

Since objects are always passed by reference, this should also be true for objects, but that is something that will be covered in another chapter.

Exercise 4.7: Creating Variable Functions

In this exercise, we will create variable functions and examine how they work in PHP:

  1. Open a file and name it variable-hello.php. Start your script with the opening PHP tag and set the strict type to 1:

    <?php

    declare(strict_types=1);

  2. Declare a variable to store the value of  the function as follows:

    $greeting = function(string $name): void

    {

        echo 'Hello ' . $name;

    };

    That's all you need and even a bit more, because you have added a string type hint and a void return type, which are both optional. They are good practice, so make a habit of using them. Note that the closure does not return output. Instead, it prints the greeting directly to stdOut.

  3. Now continue typing in your variable-hello.php script:

    $greeting('Susan');

  4. Add a newline:

    echo PHP_EOL;

  5. Verify that the output on the Terminal is Hello Susan:
Figure 4.16: Printing the output

Figure 4.16: Printing the output

In this exercise, you have learned how to use string concatenation together with a function argument and how to print output directly from a function. Although this is a bad practice in many cases, it might be useful in other scenarios.

Exercise 4.8: Playing with Functions

In this exercise, we will use a couple more predefined functions to learn about processing data and writing our processors so that they are reusable. The goal of this exercise is to take an array of directors and their movies and sort them by the director's name. We then want to process that array and print out the director's name where the first letter of the first name is in uppercase and the last name is all in uppercase. Additionally, for the movies, we want to capitalize each title, wrap them in double quotes, and separate them using commas. We will build two functions that will handle the processing of the director's name and another function for movies. We will be making use of three new built-in functions that we have yet to discuss: ksort, explode, and implode. To learn more about these functions, please review the documentation on https://packt.live/2OxT91A:

  1. First, we are going to create a new file called activity-functions.php and start our script with the opening PHP tag:

    <?php

  2. Then, we will go ahead and define an array that will hold the director's name as a key and an array of their movies for the value:

    activity-functions.php

    2  $directors = [

    3      'steven-spielberg' => [

    4          'ET',

    5          'Raiders of the lost ark',

    6          'Saving Private Ryan'

    7      ],

    8      'martin-scorsese' => [

    9          'Ashes and Diamonds',

    10         'The Leopard',

    11         'The River'

    12     ],

  3. Now we will write our first function to process our director's name. Remember, we want the first name to have a capitalized first letter and the last name will be fully capitalized:

    function processDirectorName($name){

         $nameParts = explode('-', $name);

         $firstname = ucfirst($nameParts[0]);

         $lastname = strtoupper($nameParts[1]);

         return "$firstname $lastname";

    }

  4. Next, we will write a function to process our movie strings. Note that we want to wrap the uppercase version of each movie name and separate them with commas:

    function processMovies($movies)

    {

        $formattedStrings = [];

        for ($i = 0; $i < count($movies); $i++) {

            $formattedStrings[] = '"' . strtoupper($movies[$i]) . '"';

        }

        return implode(",", $formattedStrings);

    }

  5. Finally, we can sort our array via the array keys, and loop through and process the array:

    ksort($directors);

    foreach ($directors as $key => $value) {

        echo processDirectorName($key) . ": ";

        echo processMovies($value);

        echo PHP_EOL;

    }

  6. We can now run this script in the Terminal:

    php activity-functions.php

    You should see an output like the following:

    Felix GARY: "MEN IN BLACK: INTERNATIONAL","THE FATE OF THE FURIOUS","LAW ABIDING CITIZEN"

    Kathryn BIGELOW: "DETROIT","LAST DAYS","THE HURT LOCKER"

    Martin SCORSESE: "ASHES AND DIAMONDS","THE LEOPARD","THE RIVER"

    Steven SPIELBERG: "ET","RAIDERS OF THE LOST ARK","SAVING PRIVATE RYAN"

    Note

    The third part of Felix Gary Gray is truncated in the output. Can you refactor the code to fix this bug?

Activity 4.1: Creating a Calculator

You are working on a calculator-based web app. You are given all of the user interface code but are instructed to build the function that will actually do the calculations. You are instructed to make a single function that is reusable for all the calculations that are needed within the app.

The following steps will help you to complete the activity:

  1. Create a function that will calculate and return the factorial of the input number.
  2. Create a function that will return the sum of the input numbers (a varying number of parameters).
  3. Create a function that will evaluate the $number input, which has to be an integer and will return whether the number is a prime number or not. The return type of this function is a Boolean (bool).
  4. Create a base performOperation function that will handle the predefined mathematical operations. The first parameter of the performOperation function must be a string, either 'factorial', 'sum', or 'prime'. The remaining arguments are passed to the mathematical function being called as arguments.

    Note

    A factorial is the product of an integer and all of the integers below it.

The output should look similar to the following. The output values will depend on the numbers that you input:

Figure 4.17: Expected output

Figure 4.17: Expected output

Note

The solution for this activity can be found on page 511.

Summary

In this chapter, you learned how you can use functions that are built into PHP to accomplish many tasks that would otherwise require you to write a lot of code to do the same thing much less quickly. You also learned various ways to write your own functions: with and without parameters, using default values or not, or even with varying amounts of parameters. You gained an understanding of functions that are pure and do not meddle with global scope versus functions that do have side effects, either because they pull variables from the global scope or receive parameters by reference and change them. You learned that you can call functions by their name or as callables stored in variables, anonymously or by name. Hopefully, you have got a taste of how flexible and powerful functions are and how they can help you to write robust code by enforcing strict types.

In the next chapter, you will learn how to combine constants, variables, and functions that belong together logically into objects. This will give you an even higher level of organization in your code and will take information hiding to the next level by restricting the access level of variables and functions that are part of objects. Please remember that we call variables that live on object properties and we call functions that live on objects methods, while constants that live on objects are called class constants. Although they have a different name, they behave in a very similar way, so you will be able to reuse everything you learned in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset