3. The Basics of PHP
3.1. The Script Directory Structure
The scripts for this chapter are located in two folders within the [scripts-console] folder of the course code:
These are “console” scripts: they run in a terminal using the [php script-name.php] command and output their results to that same terminal. Web scripts, which are executed by the [Apache] server running on [Laragon] and displayed by a browser, will be covered later.
3.2. Configuring PHP
PHP comes preconfigured via a text file named [php.ini]. All of these settings can be changed programmatically. The configuration of PHP greatly influences script execution. It is therefore important to understand it. The following script, [phpinfo.php], allows you to do this:
Comments:
- line 3: This line will appear at the beginning of all scripts in this course. We will explain it in the section on type conversions;
- Line 6: The function [phpinfo] displays the configuration of PHP.
Let’s open a terminal in the [scripts-console/basics] folder and run the script. Here is an excerpt:
The [phpinfo] function displays nearly 1,500 configuration lines here. We won’t comment on them because most pertain to advanced usage of PHP. Let’s highlight a few:
- [PHP Version]: the version of PHP being used—in this case, 8.5;
- [Server API]: how PHP was launched. [Command Line Interface] (CLI) refers to the interpreter running in a terminal. When PHP is executed by the [Apache] server, this line changes, and the configuration may be different;
- [display_errors] and [error_reporting]: PHP displays all errors, warnings, and deprecation notices. The value 30719 corresponds to the constant [E_ALL] as of PHP 8.4. This is the configuration required during training: a warning almost always indicates a programming error.
An important line in the full output is line [Loaded Configuration File]: it indicates which [php.ini] file was used to configure the PHP that runs your scripts. With [Laragon], this is a file like [C:\laragon\bin\php\php-8.5.x-Win32-vs17-x64\php.ini]. You can also obtain it directly using the command [php --ini]. If you want to modify the execution configuration of PHP, this is the file you need to edit ([Laragon] provides access to it via its menu, under the [PHP > php.ini] section). There are numerous comments in this file explaining the purpose of the various configurations.
3.3. A first example
3.3.1. The code
Below is a [basics-01.php] program that demonstrates the basic features of PHP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
The results:
Comments:
- line 4: instructs PHP to strictly check the declared types of the parameters and function returns. We’ll return to this in the section on type changes. For now, note that all the scripts in this course begin this way;
- line 8: In PHP, variables are not declared. They have a dynamic type that can change over time. [$name] represents the variable with identifier [name];
- line 10: To write to the screen, you can use the statement [print] or the statement [echo];
- Line 12: The notation [[...]] is used to define an array. The notation [$array[$i]] represents element No. [$i] of the array [$array], where the first element is numbered 0. The older notation [array("one", "two", 3, 4)] is still accepted, but we will not use it;
- line 14: the function [count($array)] returns the number of elements in the array [$array];
- lines 16–18: a loop. Since this loop contains only one statement, curly braces are optional. In the rest of this document, we will always use curly braces regardless of the number of statements;
- line 17: strings are enclosed in double quotes " or single quotes '. Inside double quotes, the variables [$variable] are evaluated (also called “interpolated”), but not inside single quotes;
- line 20: You can assign the elements of an array to multiple variables in a single operation. Here, [$string1="string1"] and [$string2="string2"]. This syntax, called destructuring, replaces the [list] function from older versions of PHP: [list($string1, $string2) = ...] is still accepted;
- line 22: the . (period) operator is the string concatenation operator;
- Lines 91–95: The keyword [function] defines a function. A function can be defined anywhere in the script, even after it has been used. Here, [string $string] indicates that the parameter [$string] must be a string, and [: void] indicates that the function returns no result. These type declarations are optional in PHP, but we will use them consistently: they document the code and allow PHP (and the editor) to detect errors;
- line 98: the type [mixed] (PHP 8.0) means “any type.” The parameter [$variable] can accept an integer, a string, an array, etc.;
- line 101: the predefined function [gettype($variable)] returns a string representing the type of [$variable]. The results show that this type can change over time: [$n] is first an integer and then a string;
- line 105: a function returns a result using the statement [return]. The type of this result is declared after the parentheses: [: int]. The calling code can ignore or retrieve the results of a function;
- line 112: a function can return multiple values by grouping them into an array;
- lines 53–62: The [===] operator performs a strict comparison of two elements: they must have the same type and the same value to be equal. The [] operator is less strict: two elements can be equal without being of the same type. In this case, the comparison is performed after both elements have been converted to the same type. It is quite easy to “overlook” these implicit conversions and thus end up with unexpected results, such as discovering that a condition is true when you expected it to be false. To avoid this pitfall, we will consistently use the comparison operator [=] (and its negation, [!==]);
- lines 64–68: an example of these implicit conversions. In PHP 7, the string “abc” was converted to the number 0, and the expression [0 == "abc"] was true! Since PHP 8.0, when comparing a number to a string that does not represent a number, the number is converted to a string, and the expression is false. The behavior of [] has therefore changed from one version to the next: all the more reason to prefer [=];
- Line 72: The function [is_int] returns [true] if its parameter is an integer. Similarly, there are [is_string], [is_float], [is_bool], [is_array]… The operator [&&] is the logical AND. You can also use the logical operators [||] (OR) and [!] (NOT). PHP also supports the keywords [and] and [or], but their lower priority compared to assignment can lead to unexpected results: we will not use them;
- line 80: a loop [while]. It stops as soon as the condition is false—in this case, as soon as the array is traversed or a negative or zero element is encountered;
- Line 88: The [exit] statement stops the script from executing. The functions that follow are not executed at this point, but they are recognized by the entire script.
Note that the script does not end with the [?>] closing tag for the PHP code. It is not essential. Furthermore, in a web context, it can cause problems if it is followed by spaces or line breaks, which are difficult to detect because they are not visible in a text editor. The [PSR-12] recommendation, in fact, specifies that it should be omitted in files containing only PHP. Therefore, in the remainder of this document, we will systematically omit it.
Note: In this document, we will use the keyword [print] to display text on the console. Another way to do the same thing is to use the keyword [echo]. There are subtle differences between these two keywords, but in the context of this document, there will be none. So if you prefer to use [echo], then go ahead.
3.3.2. Using VS Code
To work on the course scripts, open the [php8-oct-2026] folder within [VS Code] ([File > Open Folder] menu). The file tree appears in the explorer on the left side of the window. To run a script:
- open an integrated terminal ([Terminal > New Terminal] menu). It opens in the [php8-oct-2026] folder;
- navigate to the script’s folder: [cd scripts-console/basics];
- run the script: [php basics-01.php]. The results are displayed in the terminal. Alternatively, in the file explorer, you can right-click the [basics] folder and select [Open in Integrated Terminal].
If the command [php] is not recognized, it means that the PHP folder is not located in the Windows PATH directory: [Laragon] adds it there when you check the corresponding option in its [Tools > Path] menu. The [php -v] command should display version 8.5.
The [PHP Intelephense] extension analyzes the code as you type and flags problems with a wavy line under the incorrect code. Hovering the mouse over this line displays the message. All issues in the file are listed in the [Problems] panel ([View > Problems] menu). Among the useful warnings:
- [Undefined variable]: a variable is being used without having been initialized. This is often a typo in the variable name;
- [Undefined function]: A function that does not exist is being called; again, this is often a typo;
- [Expected type 'int'. Found 'string'.]: An actual parameter does not have the type declared by the function. This is one of the major benefits of type declarations: the error is reported even before execution;
- [Expected 2 arguments. Found 1.]: The number of actual parameters does not match the function’s definition.
Hovering over a function name displays its signature (parameters, types, return value) and its documentation, including for the predefined functions of PHP. To ensure that [Intelephense] is aware of recent language updates, check the settings for [VS Code] ([File > Preferences > Settings] menu, search for “intelephense php version”) to ensure that the specified version of PHP is 8.5. While learning the language, it is advisable to correct all reported warnings. This is a great way to learn.
[Intelephense] can also format a file (right-click, [Format Document]) according to the coding recommendations in [PSR-12]. The PSR (PHP Standard Recommendations) are guidelines for producing standard code, thereby facilitating interoperability and the maintenance of code written by different people. They are published by the [PHP-FIG] on the www.php-fig.org website. The [PSR-1] and [PSR-12] recommendations establish the rules for writing code. Here are the rules from [PSR-1]:
Element | Rule |
Class constants | Class constants MUST be written in uppercase letters with the _ character as a separator. Ex: [const VAT_RATE] |
Methods | Method names MUST be written in camelCase. Example: [public function executeBatchTaxes()] |
Properties | Property names SHOULD be written consistently within a single codebase, as StudlyCaps, camelCase, or under_score. Examples: [$AnnualSalary], [$annualSalary], [$annual_salary] |
Side Effects | A file SHOULD either declare new symbols (classes, functions, constants, etc.) without any side effects, or execute code with side effects, but SHOULD NOT do both. |
Types | Type names (classes, interfaces, etc.) MUST be written in StudlyCaps. Each type is in its own file and in a namespace with at least one level. Example: [class ScholarshipStudent] |
The side-effects rule states that a file must contain:
- either declarations of types (classes, interfaces), functions, or constants;
- or executable code without declarations of new types.
For simplicity, the examples in this chapter do not follow this rule: each script is a single file that defines its functions and uses them. Splitting the code across multiple files would be too cumbersome for basic examples. For the application presented as the central theme of this document, we will strive to follow these recommendations as closely as possible once it is written using classes.
3.4. Variable Scope
3.4.1. Example 1
The script [basics-02.php] is as follows:
The results:
Comments:
- Lines 34–35: Define two variables, [$i] and [$j], in the main program. These variables are not recognized within the functions. Thus, on line 12, the variable [$j] in the function [f1] is a local variable of the function [f1] and is different from the variable [$j] in the main program;
- line 10: a function can access a variable named [$variable] from the main program via the keyword [global]. Here, the statement refers to the global variable [$i] in the main program;
- line 28: the function [f3] has not declared [$i] as global; its variable [$i] is a local variable, unrelated to the one in the main program.
The use of [global] is not recommended: a function that modifies global variables is difficult to understand and test. It is preferable for a function to receive its data via its parameters and return its results via [return]. This is what we will do in the following example.
3.4.2. Example 2
The [basics-03.php] script is as follows:
The results:
Comments:
In some languages, a variable defined within curly braces has a scope limited to those braces: it is not accessible outside of them. The results above show that this is not the case in PHP. The variable [$i] used on line 8 inside the curly braces is the same as the one used on lines 6 and 11 outside of them.
3.5. Type Changes
Variables in PHP do not have a fixed type. The type may change at runtime depending on the value assigned to the variable. In operations involving data of various types, the PHP interpreter performs implicit conversions to bring the operands into a common type. These implicit conversions, if unknown to the developer, can be a source of errors that are difficult to detect. Below is a [basics-04.php] script demonstrating implicit and explicit conversions. Let’s start with the first part:
Comments:
- line 4: Requests strict type checking of a function’s parameters when the type is specified (and of the type of its return value). We’ll explain this instruction a bit later;
- line 19: The purpose of the function [showBool] is to demonstrate the implicit (automatic) conversion performed by the PHP interpreter when a value of any type must be converted to a Boolean (line 23);
- line 19: the parameter [$prefix] must be of type [string]. The parameter [$var] is of type [mixed]: the actual parameter can therefore be of any type. The function [showBool] returns no value ([void]);
- Line 23: In the [if ($var)] statement, the value of [$var] must be converted to a Boolean so that [if] can be evaluated. Surprisingly, the PHP interpreter returns a result for any type of value provided to it;
- lines 9–17: the value of the function parameter for [showBool] will be, in succession:
- line 9: a non-empty string: result [true] (case is not significant, TRUE=true);
- line 10: an empty string: result [false];
- line 11: a non-empty array: result [true];
- line 12: an empty array: result [false];
- line 13: the value [null] (no value): result [false];
- line 14: the real number 0: result [false];
- line 15: the integer 0: result [false];
- line 16: a real number (or integer) other than 0: result [true];
- line 17: a trap: the string "0" is converted to [false], while any other non-empty string is converted to [true].
This is shown by the resulting screen outputs:
Let’s continue with the script code:
Comments:
- line 43: the function [showNumber] accepts a parameter of type [string] and does not return a result ([void]);
- line 45: this parameter is used in an arithmetic operation, which will force the PHP interpreter to attempt to convert [$var] into a number:
- line 34: will convert the string "12" to the integer 12;
- line 35: will convert the string "45.67" to the real number 45.67;
- line 36: the string "12abcd" begins with a number. PHP uses this number, 12, but issues a warning;
- line 38: the string "abcd" contains no numbers. In PHP 7, it was converted to 0 with a simple warning. Since PHP 8.0, this operation is no longer possible: PHP throws a [TypeError]-type error, which, if not intercepted, would terminate the script;
- lines 37–41: the [try { ... } catch (TypeError $error) { ... }] structure allows this error to be caught and execution to continue. [$error->getMessage()] is the error message. Exceptions will be covered in a later chapter; for now, it is sufficient to know that the code in the [catch] block is executed when the code in the [try] block causes a [TypeError] error.
Here are the results of the execution:
The function [var_dump] displays the type and value of its parameter: int(13) is the integer 13, and float(46.67) is the floating-point number 46.67.
Note that the [declare(strict_types=1)] statement did not prevent the string "12" from being converted to a number. This is an important point: strict mode applies only to the passing of parameters to functions and the results they return. It does not affect conversions performed by operators (+, ., ==…) or by the [if]. Here’s what it does change:
- without [declare(strict_types=1)] (so-called “coercive” mode), if a function expects a [int] and is passed the string “12,” PHP converts the string to the integer 12;
- with [declare(strict_types=1)] (strict mode), the same call results in a [TypeError] error. The actual parameter must be exactly of the expected type. There is one exception: a [int] is accepted where a [float] is expected.
This statement must be the first in the file and applies only to function calls made within that file. We will use it in all scripts: it transforms silent conversions—which are sources of errors—into reported errors. We will see an example in the section on passing parameters.
Let’s continue the script code with explicit type conversions:
Comments:
- Line 58: The function [showInt] accepts a parameter of any type ([mixed]) and does not return a result. It converts the parameter [$var] to an integer (line 63). In general, to convert a variable of type [$var] to type T, write [(T) $var], where T can be: int, bool, float, string, array, or object. The notations (integer), (boolean), (double), and (binary) from older versions of PHP are obsolete: PHP 8.5 flags them as deprecated. The (real) syntax, on the other hand, was removed as of PHP 8.0;
- line 53: converts the string "12.45" to the integer 12;
- line 54: converts the floating-point number 67.8 to the integer 67 (the decimal part is lost);
- line 55: converts the Boolean [true] to the integer 1 (the Boolean [false] to the integer 0);
- line 56: converts the value [null] to the integer 0.
This is what the screen displays show:
We continue our analysis of the script with the explicit conversion of values to the [float] type:
Comments:
- line 73: the function [showFloat] takes a parameter of any type and does not return a result;
- line 78: the value of this parameter is explicitly converted to [float];
- line 68: the string "12.45" is converted to the real number 12.45;
- line 69: the integer 67 is converted to the real number 67;
- line 70: the Boolean value [true] is converted to the real number 1 (the value [false] is converted to the number 0);
- line 71: the value [null] is converted to the real number 0.
This is shown by the screen output:
We conclude the presentation of the script by examining conversions to the type [string]:
Comments:
- line 88: the function [showString] takes a parameter of any type and does not return a result;
- line 93: the parameter value is converted to type [string];
- line 83: the integer 5 will be converted to the string "5";
- line 84: the floating-point number 6.7 will be converted to the string "6.7";
- line 85: the boolean [false] will be converted to an empty string;
- line 86: the value [null] will be converted to an empty string.
Here are the screen results:
3.6. Arrays
3.6.1. Standard one-dimensional arrays
There are two notations for arrays in PHP:
Array 1 is called an array (or list), and array 2 is called a dictionary or associative array, where elements are denoted as key => value. The notation [$opposites["beautiful"]] refers to the value associated with the key "beautiful." In this case, it is the string "ugly." Array 1 is simply a variant of the dictionary and could be written as:
This gives us [$array[2]="three"]. Ultimately, there are only dictionaries. In the case of a classic array of n elements, the keys are the integers in the range [0,n-1].
The script [basics-05.php] demonstrates operations for manipulating an array of values:
The results:
Comments:
- Line 15: The array is iterated over using a [foreach]. This syntax element allows you to iterate over a dictionary—and therefore an array—using two different syntaxes: [foreach ($dictionary as $key => $value)] and [foreach ($array as $value)]. The first syntax returns a (key, value) pair on each iteration, while the second returns only the value. The PHP 7 course used the function [each] here, which was already obsolete at the time: it was removed in PHP 8.0. [foreach] is the standard way to iterate through an array;
- Line 19: The notation [$array[] = value] adds the element [value] as the last element of [$array]. Here, [$i] is equal to 6 after the first loop for [for]: 6 is added, then 7;
- line 24: an iteration using the second syntax of [foreach];
- line 29: the function [array_pop($array)] removes the last element from [$array];
- line 36: the function [array_shift($array)] removes the first element from [$array]. The remaining elements are renumbered starting from 0;
- line 43: The function [array_push($array, value)] adds [value] as the last element of [$array];
- line 50: The function [array_unshift($array, value)] adds [value] as the first element of [$array];
- line 57: the functions [array_first] and [array_last], introduced with PHP 8.5, return the first and last elements of an array (or [null] if the array is empty).
3.6.2. The dictionary or associative array
The script [basics-06.php] is as follows:
The results:
Comments:
The code above applies to a dictionary what we previously saw for a simple array. We’ll only comment on the new features:
- line 13: the function [ksort] (key sort) sorts a dictionary in natural key order;
- line 19: the function [array_keys($dictionary)] returns the list of dictionary keys as an array;
- line 24: the [array_values($dictionary)] function returns the list of dictionary values as an array;
- line 29: the function [array_is_list] (PHP 8.1) returns [true] if the array keys are 0, 1, 2… in that order, that is, if the array is a list. The function [var_export] returns the PHP representation of a value (“true” or “false”);
- line 46: the function [exists] takes an array ([array]) and a string ([string]);
- line 49: the function [isset($variable)] returns [true] if the variable [$variable] exists and does not have the value [null]; otherwise, it returns [false]. When applied to an array element, it determines whether a key exists. The function [array_key_exists($key, $array)] does the same thing, but returns [true] even if the value associated with the key is [null];
- line 36: the [??] operator (“null coalescing”) returns its left operand if it exists and is not [null], and its right operand otherwise. This is a concise way to obtain a default value for a missing key, without a warning;
- line 38: the function [unset($variable)] deletes the variable [$variable], which is an element of the dictionary here.
3.6.3. Multidimensional Arrays
The script [basics-07.php] is as follows:
Results:
Comments:
- Line 7: The elements of the array [$multi] are themselves arrays;
- line 11: Inside a string enclosed in quotation marks, simple interpolation of [$multi[$i1][$i2]] does not work: PHP would evaluate to only [$multi[$i1]]. So we remove the expression from the string and use concatenation;
- line 16: the array [$multi] becomes a dictionary (key, value) where each value is an array;
- line 20: another solution: within a string enclosed in quotes, the notation [{$expression}] allows a complex expression to be evaluated. We’ll revisit this in the next section.
3.7. Strings
3.7.1. Notation
The script [basics-08.php] is as follows:
Results:
Comments:
- Lines 6–7: A string can be enclosed in quotation marks or single quotes;
- line 8: when enclosed in quotation marks, variables are evaluated;
- line 10: within single quotes, they are not evaluated: the string is displayed as-is. Similarly, [\n] does not denote a line break here, hence the concatenation of ["\n"];
- line 13: the notation [{$...}] allows for the evaluation of expressions more complex than a simple variable within a string—in this case, elements of a dictionary. The [${variable}] notation from older versions of PHP has been deprecated since PHP 8.2: it should not be used;
- Lines 15–18: The [heredoc] syntax allows a string to span multiple lines. The string begins after [<<<END] and ends with the marker [END] (the name is arbitrary). Variables within it are evaluated as if they were enclosed in quotes. Starting with PHP 7.3, the end marker can be indented: this indentation is then removed from all lines of the string.
3.7.2. Comparison
The script [basics-09.php] is as follows:
Results:
Comments:
- Line 10: We could have used the comparator [] instead of [=]. The latter operator is more restrictive in that it requires both operands to be of the same type. Note that here, it could have been replaced by the operator [] since the type of both parameters is set to [string]* in the function signature. However, we will continue to use *[=];
- line 24: the function [str_contains($string, $substring)] (PHP 8.0) returns [true] if [$substring] is present in [$string];
- line 25: the function [str_starts_with] (PHP 8.0) checks for the start of a string;
- line 26: the function [str_ends_with] (PHP 8.0) checks for the end of a string;
- line 28: the function [strpos($string, $substring)] returns the position of [$substring] in [$string], where the first character has position 0, and [false] if the substring is absent. Before PHP 8, the presence of a substring was checked using [strpos(...) !== false], which was a misleading notation since position 0 is converted to [false]: [str_contains] is much more readable;
- line 30: the function [strlen] returns the length of a string, and [strtoupper] converts it to uppercase;
- line 32: [strlen] counts bytes, not characters. The course files are encoded in UTF-8, where an accented character takes up two bytes: “café” has 4 characters but 5 bytes. The functions of the [mbstring] extension (mb_strlen, mb_substr, mb_strtoupper…) operate on characters.
3.7.3. Relationships Between Strings and Arrays
The script [basics-10.php] is as follows:
Results:
Comments:
- Line 7: The [explode($separator, $string)] function retrieves the fields from [$string] separated by [$separator]. Thus, [explode(":", $string)] retrieves the elements of [$string]—which are separated by the string ":"—in array form;
- Line 14: The function [implode($separator, $array)] performs the inverse operation of the function [explode]. It returns a string consisting of the elements of [$array] separated by [$separator];
- line 17: The operator [.=] appends one string to the end of another. The results show that an empty field at the end of a string results in an empty element in the array returned by [explode].
3.7.4. Regular Expressions
The script [basics-11.php] is as follows:
Results:
Comments:
Here, we use regular expressions to extract the various fields from a string. Regular expressions allow us to overcome the limitations of the [explode] function. The principle is to compare a string to another string called a pattern using the [preg_match] function: [$matches = preg_match($pattern, $string, $fields)] (line 48). The function [preg_match] returns the integer 1 if the pattern can be found in the string, and 0 otherwise. If so, [$fields[0]] represents the substring corresponding to the pattern. Furthermore, if the pattern contains subpatterns enclosed in parentheses, [$fields[1]] is the portion of [$string] corresponding to the first subpattern, [$fields[2]] is the portion of [$string] corresponding to the second subpattern, and so on. Let’s return to the script:
- line 10: The pattern matches a sequence of one or more (+) digits (\d) appearing anywhere in a string. Additionally, the pattern defines a subpattern enclosed in parentheses;
- line 12: the pattern /(\d+)/ is matched against the string "xyz1234abcd." We see that the substring 1234 matches the pattern. Therefore, [$fields[0]] equals "1234." Furthermore, the pattern has a subpattern enclosed in parentheses. We will get [$fields[1]="1234"];
- line 13: the pattern /(\d+)/ is compared to the string "12 34". We see that the substrings 12 and 34 match the pattern. The comparison stops at the first substring that matches the pattern. Therefore, we will have [$fields[0]=12] and [$fields[1]=12];
- Line 14: The pattern /(\d+)/ is compared to the string "abcd". No match is found;
- line 51: [preg_match] returns an integer (and [false] if there is an error in the pattern): it is strictly compared to 1.
Let’s explain the patterns used in the rest of the code:
matches the start of the string (^), followed by zero or more (*) arbitrary characters (.), followed by one or more (+) digits, followed by zero or more (*) arbitrary characters (.). The pattern (.*) matches zero or more any characters. Such a pattern will match any string. Thus, in the pattern /^(.*)(\d+)(.*)$/, the first subpattern (.*) would match all digits except the last one, which is the only one left for the subpattern (\d+). The pattern (.*?)(\d+) matches 0 or more any characters up to the next subpattern (?), which in this case is \d+. Therefore, the digits are no longer captured by the (.*) pattern. The pattern above thus matches [start of the string (^), a sequence of any characters (.*?), a sequence of one or more digits (\d+), a sequence of any characters (.*?), the end of the string ($)].
matches [start of the string (^), a sequence of 0 or more spaces (\s*), 2 digits (\d\d), the character / (\/), 2 digits, /, 2 digits, a sequence of 0 or more spaces (\s*), the end of the string ($)].
matches [start of the string (^), 0 or more spaces (\s*), a + or - sign ([+-]) present 0 or 1 time (?), a sequence of 0 or more spaces (\s*), 1 or more digits followed by a decimal point followed by zero or more digits (\d+\.\d*) or (|) a decimal point (\.) followed by one or more digits (\d+) or (|) one or more digits (\d+), a sequence of 0 or more spaces (\s*)]. The PHP 7 course wrote it as [+|-]: within square brackets, the | character has no special meaning, and this pattern also accepted the | symbol before a number. [+-] is the correct notation.
Note: The term [space] in regular expressions refers to a set of characters: whitespace, newline \n, tab \t, carriage return \r, form feed \f…
3.8. Functions
3.8.1. Parameter Passing Method
The [basics-12.php] script is as follows:
Results:
Comments:
The code above demonstrates the two ways to pass parameters to a function. Consider the following example:
- Line 1: Defines the formal parameters [$a] and [$b] for the function [f]. This function processes these two formal parameters;
- Line 9: Calls the function [f] with two actual parameters, [$i] and [$j]. The relationships between the formal parameters ([$a], [$b]) and the actual parameters ([$i], [$j]) are defined by lines 1 and 9:
- [&$a]: The & symbol indicates that the formal parameter [$a] will take on the value of the address of the actual parameter [$i]. In other words, [$a] and [$i] are two references to the same memory location. Manipulating the formal parameter [$a] is equivalent to manipulating the actual parameter [$i]. This is demonstrated by the code execution. This passing mode is suitable for output parameters. This passing mode is called “pass-by-reference”;
- [$b]: The formal parameter [$b] will take on the value of the actual parameter [$j]. This is passing by value. The formal and actual parameters are two different variables. Manipulating the formal parameter [$b] has no effect on the actual parameter [$j]. This is demonstrated by the code execution. This passing mode is suitable for input parameters.
Consider the function [swap], which accepts two formal parameters: [$a] and [$b]. The function swaps the values of these two parameters. Thus, when [swap($i, $j)] is called, the calling code expects the values of the two actual parameters to be swapped. These are therefore output parameters (they are modified). We will therefore write:
Note: One might think that passing by reference is also suitable for large data sets, arrays, and dictionaries, to avoid copying them. This is unnecessary: PHP actually copies an array passed by value only if the function modifies it. Therefore, passing by reference should be reserved for output parameters.
The following script, [basics-13.php], shows additional examples:
Comments:
- line 4: Unlike all the other scripts in this course, this script does not include the [declare(strict_types=1)] statement: PHP operates in “coercive” mode;
- Lines 6–13: The [f] function discussed in the previous paragraph, but the parameters are not typed;
- lines 15–22: the function [g] does the same thing as the function [f], but the types of the expected parameters are specified. It expects two parameters of type [int]. We want to see what happens when the actual parameter passed to the function does not have the type expected by the function;
- lines 25–26: [$i] and [$j] are two integers;
- line 28: call to the function [f] with parameters of the expected type;
- line 31: call to the function [g] with parameters of the expected type;
- lines 34–35: the variables [$a] and [$b] are of type [float];
- line 36: call to the function [f] with parameters of type [float];
- Lines 39–41: Call to the function [g] with parameters of type [float], which is not the expected type.
Results:
- The function [f], whose parameters are untyped, accepted both parameters of type [float] and processed them as follows: [$i] is 6.0 (displayed as 6), and [$j] is 7.2;
- the function [g] accepted both parameters of type [float] but converted them to type [int]. This can be seen in the variable [$a], passed by reference, which has become the integer 6.
The floating-point numbers 5.0 and 6.0 have no decimal part: converting them to integers does not result in any loss of information. What would happen with the floating-point number 5.3? In PHP 7, it was silently converted to the integer 5. Since PHP 8.1, this conversion—which loses information—has been marked as deprecated: it will become an error in a future version of PHP.
Now let’s do the same thing in strict mode. The script [basics-13b.php] is identical to the script [basics-13.php] except that it begins with the statement:
This instruction specifies that the types of the formal parameters must be respected. If they are not, an error is reported. The results of the execution are then:
The PHP interpreter raised a [TypeError] error to indicate that the first parameter passed to the [g] function was of the wrong type: g(): Argument #1 ($i) must be of type int; float was provided. The message indicates the line number of the faulty call (42) and that of the function definition (16). Since this error is not caught (there is no [try / catch]), the script terminates. It is recommended to be strict about parameter types whenever possible to detect function call errors: this is why all scripts in the course begin with [declare(strict_types=1)].
3.8.2. Function parameters in PHP 8
PHP 8 has greatly enhanced function declaration and calling. The script [basics-14.php] demonstrates these new features:
Results:
Comments:
- line 7: The parameters [$phrase] and [$uppercase] have default values. They can be omitted when calling the function (line 13), in which case they take their default values;
- line 10: the ternary operator [condition ? value1 : value2] returns [value1] if the condition is true, and [value2] otherwise;
- line 16: named arguments (PHP 8.0). The actual parameter is preceded by the name of the formal parameter followed by a colon (:). This allows you to assign a value to the third parameter without specifying the second, which retains its default value;
- line 17: With named arguments, the order of the parameters no longer matters. This improves code readability: [uppercase: true] is more descriptive than a standalone [true];
- line 19: The predefined functions of PHP also accept named arguments. Their names are those listed in the php.net documentation;
- line 22: The union type [int|float] (PHP 8.0) indicates that the parameter can be a [int] or a [float]. The same applies to the result;
- line 31: the type [?string] is a nullable type: the parameter is a string or the value [null]. This is a shorthand notation for [string|null]. Note: In PHP 7, people often wrote [string $string = null] to achieve the same effect. This notation (“implicit nullable”) has been deprecated since PHP 8.4: you must write [?string $string = null];
- Line 34: the [??] operator, which we’ve already encountered: if [$string] equals [null], the empty string is used. In fact, since PHP 8.1, passing [null] to a non-nullable parameter of a predefined function—such as [strlen(null)] here—is deprecated;
- line 40: the parameter of type [mixed] accepts any value, including [null];
- Lines 43–49: The expression [match] (PHP 8.0). It compares the value in parentheses to the values to the left of the => arrows and returns the value to the right of the first arrow for which the comparison is true. With [match (true)], conditions are written on the left: the first true condition takes precedence. Multiple values can be separated by commas (line 44). [default] covers all other cases;
- line 48: the function [get_debug_type] (PHP 8.0) returns the type name of a value as written in the code: int, float, string, null, or the class name of an object. [gettype], on the other hand, returned historical names such as
integerordouble; - lines 58–62: [match] applied to a value. Here, the variable [$children] is compared to the values 0, then 1, and then 2; otherwise, the case for [default] applies. Unlike the [switch] instruction—which [match] effectively replaces—the comparison is performed using [===], [match] is an expression that returns a value, and there is no [break] instruction to worry about. If no branch is suitable and there is no
*[default]*,*[match]throws a*[UnhandledMatchError]error; - line 66: [int|float ...$numbers] is a variadic parameter: the function accepts any number of actual parameters (even none), which PHP collects in the array [$numbers]. A variadic parameter is always the last one;
- line 76: the [...] (spread) operator placed before an array in a call “spreads” the array: its elements become the actual parameters of the function. [sum(...$values)] is equivalent to [sum(10, 20, 30.5)];
- line 78: the same operator allows the elements of one array to be inserted into another array.
3.8.3. Results returned by a function
The script [basics-15.php] is as follows:
Results:
Comments:
- The previous program shows that a PHP function can return a set of results rather than a single one, in the form of an array or an object. The concept of an object is explained in a later chapter;
- lines 43–47: The function [f1] returns multiple values in the form of an array ([array]);
- line 8: the returned array is unpacked into three variables;
- line 15: destructuring also works with an associative array: for each variable, the key of the value to be retrieved is specified. The function [minMax] returns a dictionary (line 53). The predefined functions [min] and [max] return the smallest and largest elements of an array;
- lines 57–62: The function [f2] returns multiple values in the form of an object ([object]). It does this by converting an associative array into an object using the operator [(object)]: each key in the array becomes a property of the object;
- Lines 65–74: The function [f3] does the same thing as the function [f2], but it explicitly creates an object of type [stdClass] (line 68) and then adds properties to it. The attribute A of an object O is denoted by [$O->A];
- line 23: the function [var_dump] provides access to the object’s type and its content. In the results, we see that the object is of type [stdClass], that its property [res1] is of type integer and has the value 11, etc. The number #1 or #2 following the type is an internal object number;
- line 77: the result type [?object] indicates that the function [f4] returns an object or [null];
- Lines 35–37: The null-safe operator [?->] (PHP 8.0). If [$res] equals [null], the expression [$res?->res1] equals [null] instead of causing an error; the operator [??] then replaces [null] with “none.” Here, the operator [??] would suffice on its own, since it does not signal the absence of the property. The operator [?->] will prove particularly useful with objects and their methods: [$client?->getAddress()?->getCity()].
Note: The PHP 7 course featured a function that wrote [$res->res1 = ...] without having created the object [$res]. PHP 7 would then create a [stdClass] object by default with a simple warning. Starting with PHP 8.0, this is a fatal error (Attempt to assign property on null): an object must always be created before it is used.
3.8.4. Functions are also values
In PHP, a function can be stored in a variable, passed as a parameter to another function, or returned as a result. The [basics-17.php] script demonstrates the different ways to write and use these functions:
Results:
Comments:
- lines 9–11: An anonymous (unnamed) function is assigned to the variable [$square]. Note the semicolon at the end: this is an assignment;
- line 12: the function is called via the variable;
- line 16: the predefined function [array_map] applies the function passed as the first parameter to each element of the array passed as the second parameter, and returns the array of results;
- lines 21–23: the predefined function [array_filter] returns the array elements for which the function passed as the second parameter returns [true]. An anonymous function is not aware of the variables in the surrounding code: the keyword [use ($threshold)] gives it access to the variable [$threshold]. Note that [array_filter] retains the keys of the selected elements; this has no effect here because [implode] uses only the values;
- Line 27: an arrow function (PHP 7.4): [fn(parameters) => expression]. It contains only one expression, whose value is the result of the function. It automatically has read access to the variables in the surrounding code: no need for [use]. This is the most concise way to write small functions passed as parameters;
- Line 31: The function [usort] sorts an array using a comparison function that takes two elements, [$a] and [$b], and returns a negative number, zero, or a positive number, depending on whether [$a] should be placed before, at the same rank as, or after [$b]. The operator [<=>] (“spaceship”) returns exactly -1, 0, or 1. By writing [$b <=> $a] instead of [$a <=> $b], we obtain a descending sort. Note that [usort] modifies the array passed to it (pass-by-reference): we have therefore sorted a copy ([$sorted]) of the array [$numbers];
- Line 35: The functions [array_find], [array_any], and [array_all] were introduced with PHP 8.4. [array_find] returns the first element for which the function returns [true] (or [null] if there are none), [array_any] returns [true] if at least one element satisfies the condition, and [array_all] if all of them do;
- line 40: first-class callable syntax (PHP 8.1): [functionName(...)], with three dots between the parentheses, does not call the function but returns the function itself as a [Closure] object. It can then be used as an anonymous function;
- Line 43: The predefined function [ucfirst] (first letter capitalized) is passed to [array_map];
- Lines 46–49: the pipe operator [|>] (PHP 8.5). The expression [value |> function] calls the function with the value as its only parameter. The calls are chained from left to right, in the order in which they are read: leading and trailing spaces are removed with [trim], the text is converted to lowercase with [strtolower], and then spaces are replaced with hyphens. An arrow function used with [|>] must be enclosed in parentheses;
- line 52: the same thing without the pipe operator. Functions are nested and read from the inside out, in the reverse order of execution.
3.9. Text files
The script [basics-16.php] is as follows:
The [info.txt] file created by the script:
The results:
Comments:
- Line 12: [fopen(file_name, "w")] opens the file [file_name] in write mode (w=write). If the file does not exist, it is created. If it exists, it is cleared. The file is created in the current directory, the one from which the script was run. If creation fails, [fopen] issues a warning and returns the value [false];
- line 13: the result of [fopen] is tested by strictly comparing it to [false];
- line 19: [fputs($file, $string)] writes [$string] to the file [$file]. Here, the string ends with the end-of-line marker [\n];
- line 22: [fclose($file)] closes the file [$file];
- line 27: [fopen(file_name, "r")] opens the file [file_name] in read mode (r=read). If opening the file fails (for example, if the file does not exist), [fopen] returns the value [false];
- line 34: the dictionary is initialized as empty before being populated. PHP would create the array on the first assignment of [$dict[$login] = ...], but if the file were empty, the variable would not exist and its use would trigger a warning;
- line 38: [fgets($file, 1000)] reads the next line of the file, limited to 1,000 characters. The [while (($line = fgets($file, 1000)) !== false) {...}] operation consists of two successive operations: 1) [$line = fgets(...)] and 2) [while ($line !== false)]. After the last character of the file has been read, the function [fgets] returns the value [false], and the loop [while] stops. Here, the function [fgets] attempts to read up to 1,000 characters but stops as soon as it encounters a line-end character. Since all lines here have fewer than 1,000 characters, [fgets] reads one line of text, including the line-end character. The PHP 7 course wrote [while ($line = fgets($file, 1000))]: the loop would have stopped prematurely on a final line reduced to "0," without a line-end character, because the string "0" is converted to [false]. The strict comparison [!== false] avoids this pitfall;
- line 40: the function [cutNewLineChar] removes any line-end characters;
- line 44: the function [array_shift($array)] removes the first element of [$array] and returns it as the result;
- line 46: here, we ignore the result returned by [array_shift];
- Line 58: Another way to read a text file. The [file] function returns an array containing all the lines in the file. With the [FILE_IGNORE_NEW_LINES] option, line-end characters are removed. This is simpler than the [fgets] loop, but the entire file is loaded into memory: for a very large file, reading line by line is still preferable;
- line 71: the function [is_array($variable)] returns [true] if [$variable] is an array, and [false] otherwise;
- line 84: The end-of-line character is the string "\r\n" on Windows systems and "\n" on Unix systems. The function [rtrim($string, $characters)] removes all characters present in [$characters]—in this case, \r and \n—from the end of [$string]. Without a second parameter, [rtrim] removes all “spaces” (whitespace, tabs, line breaks, etc.); the function [trim] does the same at both ends of the string.
3.10. Encoding/Decoding JSON
The [scripts-console/json] folder contains the scripts for this section:
The JSON encoding/decoding (JavaScript Object Notation) is something we will use extensively in the exercise that serves as the central theme of this document. The [json-01.php, json-02.php, json-03.php] scripts explain what you need to know for the rest of the document.
The [json-01.php] script is as follows:
Results:
Comments:
- line 8: the [json_encode] function converts its parameter into the JSON string. In the resulting string, the accented character é has been replaced by its Unicode code, which begins with \u;
- line 12: We repeat the same process, this time specifying that the Unicode characters should be preserved as-is. The resulting JSON string is much more readable;
- Lines 16–20: We perform the reverse operation. We convert the JSON string into an associative array: this is the role of the second parameter, [true], of [json_decode]. The results show that we have indeed obtained an associative array;
- Lines 23–27: Without the parameter [true], [json_decode] converts the JSON string into an object of type [stdClass]. Attribute A of an object O is denoted as [$O->A].
Multi-level arrays can be encoded as JSON, as shown in the following script [json-02.php]:
Results:
Comments:
In the JSON string:
- non-associative arrays (lists) are enclosed in square brackets [] ;
- associative arrays are enclosed in curly braces {} .
Line 19: The options for [json_encode] are combined using the | operator (binary OR). The [JSON_PRETTY_PRINT] option produces a JSON string that is indented across multiple lines, making it more readable.
The script [json-03.php] demonstrates how to process the following JSON file [family.json]:
The [json-03.php] script is as follows:
Comments:
- line 6: The [file_get_contents] function reads the contents of the file named [family.json] and stores them in the variable [$json]. It returns [false] if the read operation fails;
- Line 8: The function [json_validate] (PHP 8.3) returns [true] if the string passed as a parameter is a valid JSON. Previously, it was necessary to decode the string and then call the function [json_last_error];
- lines 13–19: the string is then decoded into an object and then into an associative array.
Results:
- The first part of the results shows the object resulting from the decoding of the JSON file [family.json]: The JSON objects have become [stdClass] objects, and the JSON list of children has become a PHP array;
- the second part shows the associative array resulting from the same decoding: all JSON objects have become associative arrays.