Skip to content

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:

php8-oct-2026
└── scripts-console
    ├── basics
    │   ├── phpinfo.php
    │   ├── basics-01.php
    │   ├── basics-02.php
    │   ├── ...
    │   ├── basics-17.php
    │   └── info.txt
    └── json
        ├── family.json
        ├── json-01.php
        ├── json-02.php
        └── json-03.php

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:

1
2
3
4
5
6
<?php

declare(strict_types=1);

// displays the PHP configuration
phpinfo();

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:

> php phpinfo.php
phpinfo()
PHP Version => 8.5.11
…
Server API => Command Line Interface
…
PHP API => 20250925
PHP Extension => 20250925
Zend Extension => 420250925
PHP Integer Size => 64 bits
Debug Build => no
Zend Memory Manager => enabled
IPv6 Support => enabled
…
display_errors => STDOUT => STDOUT
error_reporting => 30719 => 30719
memory_limit => 512M => 512M
max_execution_time => 0 => 0
…

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:

<?php

// strict checking of the types of function parameters and results
declare(strict_types=1);

// this is a comment
// variable used without having been declared
$name = "dupont";
// a screen display
print "name=$name\n";
// an array with elements of different types
$array = ["one", "two", 3, 4];
// its number of elements
$n = count($array);
// a loop
for ($i = 0; $i < $n; $i++) {
    print "array[$i]=$array[$i]\n";
}
// initialization of 2 variables with the contents of an array
[$string1, $string2] = ["string1", "string2"];
// concatenation of the 2 strings
$string3 = $string1 . $string2;
// display result
print "[$string1,$string2,$string3]\n";
// using a function
display($string1);
// the type of a variable can be known
displayType("n", $n);
displayType("string1", $string1);
displayType("array", $array);
// the type of a variable can change during execution
$n = "has changed";
displayType("n", $n);
// a function can return a result
$res1 = f1(4);
print "res1=$res1\n";
// a function can return an array of values
[$res1, $res2, $res3] = f2();
print "(res1,res2,res3)=[$res1,$res2,$res3]\n";
// we could have retrieved these values in an array
$t = f2();
for ($i = 0; $i < count($t); $i++) {
    print "t[$i]=$t[$i]\n";
}
// some tests
for ($i = 0; $i < count($t); $i++) {
    // only displays the strings
    if (gettype($t[$i]) === "string") {
        print "t[$i]=$t[$i]\n";
    }
}
// comparison operators == and ===
if ("2" == 2) {
    print "with the == operator, the string 2 is equal to the integer 2\n";
} else {
    print "with the == operator, the string 2 is not equal to the integer 2\n";
}
if ("2" === 2) {
    print "with the === operator, the string 2 is equal to the integer 2\n";
} else {
    print "with the === operator, the string 2 is not equal to the integer 2\n";
}
// a comparison whose result changed with PHP 8
if (0 == "abc") {
    print "with the == operator, the integer 0 is equal to the string abc\n";
} else {
    print "with the == operator, the integer 0 is not equal to the string abc\n";
}
// other tests
for ($i = 0; $i < count($t); $i++) {
    // only displays the integers > 10
    if (is_int($t[$i]) && $t[$i] > 10) {
        print "t[$i]=$t[$i]\n";
    }
}
// a while loop
$t = [8, 5, 0, -2, 3, 4];
$i = 0;
$sum = 0;
while ($i < count($t) && $t[$i] > 0) {
    print "t[$i]=$t[$i]\n";
    $sum += $t[$i];   // $sum = $sum + $t[$i]
    $i++;             // $i = $i + 1
}
print "sum=$sum\n";

// end of program
exit;

//----------------------------------
function display(string $string): void
{
    // displays $string
    print "string=$string\n";
}

//----------------------------------
function displayType(string $name, mixed $variable): void
{
    // displays the type of $variable
    print "type[variable $" . $name . "]=" . gettype($variable) . "\n";
}

//----------------------------------
function f1(int $param): int
{
    // adds 10 to $param
    return $param + 10;
}

//----------------------------------
function f2(): array
{
    // returns 3 values
    return ["one", 0, 100];
}

The results:

> php basics-01.php
name=dupont
array[0]=one
array[1]=two
array[2]=3
array[3]=4
[string1,string2,string1string2]
string=string1
type[variable $n]=integer
type[variable $string1]=string
type[variable $array]=array
type[variable $n]=string
res1=14
(res1,res2,res3)=[one,0,100]
t[0]=one
t[1]=0
t[2]=100
t[0]=one
with the == operator, the string 2 is equal to the integer 2
with the === operator, the string 2 is not equal to the integer 2
with the == operator, the integer 0 is not equal to the string abc
t[2]=100
t[0]=8
t[1]=5
sum=13

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:

<?php

declare(strict_types=1);

// scope of variables

function f1(): void
{
    // we use the global variable $i
    global $i;
    $i++;
    $j = 10;
    print "f1[i,j]=[$i,$j]\n";
}

function f2(): void
{
    // we use the global variable $i
    global $i;
    $i++;
    $j = 20;
    print "f2[i,j]=[$i,$j]\n";
}

function f3(): void
{
    // we use a local variable $i
    $i = 4;
    $j = 30;
    print "f3[i,j]=[$i,$j]\n";
}

// tests
$i = 0;
$j = 0;  // these two variables are known to a function f
// only if it explicitly declares with the global statement
// that it wants to use them
f1();
f2();
f3();
print "test[i,j]=[$i,$j]\n";

The results:

1
2
3
4
5
> php basics-02.php
f1[i,j]=[1,10]
f2[i,j]=[2,20]
f3[i,j]=[4,30]
test[i,j]=[2,0]

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:

<?php

declare(strict_types=1);

// the scope of a variable is global to code blocks
$i = 0;
{
    $i = 4;
    $i++;
}
print "i=$i\n";

The results:

> php basics-03.php
i=5

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:

<?php

// strict types when passing parameters
declare(strict_types=1);

// implicit type changes
// type --> bool
print "Conversion to a boolean------------------------------\n";
showBool("abcd", "abcd");
showBool("", "");
showBool("[1, 2, 3]", [1, 2, 3]);
showBool("[]", []);
showBool("null", null);
showBool("0.0", 0.0);
showBool("0", 0);
showBool("4.6", 4.6);
showBool("\"0\"", "0");

function showBool(string $prefix, mixed $var): void
{
    print "(bool) $prefix : ";
    // the conversion of $var to a boolean is done automatically in the following test
    if ($var) {
        print "true";
    } else {
        print "false";
    }
    print "\n";
}

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:

Conversion to a boolean------------------------------
(bool) abcd : true
(bool)  : false
(bool) [1, 2, 3] : true
(bool) [] : false
(bool) null : false
(bool) 0.0 : false
(bool) 0 : false
(bool) 4.6 : true
(bool) "0" : false

Let’s continue with the script code:

// implicit type changes from string to a numeric type
// string --> number
print "Conversion string to number------------------------------\n";
showNumber("12");
showNumber("45.67");
showNumber("12abcd");
try {
    showNumber("abcd");
} catch (TypeError $error) {
    print "(abcd): error [" . $error->getMessage() . "]\n";
}

function showNumber(string $var): void
{
    $number = $var + 1;
    var_dump($number);
    print "($var): $number\n";
}

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:

Conversion string to number------------------------------
int(13)
(12): 13
float(46.67)
(45.67): 46.67

Warning: A non-numeric value encountered in .../php8-oct-2026/scripts-console/basics/basics-04.php on line 45
int(13)
(12abcd): 13
(abcd): error [Unsupported operand types: string + int]

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:

// explicit type changes
// to int
print "Explicit conversion to int------------------------------\n";
showInt("12.45");
showInt(67.8);
showInt(true);
showInt(null);

function showInt(mixed $var): void
{
    print "parameter: ";
    var_dump($var);
    print "result of the conversion: ";
    var_dump((int) $var);
}

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:

1
2
3
4
5
6
7
8
9
Explicit conversion to int------------------------------
parameter: string(5) "12.45"
result of the conversion: int(12)
parameter: float(67.8)
result of the conversion: int(67)
parameter: bool(true)
result of the conversion: int(1)
parameter: NULL
result of the conversion: int(0)

We continue our analysis of the script with the explicit conversion of values to the [float] type:

// to float
print "Explicit conversion to float------------------------------\n";
showFloat("12.45");
showFloat(67);
showFloat(true);
showFloat(null);

function showFloat(mixed $var): void
{
    print "parameter: ";
    var_dump($var);
    print "result of the conversion: ";
    var_dump((float) $var);
}

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:

1
2
3
4
5
6
7
8
9
Explicit conversion to float------------------------------
parameter: string(5) "12.45"
result of the conversion: float(12.45)
parameter: int(67)
result of the conversion: float(67)
parameter: bool(true)
result of the conversion: float(1)
parameter: NULL
result of the conversion: float(0)

We conclude the presentation of the script by examining conversions to the type [string]:

// to string
print "Explicit conversion to string------------------------------\n";
showString(5);
showString(6.7);
showString(false);
showString(null);

function showString(mixed $var): void
{
    print "parameter: ";
    var_dump($var);
    print "result of the conversion: ";
    var_dump((string) $var);
}

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:

1
2
3
4
5
6
7
8
9
Explicit conversion to string------------------------------
parameter: int(5)
result of the conversion: string(1) "5"
parameter: float(6.7)
result of the conversion: string(3) "6.7"
parameter: bool(false)
result of the conversion: string(0) ""
parameter: NULL
result of the conversion: string(0) ""

3.6. Arrays

3.6.1. Standard one-dimensional arrays

There are two notations for arrays in PHP:

$array = ["one", 2, "three"];
$opposites = ["small" => "big", "beautiful" => "ugly", "expensive" => "cheap"];

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:

$array = [0 => "one", 1 => 2, 2 => "three"];

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:

<?php

declare(strict_types=1);

// classic arrays
// initialization
$tab1 = [0, 1, 2, 3, 4, 5];
// traversal - 1
print "tab1 has " . count($tab1) . " elements\n";
for ($i = 0; $i < count($tab1); $i++) {
    print "tab1[$i]=$tab1[$i]\n";
}
// traversal - 2
print "tab1 has " . count($tab1) . " elements\n";
foreach ($tab1 as $key => $value) {
    print "tab1[$key]=$value\n";
}
// adding elements
$tab1[] = $i++;
$tab1[] = $i++;
// traversal - 3
print "tab1 has " . count($tab1) . " elements\n";
$i = 0;
foreach ($tab1 as $element) {
    print "tab1[$i]=$element\n";
    $i++;
}
// removing the last element
array_pop($tab1);
// traversal - 4
print "tab1 has " . count($tab1) . " elements\n";
for ($i = 0; $i < count($tab1); $i++) {
    print "tab1[$i]=$tab1[$i]\n";
}
// removing the first element
array_shift($tab1);
// traversal - 5
print "tab1 has " . count($tab1) . " elements\n";
for ($i = 0; $i < count($tab1); $i++) {
    print "tab1[$i]=$tab1[$i]\n";
}
// adding at the end of the array
array_push($tab1, -2);
// traversal - 6
print "tab1 has " . count($tab1) . " elements\n";
for ($i = 0; $i < count($tab1); $i++) {
    print "tab1[$i]=$tab1[$i]\n";
}
// adding at the beginning of the array
array_unshift($tab1, -1);
// traversal - 7
print "tab1 has " . count($tab1) . " elements\n";
for ($i = 0; $i < count($tab1); $i++) {
    print "tab1[$i]=$tab1[$i]\n";
}
// first and last elements (PHP 8.5)
print "first element: " . array_first($tab1) . ", last element: " . array_last($tab1) . "\n";

The results:

> php basics-05.php
tab1 has 6 elements
tab1[0]=0
tab1[1]=1
tab1[2]=2
tab1[3]=3
tab1[4]=4
tab1[5]=5
tab1 has 6 elements
tab1[0]=0
tab1[1]=1
tab1[2]=2
tab1[3]=3
tab1[4]=4
tab1[5]=5
tab1 has 8 elements
tab1[0]=0
tab1[1]=1
tab1[2]=2
tab1[3]=3
tab1[4]=4
tab1[5]=5
tab1[6]=6
tab1[7]=7
tab1 has 7 elements
tab1[0]=0
tab1[1]=1
tab1[2]=2
tab1[3]=3
tab1[4]=4
tab1[5]=5
tab1[6]=6
tab1 has 6 elements
tab1[0]=1
tab1[1]=2
tab1[2]=3
tab1[3]=4
tab1[4]=5
tab1[5]=6
tab1 has 7 elements
tab1[0]=1
tab1[1]=2
tab1[2]=3
tab1[3]=4
tab1[4]=5
tab1[5]=6
tab1[6]=-2
tab1 has 8 elements
tab1[0]=-1
tab1[1]=1
tab1[2]=2
tab1[3]=3
tab1[4]=4
tab1[5]=5
tab1[6]=6
tab1[7]=-2
first element: -1, last element: -2

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:

<?php

declare(strict_types=1);

// dictionaries
$spouses = ["Pierre" => "Gisèle", "Paul" => "Virginie", "Jacques" => "Lucette", "Jean" => ""];
// traversal - 1
print "Number of elements in the dictionary: " . count($spouses) . "\n";
foreach ($spouses as $key => $value) {
    print "spouses[$key]=$value\n";
}
// sorting the dictionary by key
ksort($spouses);
// traversal - 2
foreach ($spouses as $key => $value) {
    print "spouses[$key]=$value\n";
}
// list of the dictionary keys
$keys = array_keys($spouses);
for ($i = 0; $i < count($keys); $i++) {
    print "keys[$i]=$keys[$i]\n";
}
// list of the dictionary values
$values = array_values($spouses);
for ($i = 0; $i < count($values); $i++) {
    print "values[$i]=$values[$i]\n";
}
// is a classic array a list? and a dictionary?
print "values is a list: " . var_export(array_is_list($values), true) . "\n";
print "spouses is a list: " . var_export(array_is_list($spouses), true) . "\n";
// searching for a key
exists($spouses, "Jacques");
exists($spouses, "Lucette");
exists($spouses, "Jean");
// default value if the key does not exist: operator ??
print "spouse of Lucette: " . ($spouses["Lucette"] ?? "unknown") . "\n";
// removing a key-value pair
unset($spouses["Jean"]);
print "Number of elements in the dictionary: " . count($spouses) . "\n";
foreach ($spouses as $key => $value) {
    print "spouses[$key]=$value\n";
}
// end
exit;

function exists(array $spouses, string $husband): void
{
    // checks whether the key $husband exists in the dictionary $spouses
    if (isset($spouses[$husband])) {
        print "The key [$husband] exists, associated with the value [$spouses[$husband]]\n";
    } else {
        print "The key [$husband] does not exist\n";
    }
}

The results:

> php basics-06.php
Number of elements in the dictionary: 4
spouses[Pierre]=Gisèle
spouses[Paul]=Virginie
spouses[Jacques]=Lucette
spouses[Jean]=
spouses[Jacques]=Lucette
spouses[Jean]=
spouses[Paul]=Virginie
spouses[Pierre]=Gisèle
keys[0]=Jacques
keys[1]=Jean
keys[2]=Paul
keys[3]=Pierre
values[0]=Lucette
values[1]=
values[2]=Virginie
values[3]=Gisèle
values is a list: true
spouses is a list: false
The key [Jacques] exists, associated with the value [Lucette]
The key [Lucette] does not exist
The key [Jean] exists, associated with the value []
spouse of Lucette: unknown
Number of elements in the dictionary: 3
spouses[Jacques]=Lucette
spouses[Paul]=Virginie
spouses[Pierre]=Gisèle

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:

<?php

declare(strict_types=1);

// classic multidimensional arrays
// initialization
$multi = [[0, 1, 2], [10, 11, 12, 13], [20, 21, 22, 23, 24]];
// traversal
for ($i1 = 0; $i1 < count($multi); $i1++) {
    for ($i2 = 0; $i2 < count($multi[$i1]); $i2++) {
        print "multi[$i1][$i2]=" . $multi[$i1][$i2] . "\n";
    }
}
// multidimensional dictionaries
// initialization
$multi = ["zero" => [0, 1, 2], "one" => [10, 11, 12, 13], "two" => [20, 21, 22, 23, 24]];
// traversal
foreach ($multi as $key => $value) {
    for ($i2 = 0; $i2 < count($value); $i2++) {
        print "multi[$key][$i2]={$multi[$key][$i2]}\n";
    }
}

Results:

> php basics-07.php
multi[0][0]=0
multi[0][1]=1
multi[0][2]=2
multi[1][0]=10
multi[1][1]=11
multi[1][2]=12
multi[1][3]=13
multi[2][0]=20
multi[2][1]=21
multi[2][2]=22
multi[2][3]=23
multi[2][4]=24
multi[zero][0]=0
multi[zero][1]=1
multi[zero][2]=2
multi[one][0]=10
multi[one][1]=11
multi[one][2]=12
multi[one][3]=13
multi[two][0]=20
multi[two][1]=21
multi[two][2]=22
multi[two][3]=23
multi[two][4]=24

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:

<?php

declare(strict_types=1);

// string notation
$string1 = "one";
$string2 = 'one';
print "[$string1,$string2]\n";
// between single quotes, variables are not evaluated
print '[$string1,$string2]' . "\n";
// the {$...} notation delimits the expression to evaluate
$person = ["name" => "dupont", "age" => 34];
print "{$person['name']} is {$person['age']} years old\n";
// a string over several lines (heredoc)
$text = <<<END
    name: $string1
    age: {$person['age']}
    END;
print "$text\n";

Results:

1
2
3
4
5
6
> php basics-08.php
[one,one]
[$string1,$string2]
dupont is 34 years old
name: one
age: 34

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:

<?php

// strict respect of the type of function parameters
declare(strict_types=1);

// comparison function
function compareStrings(string $string1, string $string2): void
{
    // compares string1 and string2
    if ($string1 === $string2) {
        print "[$string1] is equal to [$string2]\n";
    } else {
        print "[$string1] is different from [$string2]\n";
    }
}

// string comparison tests
compareStrings("abcd", "abcd");
compareStrings("", "");
compareStrings("1", "");

// searching for a substring (PHP 8)
$file = "taxpayersdata.txt";
print "[$file] contains [data]: " . var_export(str_contains($file, "data"), true) . "\n";
print "[$file] starts with [tax]: " . var_export(str_starts_with($file, "tax"), true) . "\n";
print "[$file] ends with [.json]: " . var_export(str_ends_with($file, ".json"), true) . "\n";
// position of a substring
print "position of [data] in [$file]: " . strpos($file, "data") . "\n";
// length, uppercase
print "length of [$file]: " . strlen($file) . ", in uppercase: " . strtoupper($file) . "\n";
// beware: strlen counts bytes, mb_strlen counts characters
print "strlen(\"café\")=" . strlen("café") . ", mb_strlen(\"café\")=" . mb_strlen("café") . "\n";

Results:

> php basics-09.php
[abcd] is equal to [abcd]
[] is equal to []
[1] is different from []
[taxpayersdata.txt] contains [data]: true
[taxpayersdata.txt] starts with [tax]: true
[taxpayersdata.txt] ends with [.json]: false
position of [data] in [taxpayersdata.txt]: 9
length of [taxpayersdata.txt]: 17, in uppercase: TAXPAYERSDATA.TXT
strlen("café")=5, mb_strlen("café")=4

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:

<?php

declare(strict_types=1);

// string to array
$string = "1:2:3:4";
$tab = explode(":", $string);
// array traversal
print "tab has " . count($tab) . " elements\n";
for ($i = 0; $i < count($tab); $i++) {
    print "tab[$i]=$tab[$i]\n";
}
// array to string
$string2 = implode(":", $tab);
print "string2=$string2\n";
// let's add an empty field
$string .= ":";
print "string=$string\n";
$tab = explode(":", $string);
// array traversal
print "tab has " . count($tab) . " elements\n";
for ($i = 0; $i < count($tab); $i++) {
    print "tab[$i]=$tab[$i]\n";
} // we now have 5 elements, the last one being empty
// let's add an empty field again
$string .= ":";
print "string=$string\n";
$tab = explode(":", $string);
// array traversal
print "tab has " . count($tab) . " elements\n";
for ($i = 0; $i < count($tab); $i++) {
    print "tab[$i]=$tab[$i]\n";
} // we now have 6 elements, the last two being empty

Results:

> php basics-10.php
tab has 4 elements
tab[0]=1
tab[1]=2
tab[2]=3
tab[3]=4
string2=1:2:3:4
string=1:2:3:4:
tab has 5 elements
tab[0]=1
tab[1]=2
tab[2]=3
tab[3]=4
tab[4]=
string=1:2:3:4::
tab has 6 elements
tab[0]=1
tab[1]=2
tab[2]=3
tab[3]=4
tab[4]=
tab[5]=

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:

<?php

// strict type for function parameters
declare(strict_types=1);

// regular expressions in php
// retrieving the different fields of a string
// the pattern: a sequence of digits surrounded by any characters
// we only want to retrieve the sequence of digits
$pattern = "/(\d+)/";
// we match the string against the pattern
comparePattern2String($pattern, "xyz1234abcd");
comparePattern2String($pattern, "12 34");
comparePattern2String($pattern, "abcd");

// the pattern: a sequence of digits surrounded by any characters
// we want the sequence of digits as well as the fields that follow and precede it
$pattern = "/^(.*?)(\d+)(.*?)$/";
// we match the string against the pattern
comparePattern2String($pattern, "xyz1234abcd");
comparePattern2String($pattern, "12 34");
comparePattern2String($pattern, "abcd");

// the pattern - a date in dd/mm/yy format
$pattern = "/^\s*(\d\d)\/(\d\d)\/(\d\d)\s*$/";
comparePattern2String($pattern, "10/05/97");
comparePattern2String($pattern, "  04/04/01  ");
comparePattern2String($pattern, "5/1/01");

// the pattern - a decimal number
$pattern = "/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/";
comparePattern2String($pattern, "187.8");
comparePattern2String($pattern, "-0.6");
comparePattern2String($pattern, "4");
comparePattern2String($pattern, ".6");
comparePattern2String($pattern, "4.");
comparePattern2String($pattern, " + 4");

// end
exit;

// --------------------------------------------------------------------------
function comparePattern2String(string $pattern, string $string): void
{
    // compares the string $string with the pattern $pattern
    // we match the string against the pattern
    $fields = [];
    $matches = preg_match($pattern, $string, $fields);
    // display results
    print "\nResults($pattern,$string)\n";
    if ($matches === 1) {
        for ($i = 0; $i < count($fields); $i++) {
            print "fields[$i]=$fields[$i]\n";
        }
    } else {
        print "The string [$string] does not match the pattern [$pattern]\n";
    }
}

Results:

> php basics-11.php

Results(/(\d+)/,xyz1234abcd)
fields[0]=1234
fields[1]=1234

Results(/(\d+)/,12 34)
fields[0]=12
fields[1]=12

Results(/(\d+)/,abcd)
The string [abcd] does not match the pattern [/(\d+)/]

Results(/^(.*?)(\d+)(.*?)$/,xyz1234abcd)
fields[0]=xyz1234abcd
fields[1]=xyz
fields[2]=1234
fields[3]=abcd

Results(/^(.*?)(\d+)(.*?)$/,12 34)
fields[0]=12 34
fields[1]=
fields[2]=12
fields[3]= 34

Results(/^(.*?)(\d+)(.*?)$/,abcd)
The string [abcd] does not match the pattern [/^(.*?)(\d+)(.*?)$/]

Results(/^\s*(\d\d)\/(\d\d)\/(\d\d)\s*$/,10/05/97)
fields[0]=10/05/97
fields[1]=10
fields[2]=05
fields[3]=97

Results(/^\s*(\d\d)\/(\d\d)\/(\d\d)\s*$/,  04/04/01  )
fields[0]=  04/04/01
fields[1]=04
fields[2]=04
fields[3]=01

Results(/^\s*(\d\d)\/(\d\d)\/(\d\d)\s*$/,5/1/01)
The string [5/1/01] does not match the pattern [/^\s*(\d\d)\/(\d\d)\/(\d\d)\s*$/]

Results(/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/,187.8)
fields[0]=187.8
fields[1]=
fields[2]=187.8

Results(/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/,-0.6)
fields[0]=-0.6
fields[1]=-
fields[2]=0.6

Results(/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/,4)
fields[0]=4
fields[1]=
fields[2]=4

Results(/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/,.6)
fields[0]=.6
fields[1]=
fields[2]=.6

Results(/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/,4.)
fields[0]=4.
fields[1]=
fields[2]=4.

Results(/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/, + 4)
fields[0]= + 4
fields[1]=+
fields[2]=4

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:

$pattern = "/^(.*?)(\d+)(.*?)$/";

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 ($)].

$pattern = "/^\s*(\d\d)\/(\d\d)\/(\d\d)\s*$/";

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 ($)].

$pattern = "/^\s*([+-]?)\s*(\d+\.\d*|\.\d+|\d+)\s*/";

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:

<?php

// how parameters are passed to a function
// strict respect of the parameter types
declare(strict_types=1);

function f(int &$i, int $j): void
{
    // $i will be obtained by reference
    // $j will be obtained by value
    $i++;
    $j++;
    print "f[i,j]=[$i,$j]\n";
}

// tests
$i = 0;
$j = 0;
// $i and $j are passed to the function f
f($i, $j);
print "test[i,j]=[$i,$j]\n";

Results:

1
2
3
> php basics-12.php
f[i,j]=[1,1]
test[i,j]=[1,0]

Comments:

The code above demonstrates the two ways to pass parameters to a function. Consider the following example:

1
2
3
4
5
6
7
8
9
function f(int &$a, int $b): void
{
    …
}

// main program
$i = 10;
$j = 20;
f($i, $j);
  • 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:

1
2
3
4
function swap(mixed &$a, mixed &$b): void
{
    [$a, $b] = [$b, $a];
}

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:

<?php

// how parameters are passed
// no declare(strict_types=1) in this script: we are in "coercive" mode

function f(&$i, $j)
{
    // $i will be obtained by reference
    // $j will be obtained by value
    $i++;
    $j++;
    print "f[i,j]=[$i,$j]\n";
}

function g(int &$i, int $j): void
{
    // $i will be obtained by reference
    // $j will be obtained by value
    $i++;
    $j++;
    print "g[i,j]=[$i,$j]\n";
}

// tests
$i = 0;
$j = 0;
// $i and $j are passed to the function f
f($i, $j);
print "test[i,j]=[$i,$j]\n";
// $i and $j are passed to the function g
g($i, $j);
print "test[i,j]=[$i,$j]\n";
// we pass float parameters to f
$a = 5.0;
$b = 6.2;
f($a, $b);
print "test[a,b]=[$a,$b]\n";
// we pass float parameters to g
$a = 5.0;
$b = 6.0;
g($a, $b);
print "test[a,b]=[$a,$b]\n";

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:

1
2
3
4
5
6
7
8
9
> php basics-13.php
f[i,j]=[1,1]
test[i,j]=[1,0]
g[i,j]=[2,1]
test[i,j]=[2,0]
f[i,j]=[6,7.2]
test[a,b]=[6,6.2]
g[i,j]=[6,7]
test[a,b]=[6,6]
  • 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:

declare(strict_types=1);

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:

> php basics-13b.php
f[i,j]=[1,1]
test[i,j]=[1,0]
g[i,j]=[2,1]
test[i,j]=[2,0]
f[i,j]=[6,7.2]
test[a,b]=[6,6.2]

Fatal error: Uncaught TypeError: g(): Argument #1 ($i) must be of type int, float given, called in .../php8-oct-2026/scripts-console/basics/basics-13b.php on line 42 and defined in .../php8-oct-2026/scripts-console/basics/basics-13b.php:16
Stack trace:
#0 .../php8-oct-2026/scripts-console/basics/basics-13b.php(42): g(5.0, 6.0)
#1 {main}
  thrown in .../php8-oct-2026/scripts-console/basics/basics-13b.php on line 16

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:

<?php

// function parameters in PHP 8
declare(strict_types=1);

// 1 - parameters with a default value
function greeting(string $name, string $phrase = "Hello", bool $uppercase = false): string
{
    $message = "$phrase $name";
    return $uppercase ? strtoupper($message) : $message;
}

print greeting("Pierre") . "\n";
print greeting("Pierre", "Good evening") . "\n";
// 2 - named arguments (PHP 8.0): we can skip a parameter and give them in any order
print greeting("Pierre", uppercase: true) . "\n";
print greeting(uppercase: true, phrase: "Hi", name: "Paul") . "\n";
// predefined functions also accept named arguments
print str_pad(string: "7", length: 3, pad_string: "0", pad_type: STR_PAD_LEFT) . "\n";

// 3 - union type (PHP 8.0): the parameter can be of one of the listed types
function double(int|float $number): int|float
{
    return 2 * $number;
}

var_dump(double(4));
var_dump(double(4.5));

// 4 - nullable type: ?string is equivalent to string|null
function length(?string $string): int
{
    // the ?? operator returns its right operand if the left operand is null
    return strlen($string ?? "");
}

print "length(\"abc\")=" . length("abc") . ", length(null)=" . length(null) . "\n";

// 5 - mixed type (PHP 8.0) and match expression (PHP 8.0)
function describe(mixed $value): string
{
    // match compares with === and returns a value
    return match (true) {
        is_int($value), is_float($value) => "the number $value",
        is_string($value) => "the string [$value]",
        is_bool($value) => "the boolean " . ($value ? "true" : "false"),
        is_array($value) => "an array of " . count($value) . " element(s)",
        default => "a value of type " . get_debug_type($value),
    };
}

foreach ([12, 3.5, "abc", false, [1, 2], null] as $value) {
    print describe($value) . "\n";
}

// match on a value
$children = 3;
$shares = match ($children) {
    0 => 0.0,
    1, 2 => $children * 0.5,
    default => 1 + ($children - 2) * 1.0,
};
print "$children child(ren) bring $shares share(s)\n";

// 6 - variable number of parameters (variadic) and the ... (spread) operator
function sum(int|float ...$numbers): int|float
{
    // $numbers is an array
    return array_sum($numbers);
}

print "sum()=" . sum() . "\n";
print "sum(1, 2, 3)=" . sum(1, 2, 3) . "\n";
$values = [10, 20, 30.5];
// the array is "spread" into actual parameters
print "sum(...\$values)=" . sum(...$values) . "\n";
// the ... operator also makes it possible to merge arrays
$all = [0, ...$values, 40];
print "all=[" . implode(",", $all) . "]\n";

Results:

> php basics-14.php
Hello Pierre
Good evening Pierre
HELLO PIERRE
HI PAUL
007
int(8)
float(9)
length("abc")=3, length(null)=0
the number 12
the number 3.5
the string [abc]
the boolean false
an array of 2 element(s)
a value of type null
3 child(ren) bring 2 share(s)
sum()=0
sum(1, 2, 3)=6
sum(...$values)=60.5
all=[0,10,20,30.5,40]

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 integer or double;
  • 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:

<?php

// types in strict mode
declare(strict_types=1);

// results returned by a function
// a function can return several values in an array
[$res1, $res2, $res3] = f1(10);
print "[$res1,$res2,$res3]\n";
$res = f1(10);
for ($i = 0; $i < count($res); $i++) {
    print "f1: res[$i]=$res[$i]\n";
}
// an associative array can also be "destructured"
["min" => $min, "max" => $max] = minMax([4, -2, 17, 8]);
print "minMax: [min=$min,max=$max]\n";

// a function can return an object
$res = f2(10);
print "f2: [$res->res1,$res->res2,$res->res3]\n";
// what kind of object?
print "kind of object: ";
var_dump($res);

// we do the same thing with the function f3
$res = f3(10);
print "f3: [$res->res1,$res->res2,$res->res3]\n";
// what kind of object?
print "kind of object: ";
var_dump($res);

// a function can return an object or null
$res = f4(10);
// the nullsafe operator ?-> returns null instead of causing an error if $res is null
print "f4(10): res1=" . ($res?->res1 ?? "none") . "\n";
$res = f4(-10);
print "f4(-10): res1=" . ($res?->res1 ?? "none") . "\n";

// end
exit;

// function f1
function f1(int $value): array
{
    // returns an array ($value+1,$value+2,$value+3)
    return [$value + 1, $value + 2, $value + 3];
}

// function minMax
function minMax(array $numbers): array
{
    // returns an associative array
    return ["min" => min($numbers), "max" => max($numbers)];
}

// function f2
function f2(int $value): object
{
    // returns an object ($value+1,$value+2,$value+3)
    // obtained by transforming an associative array into an object
    return (object) ["res1" => $value + 1, "res2" => $value + 2, "res3" => $value + 3];
}

// function f3 - does the same thing as the function f2
function f3(int $value): object
{
    // returns an object ($value+1,$value+2,$value+3)
    $res = new stdClass();
    $res->res1 = $value + 1;
    $res->res2 = $value + 2;
    $res->res3 = $value + 3;
    // returns the object
    return $res;
}

// function f4 - returns an object if $value is positive, null otherwise
function f4(int $value): ?object
{
    return $value >= 0 ? f3($value) : null;
}

Results:

> php basics-15.php
[11,12,13]
f1: res[0]=11
f1: res[1]=12
f1: res[2]=13
minMax: [min=-2,max=17]
f2: [11,12,13]
kind of object: object(stdClass)#1 (3) {
  ["res1"]=>
  int(11)
  ["res2"]=>
  int(12)
  ["res3"]=>
  int(13)
}
f3: [11,12,13]
kind of object: object(stdClass)#2 (3) {
  ["res1"]=>
  int(11)
  ["res2"]=>
  int(12)
  ["res3"]=>
  int(13)
}
f4(10): res1=11
f4(-10): res1=none

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:

<?php

// functions are values too
declare(strict_types=1);

$numbers = [4, -2, 17, 8, -5];

// 1 - an anonymous function assigned to a variable
$square = function (int $x): int {
    return $x * $x;
};
print "square(5)=" . $square(5) . "\n";

// 2 - an anonymous function passed as a parameter to a predefined function
// array_map applies the function to each element of the array
$squares = array_map($square, $numbers);
print "squares=[" . implode(",", $squares) . "]\n";

// 3 - an anonymous function using an outside variable: the use keyword
$threshold = 5;
$large = array_filter($numbers, function (int $x) use ($threshold): bool {
    return $x > $threshold;
});
print "numbers > $threshold: [" . implode(",", $large) . "]\n";

// 4 - the arrow function (PHP 7.4): a single expression, outside variables captured automatically
$positives = array_filter($numbers, fn(int $x): bool => $x >= 0);
print "positives=[" . implode(",", $positives) . "]\n";
// sorting with a comparison function: the <=> operator returns -1, 0 or 1
$sorted = $numbers;
usort($sorted, fn(int $a, int $b): int => $b <=> $a);
print "descending sort=[" . implode(",", $sorted) . "]\n";

// 5 - searching in an array (PHP 8.4)
print "first negative: " . array_find($numbers, fn(int $x): bool => $x < 0) . "\n";
print "at least one > 10: " . var_export(array_any($numbers, fn(int $x): bool => $x > 10), true) . "\n";
print "all > -10: " . var_export(array_all($numbers, fn(int $x): bool => $x > -10), true) . "\n";

// 6 - the first-class callable syntax (PHP 8.1): functionName(...)
$uppercase = strtoupper(...);
print $uppercase("hello") . "\n";
$names = ["dupont", "durand", "martin"];
print "names=[" . implode(",", array_map(ucfirst(...), $names)) . "]\n";

// 7 - the pipe operator |> (PHP 8.5): the result on the left is passed to the function on the right
$result = "  Hello everybody out there  "
    |> trim(...)
    |> strtolower(...)
    |> (fn(string $s): string => str_replace(" ", "-", $s));
print "result=[$result]\n";
// equivalent without the pipe operator
$result = str_replace(" ", "-", strtolower(trim("  Hello everybody out there  ")));
print "result=[$result]\n";

Results:

> php basics-17.php
square(5)=25
squares=[16,4,289,64,25]
numbers > 5: [17,8]
positives=[4,17,8]
descending sort=[17,8,4,-2,-5]
first negative: -2
at least one > 10: true
all > -10: true
HELLO
names=[Dupont,Durand,Martin]
result=[hello-everybody-out-there]
result=[hello-everybody-out-there]

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:

<?php

// strict respect of the type of function parameters
declare(strict_types=1);

// sequential processing of a text file
// it is a set of lines of the form login:pwd:uid:gid:info:dir:shell
// each line is put into a dictionary in the form login => [uid, gid, info, dir, shell]
// we set the file name
$INFO = "info.txt";
// we open it for creation
$file = fopen($INFO, "w");
if ($file === false) {
    print "Error opening the file $INFO for writing\n";
    exit;
}
// we generate arbitrary content
for ($i = 0; $i < 100; $i++) {
    fputs($file, "login$i:pwd$i:uid$i:gid$i:info$i:dir$i:shell$i\n");
}
// we close the file
fclose($file);

// we process it - fgets keeps the end-of-line mark
// this avoids getting an empty string when reading a blank line
// we open it for reading
$file = fopen($INFO, "r");
if ($file === false) {
    print "Error opening the file $INFO for reading\n";
    exit;
}

// the dictionary of information
$dict = [];
// the lines are less than 1000 characters long
// reading the line stops at the end-of-line mark
// or at the end-of-file mark
while (($line = fgets($file, 1000)) !== false) {
    // we remove the end-of-line mark if it exists
    $line = cutNewLineChar($line);
    // we put the line into an array
    $info = explode(":", $line);
    // we retrieve the login
    $login = array_shift($info);
    // we ignore the pwd
    array_shift($info);
    // we create an entry in the dictionary
    $dict[$login] = $info;
}
// we close it
fclose($file);

// using the dictionary
displayInfo($dict, "login10");
displayInfo($dict, "X");

// another way to read a text file: the file function
$lines = file($INFO, FILE_IGNORE_NEW_LINES);
print "the file $INFO has " . count($lines) . " lines, the last one is [" . array_last($lines) . "]\n";

// end
exit;

// --------------------------------------------------------------------------
function displayInfo(array $dict, string $key): void
{
    // displays the value associated with key in the dictionary $dict if it exists
    if (isset($dict[$key])) {
        // value exists - is it an array?
        $value = $dict[$key];
        if (is_array($value)) {
            print "[$key," . implode(":", $value) . "]\n";
        } else {
            // $value is not an array
            print "[$key,$value]\n";
        }
    } else {
        // $key is not a key of the dictionary $dict
        print "the key [$key] does not exist\n";
    }
}

// --------------------------------------------------------------------------
function cutNewLineChar(string $line): string
{
    // we remove the end-of-line mark from $line if it exists
    // (\n on Unix, \r\n on Windows)
    return rtrim($line, "\r\n");
}

The [info.txt] file created by the script:

1
2
3
4
5
6
login0:pwd0:uid0:gid0:info0:dir0:shell0
login1:pwd1:uid1:gid1:info1:dir1:shell1
login2:pwd2:uid2:gid2:info2:dir2:shell2
…
login98:pwd98:uid98:gid98:info98:dir98:shell98
login99:pwd99:uid99:gid99:info99:dir99:shell99

The results:

1
2
3
4
> php basics-16.php
[login10,uid10:gid10:info10:dir10:shell10]
the key [X] does not exist
the file info.txt has 100 lines, the last one is [login99:pwd99:uid99:gid99:info99:dir99:shell99]

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:

1
2
3
4
5
json
├── family.json
├── json-01.php
├── json-02.php
└── json-03.php

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:

<?php

declare(strict_types=1);

$array1 = ["lastName" => "séléné", "firstName" => "bénédicte", "age" => 34];
// JSON encoding of the array array1 with escaped Unicode characters
print "JSON encoding of the array array1 with escaped Unicode characters\n";
$json1 = json_encode($array1);
print "json1=$json1\n";
// JSON encoding of the array array1 with unescaped Unicode characters
print "JSON encoding of the array array1 with unescaped Unicode characters\n";
$json2 = json_encode($array1, JSON_UNESCAPED_UNICODE);
print "json2=$json2\n";
// JSON decoding into an associative array
print "JSON decoding of json2 into an associative array\n";
$array2 = json_decode($json2, true);
var_dump($array2);
foreach ($array2 as $key => $value) {
    print "$key:$value\n";
}
// JSON decoding into an object
print "JSON decoding of json2 into a stdClass object\n";
$object2 = json_decode($json2);
var_dump($object2);
print "firstName=$object2->firstName\n";
print "lastName=$object2->lastName\n";
print "age=$object2->age\n";

Results:

> php json-01.php
JSON encoding of the array array1 with escaped Unicode characters
json1={"lastName":"s\u00e9l\u00e9n\u00e9","firstName":"b\u00e9n\u00e9dicte","age":34}
JSON encoding of the array array1 with unescaped Unicode characters
json2={"lastName":"séléné","firstName":"bénédicte","age":34}
JSON decoding of json2 into an associative array
array(3) {
  ["lastName"]=>
  string(9) "séléné"
  ["firstName"]=>
  string(11) "bénédicte"
  ["age"]=>
  int(34)
}
lastName:séléné
firstName:bénédicte
age:34
JSON decoding of json2 into a stdClass object
object(stdClass)#1 (3) {
  ["lastName"]=>
  string(9) "séléné"
  ["firstName"]=>
  string(11) "bénédicte"
  ["age"]=>
  int(34)
}
firstName=bénédicte
lastName=séléné
age=34

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]:

<?php

declare(strict_types=1);

$array = [
    "lastName" => "séléné", "firstName" => "bénédicte", "age" => 34,
    "husband" => ["lastName" => "icariù", "firstName" => "ignacio", "age" => 35],
    "children" => [
        ["firstName" => "angèle", "age" => 8],
        ["firstName" => "andré", "age" => 2],
    ],
];
// JSON encoding of the multi-level array
print "JSON encoding of a multi-level array\n";
$json = json_encode($array, JSON_UNESCAPED_UNICODE);
print "json=$json\n";
// the same encoding, presented in a readable way
print "JSON encoding with the JSON_PRETTY_PRINT option\n";
print json_encode($array["husband"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";

Results:

1
2
3
4
5
6
7
8
9
> php json-02.php
JSON encoding of a multi-level array
json={"lastName":"séléné","firstName":"bénédicte","age":34,"husband":{"lastName":"icariù","firstName":"ignacio","age":35},"children":[{"firstName":"angèle","age":8},{"firstName":"andré","age":2}]}
JSON encoding with the JSON_PRETTY_PRINT option
{
    "lastName": "icariù",
    "firstName": "ignacio",
    "age": 35
}

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]:

{
    "wife": {
        "lastName": "séléné",
        "firstName": "bénédicte",
        "age": 34
    },
    "husband": {
        "lastName": "icariù",
        "firstName": "ignacio",
        "age": 35
    },
    "children": [
        {
            "firstName": "angèle",
            "age": 8
        },
        {
            "firstName": "andré",
            "age": 2
        }
    ]
}

The [json-03.php] script is as follows:

<?php

declare(strict_types=1);

// reading the JSON file
$json = file_get_contents("family.json");
// is the content read valid JSON? (PHP 8.3)
if ($json === false || !json_validate($json)) {
    print "The file [family.json] is missing or does not contain valid JSON\n";
    exit;
}
// JSON decoding into an object
$family1 = json_decode($json);
print "----family1\n";
var_dump($family1);
// JSON decoding into an associative array
print "----family2\n";
$family2 = json_decode($json, true);
var_dump($family2);

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:

> php json-03.php
----family1
object(stdClass)#2 (3) {
  ["wife"]=>
  object(stdClass)#1 (3) {
    ["lastName"]=>
    string(9) "séléné"
    ["firstName"]=>
    string(11) "bénédicte"
    ["age"]=>
    int(34)
  }
  ["husband"]=>
  object(stdClass)#3 (3) {
    ["lastName"]=>
    string(7) "icariù"
    ["firstName"]=>
    string(7) "ignacio"
    ["age"]=>
    int(35)
  }
  ["children"]=>
  array(2) {
    [0]=>
    object(stdClass)#4 (2) {
      ["firstName"]=>
      string(7) "angèle"
      ["age"]=>
      int(8)
    }
    [1]=>
    object(stdClass)#5 (2) {
      ["firstName"]=>
      string(6) "andré"
      ["age"]=>
      int(2)
    }
  }
}
----family2
array(3) {
  ["wife"]=>
  array(3) {
    ["lastName"]=>
    string(9) "séléné"
    ["firstName"]=>
    string(11) "bénédicte"
    ["age"]=>
    int(34)
  }
  ["husband"]=>
  array(3) {
    ["lastName"]=>
    string(7) "icariù"
    ["firstName"]=>
    string(7) "ignacio"
    ["age"]=>
    int(35)
  }
  ["children"]=>
  array(2) {
    [0]=>
    array(2) {
      ["firstName"]=>
      string(7) "angèle"
      ["age"]=>
      int(8)
    }
    [1]=>
    array(2) {
      ["firstName"]=>
      string(6) "andré"
      ["age"]=>
      int(2)
    }
  }
}
  • 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.