Every computer program in every language is built by tying various components of business logic together. Often, these components need to be somewhat reusable, encapsulating common functionality that needs to be referenced in multiple places throughout an application. The easiest way to make these components modular and reusable is to encapsulate their business logic into functions, specific constructs within the application that can be referenced elsewhere throughout an application.
For example, Example 3-1 illustrates how a simple program might be written to capitalize the first character in a string. Coding without using functions is considered imperative or simple procedural programming as we define exactly what the program needs to accomplish one command (or line of code) at a time.
$str
=
"this is an example"
;
if
(
ord
(
$str
[
0
])
>=
97
&&
ord
(
$str
[
0
])
<=
122
)
{
$str
[
0
]
=
chr
(
ord
(
$str
[
0
])
-
32
);
}
echo
$str
.
PHP_EOL
;
// This is an example
$str
=
"and this is another"
;
if
(
ord
(
$str
[
0
])
>=
97
&&
ord
(
$str
[
0
])
<=
122
)
{
$str
[
0
]
=
chr
(
ord
(
$str
[
0
])
-
32
);
}
echo
$str
.
PHP_EOL
;
// And this is another
$str
=
"3 examples in total"
;
if
(
ord
(
$str
[
0
])
>=
97
&&
ord
(
$str
[
0
])
<=
122
)
{
$str
[
0
]
=
chr
(
ord
(
$str
[
0
])
-
32
);
}
echo
$str
.
PHP_EOL
;
// 3 examples in total
The references to ord()
and chr()
are references to native functions defined by PHP itself. ord()
returns the binary value of a character as an integer. Similarly, chr()
converts a binary value (represented as an integer) into its corresponding character.
When we write code without defining functions, our code ends up rather repetitive as we’re forced to copy and paste identical blocks of code throughout the application. This violates one of the key principles of software development: DRY, or “don’t repeat yourself.”
A common way to describe the opposite of this principle is WET, or “write everything twice.” Writing the same block of code over again leads to two problems:
Our code becomes rather long and difficult to maintain
If the logic within the repeated code block needs to change, we have to update several parts of our program every time
Rather than repeating logic imperatively, as we did in Example 3-1 we can define a function that wraps this logic and later invoke that function directly as in Example 3-2. Defining functions is an evolution of imperative/procedural programming that augments the functions provided by the language itself with those defined by our application.
function
capitalize_string
(
$str
)
{
if
(
ord
(
$str
[
0
])
>=
97
&&
ord
(
$str
[
0
])
<=
122
)
{
$str
[
0
]
=
chr
(
ord
(
$str
[
0
])
-
32
);
}
return
$str
;
}
$str
=
"this is an example"
;
echo
capitalize_string
(
$str
)
.
PHP_EOL
;
// This is an example
$str
=
"and this is another"
;
echo
capitalize_string
(
$str
)
.
PHP_EOL
;
// And this is another
$str
=
"3 examples in total"
;
echo
capitalize_string
(
$str
)
.
PHP_EOL
;
// 3 examples in total
User-defined functions are incredibly powerful and quite flexible. The capitalize_string()
function in Example 3-2 is relatively simple — it takes a single string parameter and returns a string. However, there is no indication in the function as defined that the $str
parameter must be a string - you could just as easily pass a number or even an array as follows:
$out
=
capitalize_string
(
25
);
// 25
$out
=
capitalize_string
([
'a'
,
'b'
]);
// ['A', 'b']
Recall our discussion of PHP’s loose type system from Chapter 1 — by default, PHP will try to infer what you mean when you pass a parameter into capitalize_string()
and, in most cases, will return something useful. In the case of passing an integer, PHP will trigger a warning that you are trying to access elements of an array incorrectly, but it will still return an integer without crashing.
More sophisticated programs can add explicit typing information to both the function parameters and its return to provide safety checks around this kind of usage. Other functions could return multiple values rather than a single item. The recipes that follow cover a variety of ways functions can be used in PHP and begins scratching at the surface of building a full application.
You want to access the values passed into a function when it’s called elsewhere in a program.
Use the variables defined in the function signature within the body of the function itself as follows:
function
multiply
(
$first
,
$second
)
{
return
$first
*
$second
;
}
multiply
(
5
,
2
);
// 10
$one
=
7
;
$two
=
5
;
multiply
(
7
,
5
);
// 35
The variable names defined in the function signature are available only within the scope of the function itself and will contain values matching the data passed into the function when its called. Inside the curly braces that define the function, you can use these variables as if you’ve defined them yourself. Just know that any changes you make to those variables will only be available within the function and won’t impact anything elsewhere in the application by default.
Example 3-3 illustrates how a specific variable name can be used both within a function and outside a function while referring to two, completely independent values. Said another way, changing the value of $number
within the function will only impact the value within the function, not within the parent application.
function
increment
(
$number
)
{
$number
+=
1
;
return
$number
;
}
$number
=
6
;
echo
increment
(
$number
);
// 7
echo
$number
;
// 6
By default, PHP passes values into functions rather than passing a reference to the variable. In Example 3-3 this means PHP passed the value 6
into a new $number
variable within the function, performed a calculation, and returned the result. The $number
variable outside the function was entirely unaffected.
PHP passes simple values (strings, integers, Booleans, arrays) by value by default. More complex objects, however, are always passed by reference. In the case of objects, the variable inside the function points back to the same object as the variable outside the function rather than to a copy of it.
In some cases, you might want to explicitly pass a variable by reference rather than just passing its value. In that case, we need to modify the function signature as this is a change to its very definition rather than something that can be modified when the function is called. Example 3-4 illustrates how our increment()
function would change to pass $number
by reference instead of by value.
function
increment
(
&
$number
)
{
$number
+=
1
;
return
$number
;
}
$number
=
6
;
echo
increment
(
$number
);
// 7
echo
$number
;
// 7
In reality, the variable name doesn’t need to match both inside and outside the function - I’m using $number
in both cases here to illustrate the difference in scoping. If we stored an integer in $a
and passed that variable instead as increment($a)
the result would be identical to the example in Example 3-4.
PHP reference documentation on user-defined functions and passing variables by reference.
You want to set a default value for a function’s parameter so invocations don’t have to pass it.
Assign a default value within the function signature itself. For example:
function
get_book_title
(
$isbn
,
$error
=
'Unable to query'
)
{
try
{
$connection
=
get_database_connection
();
$book
=
$query_isbn
(
$connection
,
$isbn
);
return
$book
->
title
;
}
catch
{
return
$error
;
}
}
get_book_title
(
'978-1-098-12132-7'
);
The example in the Solution attempts to query a database for the title of a book based on its ISBN. If the query fails for any reason, the function will return the string passed into the $error
parameter instead.
To make this parameter optional, the function signature assigns a default value - when calling get_book_title()
with a single parameter, the default $error
value is used automatically. You alternatively have the option to pass your own string into this variable when invoking the function, such as get_book_title(978-1-098-12132-7, Oops!);
.
When defining a function with default parameters, it’s a best practice to place all parameters with default values last in the function signature. While it is possible to define parameters in any order, doing so makes it difficult to call the function properly.
Example 3-5 illustrates the kinds of problems that can come up by placing optional parameters before required ones.
It is possible to define function parameters with specific defaults in any order. However, declaring mandatory parameters after optional ones is deprecated as of PHP 8.0. Continuing to do so might result in an error in a future version of PHP.
function
brew_latte
(
$flavor
=
'unflavored'
,
$shots
)
{
return
"
Brewing a
{
$shots
}
-shot,
{
$flavor
}
latte!
"
;
}
brew_latte
(
'vanilla'
,
2
);
brew_latte
(
3
);
There might be cases where the parameters themselves make logical sense to be in a particular order (to make the code more readable, for example). Know that if any parameters are required, every parameter to their left is also effectively required even if you try to define a default value.
Examples of default arguments in the PHP manual.
You want to pass arguments into a function based on the name of the parameter rather than its position.
Use the named argument syntax while calling a function as follows:
array_fill
(
start_index
:
0
,
count
:
100
,
value
:
50
);
By default, PHP leverages positional parameters in function definitions. The Solution example references the native array_fill()
function that has the following function signature:
array_fill
(
int
$start_index
,
int
$count
,
mixed
$value
)
:
array
Basic PHP coding must supply arguments to array_fill()
in the same order in which they’re defined - $start_index
followed by $count
followed by $value
. While the order itself is not a problem, making sense of what each value means when scanning visually through code can be a challenge. Using the basic, ordered parameters, the Solution example would be written as follows, requiring deep familiarity with the function signature to know which integer represents which parameter:
array_fill
(
0
,
100
,
50
);
Named function parameters disambiguate which value is being assigned to which internal variable. They also allow for arbitrary reordering of parameters when you invoke the function as said invocation is now explicit as to which value is assigned to which parameter.
Another key advantage of named arguments is that optional arguments can be skipped entirely during function invocation. Consider a verbose activity logging function like in Example 3-6 where multiple parameters are considered optional as they set defaults:
activity_log
(
string
$update_reason
,
string
$note
=
''
,
string
$sql_statement
=
''
,
string
$user_name
=
'anonymous'
,
string
$ip_address
=
'127.0.0.1'
,
?
DateTime
$time
=
null
)
:
void
Internally, Example 3-6 will use its default values when it’s called with a single argument - if $time
is null
it will silently replace it with a new DateTime
instance representing “now.” However, there might be times where we want to populate one of these optional parameters without wanting to explicitly set all of them.
Consider the use case where we want to rehydrate events from a static log file. User activity was anonymous (so the defaults for $user_name
and $ip_address
are adequate), but we need to explicitly set the date at which an event occurred. Without named arguments, an invocation in this case would appear as follows:
activity_log
(
'Testing a new system'
,
''
,
''
,
'anonymous'
,
'127.0.0.1'
,
new
DateTime
(
'2021-12-20'
)
);
With named arguments, we can skip setting default parameters to their defaults entirely and explicitly set just the parameters we need to. The preceding code code be simplified to the following:
activity_log
(
'Testing a new system'
,
time
:
new
DateTime
(
'2021-12-20'
));
In addition to drastically simplifying our usage of activity_log()
, the usage of named parameters has the added benefit of keeping our code DRY. The default values for our arguments are stored directly in the function definition rather than being copied to every invocation of the function as well. If we later need to change a default, we can edit the function definition alone.
The original RFC proposing named parameters.
You want to force your program to implement type safety and avoid PHP’s native loose type comparisons.
Add input and return types to function definitions. Optionally add a strict type declaration to the top of each file to enforce values match type annotations (and emit a Fatal error
if they don’t match). For example:
declare
(
strict_types
=
1
);
function
add_numbers
(
int
$left
,
int
$right
)
:
int
{
return
$left
+
$right
;
}
add_numbers
(
2
,
3
);
add_numbers
(
2
,
'3'
);
PHP natively supports various scalar types and allows developers to annotate both function input parameters and returns to identify the kinds of values that are allowable for each. In addition, developers can specify their own custom classes as types, or leverage class inheritance within the type system.1
Parameter types are annotated by placing the type directly before the name of the parameter when defining the function. Similarly, return types are specified by appending the function signature with a :
and the type that function would return as in the following:
function
name
(
type
$parameter
)
:
return_type
{
// ...
}
The simplest types leveraged by PHP are enumerated in Table 3-1.
Type | Description |
---|---|
array |
The value must be an array (containing any type of values) |
callable |
The value must be a callable function |
bool |
The value must be Boolean |
float |
The value must be a floating point number |
int |
The value must be an integer |
string |
The value must be a string |
The value must be an array or an object that implements |
|
The object can be any value |
In addition, both built-in and custom classes can be used to define types as shown in Table 3-2.
Type | Description |
---|---|
Class/Interface name |
The value must be an instance of the specified class or interface |
self |
The value must be an instance of the same class as the one in which the declaration is used |
parent |
The value must be an instance of the parent of the class in which the declaration is used |
object |
The value must be an instance of an object |
PHP also permits simple scalar types to be expanded by either making them nullable or by combining them into union types. To make a specific type nullable, you have to prefix the type annotation with a ?
. This will instruct the compiler to allow values to be either the specified type or null
as in the following example:
function
say_hello
(
?
string
$message
)
:
void
{
echo
'Hello, '
;
if
(
$message
===
null
)
{
echo
'world!'
;
}
else
{
echo
$message
.
'!'
;
}
}
say_hello
(
'Reader'
);
// Hello, Reader!
say_hello
(
null
);
// Hello, World!
A union type declaration combines multiple types into a single declaration by concatenating simple types together with the pipe character (|
). If we were to rewrite the type declarations on the Solution example with a union type combining strings and integers, the Fatal error thrown by passing a string in for addition would resolve itself. Consider the possible rewrite in Example 3-7 that would permit either integers or strings as parameters.
function
add_numbers
(
int
|
string
$left
,
int
|
string
$right
)
:
int
{
return
$left
+
$right
;
}
add_numbers
(
2
,
'3'
);
// 55
The biggest problem with the Example 3-7 is that adding strings together with the +
operator has no meaning in PHP. If both parameters are numeric (either integers or integers represented as strings) the function will work just fine. If either is a non-numeric string, PHP will throw a TypeError
as it doesn’t know how to “add” two strings together. These kinds of errors are what we hope to avoid by adding type declarations to our code and enforcing strict typing - they formalize the contract we expect our code to support and encourage programming practices that naturally defend against coding mistakes.
By default, PHP uses its typing system to hint at what types are allowed into and returned from functions. This is useful to prevent passing bad data into a function, but relies heavily on either developer diligence or additional tooling2 to enforce typing. Rather than rely on humans’ ability to check code, PHP allows for a static declaration in each file that all invocations should follow strict typing.
Placing declare(strict_types=1);
at the top of a file tells the PHP compiler you intend for all invocations in that file to obey parameter and return type declarations. Note that this directive applies to invocations within the file where it’s used, not to the definitions of functions in that file. If you call functions from another file, PHP will honor the type declarations in that file as well. However, placing this directive in your file will not force other files that reference your functions to obey the typing system.
PHP documentation on type declarations and the declare
construct.
You want to define a function that takes one or more arguments without knowing ahead of time how many values will be passed in.
Use PHP’s spread operator (…
) to define a variable number or arguments.
function
greatest
(
int
...
$numbers
)
:
int
{
$greatest
=
0
;
foreach
(
$numbers
as
$number
)
{
if
(
$number
>
$greatest
)
{
$greatest
=
$number
;
}
}
return
$greatest
;
}
greatest
(
7
,
5
,
12
,
2
,
99
,
1
,
415
,
3
,
-
7
,
4
);
// 415
The spread operator automatically adds all parameters passed in that particular position or after it to an array. This array can be typed by prefixing the spread operator with a type declaration (review Recipe 3.4 for more on typing), thus requiring every element of the array to match a specific type. Invoking the function defined in the Solution example as greatest(2, "five");
will throw a type error as we have explicitly declared an int
type for every member of the $numbers
array.
Your function can accept more than one positional parameter while still leveraging the spread operator to accept an unlimited number of additional arguments. The following example will print a greeting to the screen for an unlimited number of individuals:
function
greet
(
string
$greeting
,
string
...
$names
)
:
void
{
foreach
(
$names
as
$name
)
{
echo
$greeting
.
', '
.
$name
.
PHP_EOL
;
}
}
greet
(
'Hello'
,
'Tony'
,
'Steve'
,
'Wanda'
,
'Peter'
);
// Hello, Tony
// Hello, Steve
// Hello, Wanda
// Hello, Peter
greet
(
'Welcome'
,
'Alice'
,
'Bob'
);
// Welcome, Alice
// Welcome, Bob
The spread operator has more utility than just in function definition. While it can be used to pack multiple arguments into an array, it can also be used to unpack an array into multiple arguments for a more traditional function invocation. Example 3-8 provides a trivial illustration of how this array unpacking works by passing an array into a function that does not accept an array by leveraging the spread operator.
function
greet
(
string
$greeting
,
string
$name
)
:
void
{
echo
$greeting
.
', '
.
$name
.
PHP_EOL
;
}
$params
=
[
'Hello'
,
'world'
];
greet
(
...
$params
);
// Hello, world!
In some cases, a more complex function might return multiple values (as discussed in the next recipe) so passing the return of one function into another becomes simple with the spread operator. In fact, any array or variable that implements PHP’s Traversable interface can be unpacked into a function invocation in this manner.
PHP documentation on variable argument lists.
You want to return multiple values from a single function invocation.
Rather than returning a single value, instead return an array of multiple values and unpack them using list()
outside of the function.
function
describe
(
float
...
$values
)
:
array
{
$min
=
min
(
$values
);
$max
=
max
(
$values
);
$mean
=
array_sum
(
$values
)
/
count
(
$values
);
$variance
=
0.0
;
foreach
(
$values
as
$val
)
{
$variance
+=
pow
((
$val
-
$mean
),
2
);
}
$std_dev
=
(
float
)
sqrt
(
$variance
/
count
(
$values
));
return
[
$min
,
$max
,
$mean
,
$std_dev
];
}
$values
=
[
1.0
,
9.2
,
7.3
,
12.0
];
list
(
$min
,
$max
,
$mean
,
$std
)
=
describe
(
...
$values
);
PHP is only capable of returning one value from a function invocation, but that value itself could be an array containing multiple values. When paired with PHP’s list()
construct, this array can be easily destructured to individual variables for further use by the program.
While the need to return many different values isn’t common, when the occasion comes up it can be incredibly handy. One example is in web authentication. Many modern systems today use JSON Web Tokens (JWTs), which are period-delimited strings of Base64-encoded data. Each component of a JWT represents a separate, discrete thing - a header describing the algorithm used, the data in the token payload, and a verifiable signature on that data.
When reading a JWT as a string, PHP applications often leverage the built-in explode()
function to split the string on the periods delimiting each component. A simple use of explode()
might appear as follows:
$jwt_parts
=
explode
(
'.'
,
$jwt
);
$header
=
base64_decode
(
$jwt_parts
[
0
]);
$payload
=
base64_decode
(
$jwt_parts
[
1
]);
$signature
=
base64_decode
(
$jwt_parts
[
2
]);
The preceding code works just fine, but the repeated references to positions within an array can be difficult to follow both during development and debugging later if there is a problem. In addition, developers must manually decode every part of the JWT separately - forgetting to invoke base64_decode()
could be fatal to the operation of the program.
An alternative approach would be to unpack and automatically decode the JWT within a function and return an array of the components as follows:
function
decode_jwt
(
string
$jwt
)
:
array
{
$parts
=
explode
(
'.'
,
$jwt
);
return
array_map
(
'base64_decode'
,
$parts
);
}
list
(
$header
,
$payload
,
$signature
)
=
decode_jwt
(
$jwt
);
A further advantage of using a function to unpack a JWT rather than decomposing each element directly is that you could build in automated signature verification or even filter JWTs for acceptability based on the encryption algorithms declared in the header. While this logic could be applied procedurally while processing a JWT, keeping everything in a single function definition leads to cleaner, more maintainable code.
The biggest drawback to returning multiple values in one function call is in typing - these functions have an array
return type, but PHP doesn’t natively allow for specifying the type of the elements within an array. There are potential workarounds to this limitation by way of documenting the function signature and integrating with a static analysis tool like Psalm, but no native support within the language for typed arrays. As such, if you’re using strict typing (and you should be using strict typing), returning multiple values from a single function invocation should be a rare occurrence.
Recipe 3.5 on passing a variable number of arguments and Recipe 1.3 for more on PHP’s list()
construct. Also reference the phpDocumentor documentation on typed arrays that can be enforced by tools like Psalm.
Your function needs to reference a globally-defined variable from elsewhere in the application.
Prefix any global variables with the global
keyword to access them within the function’s scope.
$counter
=
0
;
function
increment_counter
()
{
global
$counter
;
$counter
+=
1
;
}
count
();
echo
$counter
;
// 1
PHP separates operations into various scopes based on the context in which a variable is defined. For most programs, there is a single scope that spans all included or required files. A variable defined in this global scope is available everywhere regardless of which file is currently executing.
$apple
=
'honeycrisp'
;
include
'someotherscript.php'
;
User-defined functions, however, define their own scope. A variable defined outside of a user-defined function is not available within the body of the function. Likewise, any variable defined within the function is not available outside of the function. Example 3-9 illustrates the boundaries of the parent and function scope in a program.
$a
=
1
;
function
example
()
:
void
{
echo
$a
.
PHP_EOL
;
$a
=
2
;
$b
=
3
;
}
example
();
echo
$a
.
PHP_EOL
;
echo
$b
.
PHP_EOL
;
The variable $a
is initially defined in the parent scope.
Inside the function scope, $a
is not yet defined. Attempting to echo
its value will result in a warning.
Defining a variable called $a
within the function will not overwrite the value of the same-named variable outside of the function.
Defining a variable called $b
within the function makes it available within the function, but this value will not escape the scope of the function.
Echoing $a
outside of the function, even after invoking example()
, will print the initial value we’ve set as the function did not change the variable’s value.
Since $b
was defined within the function, it is undefined in the scope of the parent application.
It is possible to pass a variable into a function call by reference if the function is defined to accept a variable in such a way. However, this is a decision made by the definition of the function and not a runtime flag available to routines leveraging that function after the fact. See Example 3-4 for an example of what pass-by-reference might look like.
In order to reference variables defined outside of its scope, a function needs to declare those variables as “global” within its own scope. To reference the parent scope, we can rewrite Example 3-9 as follows:
$a
=
1
;
function
example
()
:
void
{
global
$a
,
$b
;
echo
$a
.
PHP_EOL
;
$a
=
2
;
$b
=
3
;
}
example
();
echo
$a
.
PHP_EOL
;
echo
$b
.
PHP_EOL
;
By declaring both $a
and $b
to be global variables, we are telling the function to use values from the parent scope rather than its own scope.
With a reference to the global $a
variable, we can now actually print it to output.
Likewise, any changes to $a
within the scope of the function will impact the variable in the parent scope.
Similarly, we now define $b
but, as it’s global, this definition will bubble out to the parent scope as well.
Echoing $a
will now reflect the changes made within the scope of example()
as we made the variable global.
Likewise, $b
is now defined globally and can be echoed to output as well.
There is no limit on the number of global variables PHP can support. Additionally, all globals can be listed by enumerating the special $GLOBALS
array defined by PHP. This associative array contains references to all variables defined within the global scope. This special array can be useful if you want to reference a specific variable in the global scope without declaring a variable as global as in Example 3-10.
$GLOBALS
array$var
=
'global'
;
function
example
()
:
void
{
$var
=
'local'
;
echo
'Local variable: '
.
$var
.
PHP_EOL
;
echo
'Global variable: '
.
$GLOBALS
[
'var'
]
.
PHP_EOL
;
}
example
();
// Local variable: local
// Global variable: global
As of PHP 8.1, the $GLOBALS
array is read-only. In past versions of PHP you could both read from and write to this array to update global variables. In version 8.1 and beyond, you must declare a regular variable to be global and modify the global variable that way.
Global variables are a handy way to reference state across your application, but can lead to confusion and maintainability issues if overused. Some large applications leverage global variables heavily - WordPress, a PHP-based project that powers more than 40% of the Internet3 uses global variables throughout its codebase. However, most application developers agree that global variables should be used sparingly, if at all, to help keep systems clean and easy to maintain.
PHP documentation on variable scope and the special $GLOBALS
array.
Your function needs to keep track of its change in state over time.
Use the static
keyword to define a locally-scoped variable that retains its state between function invocations.
function
increment
()
{
static
$count
=
0
;
return
$count
++
;
}
echo
increment
();
// 0
echo
increment
();
// 1
echo
increment
();
// 2
A static variable exists only within the scope of the function in which it is declared. However, unlike regular local variables, it holds onto its value every time you return to the scope of the function. In this way, a function can become stateful and keep track of certain data (like the number of times it’s been called) in between independent invocations.
In a typical function, using the =
operator will assign a value to a variable. When the static
keyword is applied, this assignment operation only happens the first time that function is called. Subsequent calls will reference the previous state of the variable and allow the program to either use or modify the stored value as well.
One of the most common use cases of static variables is to track the state of a recursive function. Example 3-11 provides an illustration of a function that recursively calls itself a fixed number of times before exiting.
function
example
()
:
void
{
static
$count
=
0
;
if
(
$count
>=
3
)
{
$count
=
0
;
return
;
}
$count
+=
1
;
echo
'Running for loop number '
.
$count
.
PHP_EOL
;
example
();
}
The static
keyword can also be used to keep track of expensive resources that might be needed by a function multiple times, but might be the kinds of things we only want a single instance of. Consider a function that logs messages to a database - we might not be able to pass a database connection into the function itself, but we want to ensure the function only opens a single database connection. Such a logging function might be implemented as in Example 3-12.
function
logger
(
string
$message
)
:
void
{
static
$dbh
=
null
;
if
(
null
===
$dbh
)
{
$dbh
=
new
PDO
(
DATABASE_DSN
,
DATABASE_USER
,
DATABASE_PASSWORD
);
}
$sql
=
'INSERT INTO messages(message) VALUES(:message)'
;
$statement
=
$dbh
->
prepare
(
$sql
);
$statement
->
execute
([
':message'
,
$message
]);
}
logger
(
'This is a test'
);
logger
(
'This is another message'
);
The first time logger()
is called, it will define the value of the static $dbh
variable. In this case it will connect to a database using the PHP Data Objects (PDO) interface. This interface is a standard object provided by PHP for accessing databases.
Every subsequent call to logger()
will leverage the initial connection opened to the database and stored in $dbh
.
Note that PHP automatically manages its memory usage and automatically clears variables from memory when they leave scope. For regular variables within a function, this means the variables are freed from memory as soon as the function completes. Static and global variables are never cleaned up until the program itself exits as they are always in scope. Take care when using the static
keyword to ensure you aren’t storing unnecessarily large pieces of data in memory. In Example 3-12 we open a connection to a database that will never be automatically closed by the function we’ve created.
While the static
keyword can be a powerful way to reuse state across function calls, it should be used with care to ensure your application doesn’t do anything unexpected. In many cases it might be better to explicitly pass variables representing state into the function. Even better would be to encapsulate the function’s state as part of an overarching object, which will be covered in Chapter 8.
PHP documentation on variable scoping, including the static
keyword.
You want to define an anonymous function and reference it as a variable within your application.
Define a closure that can be assigned to a variable and passed into another function as needed.
$greet
=
function
(
$name
)
{
echo
'Hello, '
.
$name
.
PHP_EOL
;
};
$greet
(
'World!'
);
// Hello, World!
Whereas most functions in PHP have defined names, the language supports the creation of unnamed (so-called anonymous) functions, or closures. These functions can encapsulate either simple or complex logic, but can be assigned directly to variables for reference elsewhere in the program.
Internally, anonymous functions are implemented using PHP’s native Closure
class. This class is declared as final
, which means no class can descend from it directly yet anonymous functions are all instances of this class and can be used either directly as functions or as objects.
By default, closures do not inherit any scope from the parent application and, like regular functions, define variables within their own scope. Variables from the parent scope can be passed directly into a closure by leveraging the use
directive when defining a function. Example 3-13 illustrates how variables from one scope can be passed into another dynamically.
use()
$some_value
=
42
;
$foo
=
function
()
{
echo
$some_value
;
};
$bar
=
function
()
use
(
$some_value
)
{
echo
$some_value
;
};
$foo
();
// Warning: Undefined variable
$bar
();
// 42
Anonymous functions are used in many projects to encapsulate a piece of logic for application against a collection of data. The next recipe covers exactly that use case.
Older versions of PHP used create_function()
for similar utility. Developers could create an anonymous function as a string and pass that code into create_function()
to turn it into a Closure instance. Unfortunately, this method used eval()
under the hood to evaluate the string - a practice that is considered highly unsafe. While some older projects might still use create_function()
, the function itself was deprecated in PHP 7.2 and removed from the language entirely in version 8.0.
PHP documentation on anonymous functions.
You want to define part of a function’s implementation and pass that implementation as an argument to another function.
Define a closure that implements part of the logic you need and pass that directly into another function as if it were any other variable.
$reducer
=
function
(
?
int
$carry
,
int
$item
)
:
int
{
return
$carry
+
$item
;
};
function
reduce
(
array
$array
,
callable
$callback
,
?
int
$initial
=
null
)
:
?
int
{
$acc
=
$initial
;
foreach
(
$array
as
$item
)
{
$acc
=
$callback
(
$acc
,
$item
);
}
return
$acc
;
}
$list
=
[
1
,
2
,
3
,
4
,
5
];
$sum
=
reduce
(
$list
,
$reducer
);
//15
PHP is considered by many to be a functional language as functions are first-class elements in the language and can be bound to variable names, passed as arguments, or even returned from other functions. PHP supports functions as variables through the callable type as implemented in the language. Many core functions (like usort()
, array_map()
, array_reduce()
, etc) support passing a callable parameter, which is then used internally to define the function’s overall implementation.
The reduce()
function defined in the Solution example is a user-written implementation of PHP’s native array_reduce()
function. They both have the same behavior, and the Solution could be rewritten to pass $reducer
directly into PHP’s native implementation with no change in the result:
$sum
=
array_reduce
(
$list
,
$reducer
);
// 15
Since functions can be passed around like any other variable, PHP has the ability to define partial implementations of functions. This is achieved by defining a function that, in turn, returns another function that can be used elsewhere in the program.
For example, we can define a function to set up a basic multiplier routine that multiplies any input by a fixed base amount as in Example 3-14. The main function returns a new function each time we call it, so we can create functions to double or triple arbitrary values and use them however we want.
function
multiplier
(
int
$base
)
:
callable
{
return
function
(
int
$subject
)
use
(
$base
)
:
int
{
return
$base
*
$subject
;
};
}
$double
=
multiplier
(
2
);
$triple
=
multiplier
(
3
);
$double
(
6
);
// 12
$double
(
10
);
// 20
$triple
(
3
);
// 9
$triple
(
12
);
// 36
Breaking functions apart like this is known as currying. This is the practice of changing a function with multiple input parameters and turning it into a series of functions that each take a single parameter, with most of those parameters being functions themselves. To fully illustrate how this can work in PHP, let’s look at Example 3-15 and walk through a rewrite of the multiplier()
function:
function
multiply
(
int
$x
,
int
$y
)
:
int
{
return
$x
*
$y
;
}
multiply
(
7
,
3
);
// 21
function
curried_multiply
(
int
$x
)
:
callable
{
return
function
(
int
$y
)
use
(
$x
)
:
int
{
return
$x
*
$y
;
};
}
curried_multiply
(
7
)(
3
);
// 21
The most basic form of our function takes two values, multiplies them together, and returns a final result.
When we curry the function, we want each component function to only take a single value. Our new curried_multiply()
only accepts one parameter but returns a function that leverages that parameter internally.
The internal function references the value passed by our previous function invocation automatically (with the use
directive).
The resulting function implements the same business logic as our basic form.
Calling a curried function has the appearance of calling multiple functions in series, but the result is the same.
The biggest advantage of currying like in Example 3-15 is that a partially-applied function can be passed around as a variable and used elsewhere. Similar to our multiplier()
function, we can create a doubling or tripling function by partially applying our curried multiplier as follows:
$double
=
curried_multiply
(
2
);
$triple
=
curried_multiply
(
3
);
Partially-applied, curried functions are themselves callable functions but can be passed into other functions as variables and fully invoked later.
Details on anonymous functions in Recipe 3.9.
You want to create a simple, anonymous function that references the parent scope without verbose use
declarations.
Use PHP’s short anonymous function (arrow function) syntax to define a function that inherits its parent’s scope automatically.
$outer
=
42
;
$anon
=
fn
(
$add
)
=>
$outer
+
$add
;
$anon
(
5
);
// 47
Arrow functions were introduced in PHP 7.4 as a way to write more concise anonymous functions, as in Recipe 3.9. Arrow functions automatically capture any referenced variables and import them (by value rather than by reference) into the scope of the function.
A more verbose version of the Solution example could be written as follows and achieve the same level of functionality:
$outer
=
42
;
$anon
=
function
(
$add
)
use
(
$outer
)
{
return
$outer
+
$add
;
}
$anon
(
5
);
Arrow functions always return a value - it is impossible to either implicitly or explicitly return void
. These functions follow a very specific syntax and always return the result of their expression: fn (arguments) => expression
. This structure makes arrow functions useful in a wide variety of situations.
One example is a very concise, in-line definition of a function to be applied to all elements in an array via PHP’s native array_map()
. Assume input user data is an array of strings that each represent an integer value and we want to convert the array or strings into an array of integers to enforce proper type safety. This can easily be accomplished via Example 3-16.
$input
=
[
'5'
,
'22'
,
'1093'
,
'2022'
];
$output
=
array_map
(
fn
(
$x
)
=>
intval
(
$x
),
$input
);
// $output = [5, 22, 1093, 2022]
Arrow functions only permit a single-line expression. If your logic is complicated enough to require multiple expressions, use a standard anonymous function (see Recipe 3.9) or define a named function in your code. This being said, an arrow function itself is an expression, so one arrow function can actually return another.
The ability to return an arrow function as the expression of another arrow function leads to a way to use arrow functions in in curried or partially applied functions to encourage code reuse. Assume we want to pass a function in our program that performs modulo arithmetic with a fixed modulus. We can do so by defining one arrow function to perform the calculation and wrap it in another that specifies the modulus, assigning the final, curried function to a variable we can use elsewhere as in Example 3-17.
Modulo arithmetic is used to create so-called clock functions that always return a specific set of integer values regardless of the integer input. Specifically, taking the modulus between two integers is to divide them and return the integer remainder. For example, “twelve modulo 3” is written as 12 % 3
and returns the remainder of 12/3
or 0
. Similarly, “fifteen modulo 6” is written as 15 % 6
and returns the remainder of 15/6
or 3
. The return of a modulo operation is never greater than the modulus itself (3
or 6
in the previous two examples, respectively). Modulo arithmetic is commonly used to group large collections of input values together or to power cryptographic operations, which will be discussed further in Chapter 9.
$modulo
=
fn
(
$x
)
=>
fn
(
$y
)
=>
$y
%
$x
;
$mod_2
=
$modulo
(
2
);
$mod_5
=
$modulo
(
5
);
$mod_2
(
15
);
// 1
$mod_2
(
20
);
// 0
$mod_5
(
12
);
// 2
$mod_5
(
15
);
// 0
Finally, just like regular functions, arrow functions can accept multiple arguments. Rather than passing a single variable (or implicitly referencing variables defined in the parent scope), you can just as easily define a function with multiple parameters and freely use them within the expression. A trivial equality function might use an arrow function as follows:
$eq
=
fn
(
$x
,
$y
)
=>
$x
==
$y
;
$eq
(
42
,
'42'
);
// true
Details on anonymous functions in Recipe 3.9 and the PHP manual documentation on arrow functions.
You need to define a function that does not return data to the rest of the program after it completes.
Use explicit type annotations and reference the void
return type.
const
MAIL_SENDER
=
'[email protected]'
;
const
MAIL_SUBJECT
=
'Incoming from the Wonderful Wizard'
;
function
send_email
(
string
$to
,
string
$message
)
:
void
{
$headers
=
[
'From'
=>
MAIL_SENDER
];
$success
=
(
$to
,
MAIL_SUBJECT
,
$message
,
$headers
);
if
(
!
$success
)
{
throw
new
Exception
(
'The man behind the curtain is on break.'
);
}
}
send_email
(
'[email protected]'
,
'Welcome to the Emerald City!'
);
The Solution example uses PHP’s native mail()
function to dispatch a simple message with a static subject to the specified recipient. PHP’s mail()
returns either true
(on success) or false
when there’s an error. In our example, we merely want to throw an exception when something goes wrong but otherwise want to return silently.
In many cases, you might want to return a flag - a Boolean value or a string or null
- when a function completes to provide an indication as to what has happened so the rest of your program can behave appropriately. Functions that return nothing are relatively rare, but do come up when your program is communicating with an outside party and the result of that communication doesn’t impact the rest of the program. Sending a fire-and-forget connection to a message queue or logging to the system error log are both common use cases for a function that returns void
.
The void
return type is enforced on compile time in PHP, meaning your code will trigger a fatal error if the function body returns anything at all, even if you haven’t executed anything yet. Example 3-18 illustrates both valid and invalid uses of void
.
void
return typefunction
returns_scalar
()
:
void
{
return
1
;
}
function
no_return
()
:
void
{
}
function
empty_return
()
:
void
{
return
;
}
function
returns_null
()
:
void
{
return
null
;
}
Unlike most other types in PHP, the void
type is only valid for returns. It cannot be used as a parameter type in a function definition; attempts to do so will result in a Fatal error at compile time.
The original RFC introducing the void
return type in PHP 7.1
You need to define a function that explicitly exits and ensure other parts of your application are aware it will never return.
Use explicit type annotations and reference the never
return type. For example:
function
redirect
(
string
$url
)
:
never
{
header
(
"Location:
$url
"
);
exit
();
}
Some operations in PHP are intended to be the last thing the engine does before exiting the current process. Calling header()
to define a specific response header must be done prior to printing any body to the response itself. Specifically, calling header()
to trigger a redirect is usually the last thing you want your application to do - printing any body text or processing any other operation after you’ve told the requesting client to redirect elsewhere has no meaning or value.
The never
return type signals both to PHP and to other parts of your code that the function is guaranteed to halt the program’s execution by way of either exit()
or die()
or throwing an exception.
If a function that leverages the never
return type still returns implicitly, as in Example 3-19, PHP will throw a TypeError
exception.
function
log_to_screen
(
string
$message
)
:
never
{
echo
$message
;
}
Likewise, if a never
-typed function explicitly returns a value, PHP will through a TypeError
exception. In both situations, an implicit or an explicit return, this exception is enforced at call-time (when the function is invoked) rather than when the function is defined.
The original RFC introducing the never
return type in PHP 8.1.
1 We will discuss custom classes and objects at length in Chapter 8
2 PHP CodeSniffer is a popular developer tool for automatically scanning a codebase and ensuring all code matches a specific coding standard. It can be trivially extended to enforce a strict type declaration in all files as well.
3 WordPress’ market reach was about 65% of websites using content management systems and more than 43% of all websites as of December 2021 according to W3Techs.
44.220.184.63