PHP is a popular web scripting language. According to w3techs.com, in December 2019, 78.9% of server-side websites with a known language used PHP. It happens that PHP was installed on your computer as part of Catalina.
For some, it might be a stretch to talk about PHP in a book about Terminal. However, if you’ve never used Terminal before, chances are you won’t know about PHP. I will show you some of the basic concepts of PHP, like variables and control statements, and finally finish with some projects. Let’s dive in.
Determining Your PHP Version
Before you start learning about coding in PHP, it is a good idea to determine what version you have installed. As of Catalina, macOS ships with PHP 7.3. With Catalina 10.15.6, PHP has had a few security releases, but is still following the 7.3 branch.
Each new branch of PHP introduces new features and removes out-of-date ones. Eventually a branch of PHP will cease to receive updates.
Basic Usage
This file shows different aspects of a PHP file. By default, PHP will output the contents of the file to the screen, until it encounters <?php to enable the PHP parser. As part of the previous chapter, we downloaded a ZIP file that contained many different files.
PHP Configuration
The info.php file is a PHP file that is designed to show you all the different settings available within PHP.
% cat ~/random-files-master/info.php
<?php
// PHP File to show information about PHP and your system
phpinfo();
By changing these values, you can control how PHP operates. For example, the memory_limit setting will cause PHP to terminate if it uses more memory than is specified. By default, the memory limit is set to 128MB, which is fine for simple scripts. However, if you have a script that builds a report, you might need more memory.
PHP Configuration on Cli
phpinfo() is a function in PHP that outputs all the settings. It’s very handy for diagnosing problems, but also for making sure PHP doesn’t have any startup errors. Now that we’ve seen the contents, let’s run it. You can get PHP to parse this file by passing it in as an argument.
% php ~/random-files-master/info.php
phpinfo()
PHP Version => 7.3.11
System => Darwin MacBook-Pro.lan 19.6.0 Darwin Kernel Version 19.6.0: Sun Jul 5 00:43:10 PDT 2020; root:xnu-6153.141.1~9/RELEASE_X86_64 x86_64
Build Date => Jul 5 2020 03:22:53
There is a lot of information in the phpinfo file, and when you are looking for a specific configuration variable, it makes sense to use grep to filter for what you are looking for.
PHP Configuration File
There is a PHP configuration file that controls all of these settings and it is named php.ini. Depending on how PHP has been set up, this file can be anywhere on your filesystem. Thankfully there is a line in the phpinfo that tells you where to look for this file.
Configuration File (php.ini) Path => /etc
If the file does not exist, then PHP is using default values for settings that are baked into it when it was originally compiled. In the past, these default settings needed to be modified before you could use PHP on a web server. The memory limit used to be only 8MB, which caused problems for all but the smallest of scripts.
It is also possible to change most of the settings in the php.ini file, inside of your PHP scripts, with the PHP function ini_set(). Note that changing the settings in this way is only temporary.
PHP Configuration in a Browser
Some installations of PHP use a different php.ini file for the browser version of PHP. Currently, Brew uses the same php.ini file for the command line and the browser. It is worth doubling-checking this, because if you try to change settings in the wrong version, you will invariably become frustrated.
PHP Web Server
PHP has a built-in web server. It should only be used for testing and never be used in production. Let’s start PHP’s built-in web server.
% cd ~/random-files-master
% php -S 127.0.0.1:8000
PHP 7.3.11 Development Server started at Thu Jul 23 23:59:35 2020
Listening on http://127.0.0.1:8000
Document root is /Users/danielplatt/random-files-master
Press Ctrl-C to quit.
You can see from this output which folder PHP is looking in for files, by looking at the line that starts Document root. This should be set to your current directory.
Document root is /Users/danielplatt/random-files-master
If you visit http://127.0.0.1:8000/ from your web browser, you’ll see that PHP doesn’t have a file to give you, as shown in Figure 6-1.
If you want PHP to serve a file for http://127.0.0.1:8000/, you can create a default file called index.php. Try the info.php file that you created earlier, by going to http://127.0.0.1:8000/info.php, as shown in Figure 6-2.
Figure 6-2
PHP info when viewed in a web browser
You should find this format easier to read than the same information on the command line. Look back at your Terminal, where you started the PHP web server. Every request to the PHP server has been logged for you to see.
Listening on http://127.0.0.1:8000
Document root is /Users/danielplatt/random-files-master
Press Ctrl-C to quit.
php -S 127.0.0.1:8000
[Sun Jan 19 13:29:51 2020] 127.0.0.1:50302 [404]: / - No such file or directory
[Sun Jan 19 13:29:51 2020] 127.0.0.1:50303 [404]: /favicon.ico - No such file or directory
[Sun Jan 19 13:29:55 2020] 127.0.0.1:50304 [404]: / - No such file or directory
[Sun Jan 19 13:29:56 2020] 127.0.0.1:50307 [404]: /favicon.ico - No such file or directory
[Sun Jan 19 13:29:58 2020] 127.0.0.1:50310 [200]: /info.php
[Sun Jan 19 13:29:58 2020] 127.0.0.1:50311 [404]: /favicon.ico - No such file or directory
Being able to see the requests to the web server greatly helps when trying to create your own applications with PHP.
PHP Basics
In this section, we are going to look at some of the core concepts and structures in PHP. These will be enough to get started building your own scripts.
Language
PHP has a lot of the same core concepts that we’ve seen with shell scripting, but you will quickly find out that the structure or syntax is quite different.
We are going to go through a lot of the common concepts to help you get started, but we will only be scratching the surface of what you will be able to do.
Comments
When you start writing a script, you will not know how everything is going to work. It’s beneficial to write pseudocode to help you figure out what will go where and how it will flow. You use comment markers to tell PHP to ignore whatever has been commented out.
Comments are typically used to describe a piece of code, without someone having to read the code fully. It may also be that the code block is quite complex and would require some time to fully understand it. A comment can be a single line of text that describes the function or the reason for doing the next few lines of code. A bad comment is one that isn’t updated when the code is and therefore becomes misleading. Comments come in two different forms—single-line and multiple-line comments.
Single-Line Comments
Single-line comments use either the hash (#) symbol or two backslashes (//), as shown in Listing 6-1.
<?php
// This is a comment
# And this is another comment.
Listing 6-1
Example of Single-Line Comments
The single-line comments can occur anywhere on a line, and even after a piece of code:
<?php
echo "Hello"; # Output greeting
Multi-Line Comments
Multi-line comments, like single-line comments, have a start symbol, but they also have an end symbol. They begin with a forward slash and an asterisk (/*) and end with an asterisk and a forward slash (*/). Everything between the /* and */ is ignored by PHP:
<?php
/*
This is a multi-line comment.
PHP ignores everything on these lines.
Since they are surrounded by the comment symbols.
*/
Constants
By default, all variables are changeable. However, a constant is a special variable that cannot be changed. Constants are named using all capital letters. A constant is defined by using the define function:
define("MY_CONSTANT", 123);
Then you can access this constant using MY_CONSTANT or using constant('MY_CONSTANT'):
echo MY_CONSTANT;
echo constant('MY_CONSTANT');
What Can You Store in a Constant?
You can store any simple (scalar) value, such as a string, int, float, and Boolean. You can also use the array.
Can You Change Constants?
Constants can never be changed once they have been created.
define("MY_CONSTANT", 123);
MY_CONSTANT = 1;
If you ran that code, you would get an error.
Parse error: syntax error, unexpected '='
Why Use Constants?
Constants are used typically for settings in PHP programs. They are really good at this, because you cannot accidentally change their value.
Predefined Constants
There are a few predefined constants available in PHP. We will see a couple of these in action when exploring ints and floats. Another predefined constant that you should know about is PHP_EOL. This constant contains a string for a newline for your current system and can be used in place of \n.
Variables in PHP are similar to shell variables. The difference is that PHP variables can store different types of values. When referencing a variable, you use the dollar ($) symbol in front of the name, regardless of whether you are reading or setting the value. The different types of variables that we cover are:
Strings
Integers
Floats
Arrays
Objects
Nulls
Let’s explore them in more detail.
String Variables
A string is just a series of characters, enclosed in quotes.
$a = '1 string';
It doesn’t matter if the string contains letters, numbers, or symbols. You can use the single quote (') or the double quote (") symbols to define a string. Whichever symbol you use, you must start and end a string with the same type, and include it within the string without first escaping it with backslash.
$a = 'PHP ';
Combining strings, called concatenating, is easy to do using the dot (.) symbol.
echo "hello" . " " . "world";
hello world
There is also a shorthand method for combining strings. Consider adding an extra string to $a.
$a = 'hello';
$a = $a . ' world';
We can condense that into one operation with .= symbol.
$a = 'hello';
$a .= ' world';
One thing to note is that the double quote version of a string allows you to use more than the single quoted string. With the double quoted string, you can actually reference variables, like we did in shell programming.
Integer Variables
With the integer type (usually called an int), you can store any number, positive or negative.
$a = -5;
There is a limit to the size of the number that you can store in an int.
In PHP, there are a few constants that define PHP limits. For integers, there are PHP_INT_MIN and PHP_INT_MAX, which define the bounds (see Listing 6-2).
echo "The smallest int is " . PHP_INT_MIN . PHP_EOL;
echo "The largest int is " . PHP_INT_MAX . PHP_EOL;
% php int.php
The smallest int is -9223372036854775808
The largest int is 9223372036854775807
Listing 6-2
Display Lower and Upper Bounds for an Int
You can do basic math on these variables.
$a = (10 + 4) / 2;
echo $a; // The output will be 7
The result will be an int, unless the answer isn’t a whole number. Then the result will be a float.
Binary Representation of an Integer
It is worth noting that PHP internally stores your integer as a binary. Binary is your computer’s native way to process and store numbers. For example, an 8-bit number can store a number between 0 and 255, which is 256 different numbers. Table 6-1 shows you the different numbers that each size can store.
Table 6-1
Number of States a Binary Number Can Store
8-Bit
7-Bit
6-Bit
5-Bit
4-Bit
3-Bit
2-Bit
1-Bit
256
128
64
32
16
8
4
2
The maximum number of states a bits can store is calculated as 2 to the power of the number of bits (n) or 2n. In mathematical terms, it is written as 2 ^ n. Therefore, the largest number that can be stored is (2 ^ n) - 1, because the numbers start counting at zero.
Apple has standardized its whole computer lineup on 64-bit processors. Let’s look at a 64-bit number.
(2 ^ 64) -1
= 18446744073709551615
We can see this in action with the Calculator app in macOS Catalina. When you open the calculator, you need to change the view to Programmer, as shown in Figure 6-3.
Figure 6-3
The view menu in Calculator
Enter the value of 18446744073709551615 or press the one’s complement (1’s) when the calculator displays 0 and the calculator should look like Figure 6-4.
Figure 6-4
The largest number that can be stored in a 64-bit number
This is larger than the maximum number that PHP can store, which in my case is 9223372036854775807. There is a very good reason for this and it has to do with the smallest number that PHP can store.
When we talked about the largest 64-bit number, we knew that the smallest number was 0. We also know that the smallest number PHP can store is -9223372036854775808. The most significant bit stores whether an integer is positive or negative. We can see this in Figure 6-5, where the number is now the same as PHP’s largest number.
Figure 6-5
Largest number using 63 bits
Calculating a Value in Binary
The simplest way to determine binary numbers is to lay out the numbers that each binary bit represents. When there is a 1, you add that to the total number, and you skip it when there is a 0. For example, the number 200 is shown in Table 6-2.
Table 6-2
Binary Representation of the Number 200
128
64
32
16
8
4
2
1
1
1
0
0
1
0
0
0
We see this in PHP by using printf to print the binary representation of the value we supply, as shown in Listing 6-3.
<?php
$value = 200;
printf("%d in binary is %b\n", $value, $value);
// 200 in binary is 11001000
Listing 6-3
Displaying an Integer in Binary
Floating-Point Numbers
Floating-point numbers (usually called floats, but sometimes they are called a doubles) are similar to ints, but they also have a decimal fraction to them.
$a = 0.1;
Floats have two constants—PHP_FLOAT_MIN and PHP_FLOAT_MAX—that define the bounds (see Listing 6-4).
echo "The smallest positive float is " . PHP_FLOAT_MIN . PHP_EOL;
echo "The largest float is " . PHP_FLOAT_MAX . PHP_EOL;
% php float.php
The smallest positive float is 2.2250738585072E-308
The largest float is 1.7976931348623E+308
Listing 6-4
Displaying Lower and Upper Bounds of a Float
The PHP_FLOAT_MAX value can be converted from e-notation by multiplying by 10 to the power of the number.
1.7976931348623 * 10^308
Effectively, the decimal place will move 308 digits to the right. Suffice it to say, you can store some pretty large numbers in a float. As with integers, you can do basic math with floating-point numbers.
However, the result will always be a float. This happens, even if you think the answer should be an integer.
var_dump(0.5 + 0.5);
float(1)
Binary Representation of a Float
You would be forgiven in thinking that computers are perfect at storing floating-point numbers. However, this isn’t always possible. Earlier we saw how computers stored binary numbers of integers.
Floats are split into the integer part of the number and the decimal fraction, but this is internally stored, so you don’t need to worry about that. The split is determined by the precision and is currently set to 14 digits in PHP. The precision value is controlled by your php.ini file.
% php --info | grep precision
precision => 14 => 14
A 64-bit float uses 48 bits to store the integer and 16 bits to store the float. When PHP stores the decimal fraction of the number, the binary represents a fraction of 1, as shown in Table 6-3.
Table 6-3
Storing a Decimal in Binary
Bits
1-Bit
2-Bit
3-Bit
4-Bit
5-Bit
6-Bit
7 -it
8-Bit
Fraction
1/2
¼
1/8
1/16
1/32
1/64
1/128
1/256
Value
0.5
0.25
0.125
0.0625
0.03125
0.015625
0.0078125
0.00390625
It’s easy for PHP to store decimal numbers like 0.25 and 0.5, as they are 01000000 and 10000000, respectively. A number like 0.03125 is still straightforward and requires five bits to successfully store it as 00100000. However, numbers like 0.1 and 0.2 are much more difficult to store.
In Listing 6-5, the set of instructions calculate the binary representation of a positive float. It works by multiplying the fraction by 2 and seeing if that result is greater than 2. If it is, that is a binary 1. It also subtracts 1 from the number. Otherwise it is a binary 0. It will keep looping to 8 bits.
<?php
$num = 0.1;
$int = intval($num);
$fraction = $num - $int;
$binary = '';
$position = 1;
for ($i = 0; $i<8; $i++) {
$fraction *= 2;
if ($fraction >= 1) {
$fraction -= 1;
$binary .= '1';
} else {
$binary .= '0';
}
}
printf("%f in binary is %08b.%s\n", $num, $int, $binary);
Listing 6-5
Calculating the Binary Representation of a Float
When we run Listing 6-5, we get the binary 00011001 from the output:
0.100000 in binary is 00000000.00011001
However, 00011001 works out to be 0.097656125, which isn’t 0.1. Let’s try the same Listing 6-5, but change the number of bits from 8 to 16. This time, the binary is 0001100110011001, which works out to be 0.0999908447265625:
0.100000 in binary is 00000000.0001100110011001
No matter how many bits we use, some decimal numbers cannot be stored correctly. This is something to be mindful of when you’re comparing floating-point numbers.
Shorthand Operators for Integers and Floats
PHP allows you to use shorthand for arithmetic operations.
$b = 10;
Normally, if you wanted to change $b, you would have to include $b in the calculation.
$b = $b / 5;
Table 6-4 shows the mathematical operations that you can use.
Table 6-4
Mathematical Operators
Symbol
Operation
+
Addition
-
Subtraction
*
Multiplication
/
Division
%
Modulo
**
Raise to the power of
However, you can shorten is and remove the additional $b variable.
An array is a collection of elements. Each element represents a variable that can be of any type, including another array. There are two different types of array—numeric arrays and associative arrays. They both have their place and the difference between them is related to how you access the elements stored in the array.
Numeric Arrays
Numeric arrays are indexed using an integer. Unless specified, they will start at zero. The following two examples will produce the same array:
Example 1:
$a = [1, 2.0, 3, "string"];
Example 2:
$a = [
0 => 1,
1 => 2.0,
2 => 3,
3 => "string",
];
We can see what this looks like with var_dump:
var_dump($a);
array(4) {
[0]=>
int(1)
[1]=>
float(2)
[2]=>
int(3)
[3]=>
string(6) "string"
}
The best way to think about an array is like a row of boxes with a robot arm for a picker, as shown in Figure 6-6.
Figure 6-6
Representation of an array using boxes
How would you read the number 1 value from the array, in Figure 6-6? You have to tell the robot (PHP) how many boxes to move across to get the value you want. You start at the first box, so we don’t have to move. So, we have to tell PHP to move 0 boxes across. In PHP, you do that using square brackets:
echo $a[0];
1
To get a different number, you just have to assign a new value, as shown in Listing 6-6.
$a[0] = 'New value'
echo $a[0];
New value
Listing 6-6
An Element to a Numeric Array
Adding a new value to the array is just as straightforward. We use the square brackets again, but without a value. This will cause PHP to append a value into the array:
$a[] = 'Additional value'
Let’s use var_dump to see how our array now looks:
array(4) {
[0]=>
string(9) "New value"
[1]=>
float(2)
[2]=>
int(3)
[3]=>
string(6) "string"
[4]=>
string(16) "Additional value"
}
Removing elements from the array is as easy as unsetting them:
unset($a[4]);
Associative Arrays
Associative arrays are like numeric arrays, but the value you use to access them is different. The index is based on a string, rather than a number.
$a = [
'key1' => 'carrot',
'key2' => 'potato',
];
Adding and removing elements is the same as with numeric arrays:
$a['key'] = 'cherry';
unset($a['key']);
Booleans
A Boolean value is a simple true or false.
$a = false;
Not to be confused with 'false', which is the word false in a string. These values are used a lot in conditional statements and are the result of comparison operators. I will discuss this more in the section covering if statements.
Object Variables
Objects are an important part of PHP.
$a = new stdClass();
There is a lot you can put into a class, but for the moment, know that you can store variables and code in them.
Null Variables
Null means no value and it is used to define a variable without a value.
$a = null;
Type-Checking Variables
A variable isn’t locked into a specific type; it can be changed by writing a value of a different type. How do you know what value is in a variable? There is a function called gettype(), which will return the type of a variable as a string.
echo gettype($a);
The possible values that will be echoed are:
"string"
"integer"
"float"
"array"
"object"
"NULL"
"resource"
"resource closed"
"unknown type"
There are also built-in functions that can check that a variable is a particular type. A full list is shown in Table 6-6.
Table 6-6
List of Built-In Type Checking Functions
Variable Type
Built-in Function
string
is_string()
integer
is_int() or is_integer()
Float
is_float()
array
is_array()
boolean
is_bool()
object
is_object()
null
is_null()
You can use the functions with an if statement to handle different types of variables, as shown in Listing 6-7.
if (is_string($a)) {
echo "\$a is a string";
} elseif (is_bool($a)) {
echo "\$a is a boolean";
}
Listing 6-7
Checking if $a is a String
We will discuss the if statement in more detail later on in this chapter.
Superglobals
PHP has a few variables that are called superglobals. As superglobal is a variable that can be accessed anywhere in your project. Some superglobals are:
$_SERVER
$_GET
$_POST
$_REQUEST
$_SESSION
These variables are associative arrays and, for the most part, should be treated as read-only, with the exception of $_SESSION.
$_SERVER
You’ve seen this variable before, when we were looking at phpinfo() in the Basic Usage section.
Note
These variables will change depending on whether you are accessing them within a Terminal PHP script or as a web request.
When accessed through a web browser, you will see the following variables about the server and the client:
Whereas, on Terminal, you have details about that environment.
These only show up when run from Terminal. You won't see argv or argc in the browser version of phpinfo.
These variables define how the command was started.
$_GET
This variable is made up of parameters from the URL used to access the PHP script. Given the following URL, http://127.0.0.1/info.php?a=1&b=2, you can expect the $_GET variable to look like this.
array(2) {
["a"]=>
string(1) "1"
["b"]=>
string(1) "2"
}
We can see this in action with the PHP web server:
% sudo /usr/bin/php -S 127.0.0.1:80
Password:
PHP 7.3.11 Development Server started at Thu Jul 30 10:23:18 2020
Listening on http://127.0.0.1:80
Document root is /Users/danielplatt/random-files-master
Press Ctrl-C to quit.
Everything from the question mark until the end of the URL or hash symbol is considered the query string. The bit before the equals sign is the key and after is the value. The ampersand is the separator.
You can even pass an array into the query string by using the square brackets. PHP will interpret them and append the next instance in the array. Given the URL http://127.0.0.1/info.php?arr[]=1&arr[]=2, the variable will show up as follows:
$_POST
The $_POST variable is similar. However, the data can be sent differently from the browser. One way of sending this data is using a HTML form, with the post method.
$_REQUEST
The request variable is a combination of $_GET and $_POST. Where possible, use $_GET or $_POST directly.
Note
The order is defined in phpinfo, by the request_order directive.
$_SESSION
The session variable has the ability to persist between page reloads, given the right circumstances. It will be unique for every different user/browser that visits your page. Listing 6-8 is a very simple page counter to show the user/browser page loads.
<?php
session_start();
$i = 0;
if (isset($_SESSION['i'])) {
$i = $_SESSION['i'];
}
$_SESSION['i'] = $i+1;
var_dump($_SESSION);
Listing 6-8
Simple Page Counter for Each User/Browser
Every time you load the page, $_SESSION['i'] will increment by 1. However, if you were using Safari, and then you used Chrome, the count would reset back to 1.
A function is a label you give to a block of PHP code, which you can call from anywhere. We’ve already used one function, called var_dump. The label is made up of the function name, var_dump, and the arguments within the brackets. This function outputs the type of value in the variable and the value.
var_dump(1.0);
float(1)
We can also create our own functions. Let’s try to re-create the var_dump function. To start a function, we need to use the function keyword, then provide the label and arguments. After this, we need to surround the code with curly braces { }.
function var_dump($variable) {
# code
}
We just need the code now. You have seen all the required components.
function var_dump($variable) {
echo gettype($var) . "(" . $var . ")" . PHP_EOL;
}
If we try to run this in PHP, we will get an error.
Fatal error: Cannot redeclare var_dump()
This error is because we are trying to create a function with the same name as an existing function. We need to give the function a unique name.
function my_var_dump($variable) {
echo gettype($var) . "(" . $var . ")" . PHP_EOL;
}
Listing 6-9 shows how this function compares to the PHP version. I will put the output in a comment after the function.
As you can see, the output is similar, but not exactly the same. What happens if you want to do something in a function and then return the result?
$answer = addTwo(4);
# $answer = 6
You have seen all the components required to create a function that returns a value, except one. The last component you need is the ability to return a value from within a function, which is the return statement.
Let’s create that function now.
function addTwo($int) {
return $int + 2;
}
The thing to note about the return statement is that no instructions that happen after the return will be run.
function addTwo($int) {
return $int + 2;
echo "This will never been seen";
}
If Statements
So far, you’ve seen PHP programs that start at the top and run through, line after line, until they finish, with no opportunity to skip any lines. An if statement is used to change the flow of the program, in order to execute different code depending on an expression. It’s like coming to a fork in the road.
You can go left or right, but you need something to help you decide which way to go. Maybe it’s a sign telling you which way to go, or maybe it’s a flip of a coin. PHP is similar. The something that I spoke of is a Boolean expression, something that works out to be true or false.
if (<expression>) {
# if expression is true
echo 'Go Left' . PHP_EOL;
} else {
# if expression is false
echo 'Go Right' . PHP_EOL;
}
You may be aware that the else in the if statement is optional.
if (<expression>) {
# if expression is true
echo 'Go Left' . PHP_EOL;
}
It is also possible to include more than one expression check. You can include additional if checks with theelseif keyword, as shown in Listing 6-10.
$a = rand (0, 1000 );
if ($a > 500) {
echo "\$a is greater than 500.";
} elseif ($a > 250) {
echo "\$a is greater than 250.";
} elseif ($a > 99) {
echo "\$a is greater or equal to 100.";
} else {
echo "\$a is less than to 100.";
}
Listing 6-10
Example Using elseif
It is possible to have as many or as few elseif checks as you want. However, you need to make sure that they are performed in the correct order. Imagine if the check for $a > 99 was first.
Boolean Expression
A Boolean expression is a logical statement that works out to a simple Boolean answer. The expression could be a simple, true or false. Some built-in functions return Boolean values. An example of a function that returns a Boolean is file_exists('/a/file/path'). It’s based on a file existing or not.
Comparison operators are used to compare two values and produce a Boolean. The values could be literally strings or variables. It doesn’t matter. If you wanted to see if a variable equaled a string, you could use the following:
if ($var1 === 'people') {
echo 'Var1 is a string and equals.' . PHP_EOL;
}
Table 6-7 lists most of the comparison operators available in PHP.
Table 6-7
Comparison Operators in PHP
Type
Symbol
Notes
Equals
==
Not Equal
!=
Identical
===
Checks variable type
Not Identical
!==
Checks variable type
Greater than
>
Greater than or equal
>=
Less than
<
Less than or equal
<=
When you compare two values, PHP will convert both sides of the expression to the same type before performing the comparison.
Note
It is best to use === and !== to check the comparison type, before checking if they are equal.
Does the Variable Exist?
Occasionally, you might not know if a variable has been set. This comes up a lot with superglobals. What happens if we try to use a superglobal that doesn’t exist? The easiest way to find out is to try it. The following code is trying to access an associate by an element that doesn’t exist.
<?php
echo $_GET['a'];
When this runs, we get an undefined index, as shown here:
% php /tmp/test.php
PHP Notice: Undefined index: a in /private/tmp/test.php on line 2
How do you check that a particular query parameter has been set? You can wrap the variable in a function called isset(), to test if a variable exists, and isset will return either true or false. You can see this in Listing 6-11.
<?php
if (isset($_GET['a'])) {
echo "The variable $_GET['a'] exists.";
} else {
echo "The variable $_GET['a'] does not exist.";
}
Listing 6-11
Using isset with an if Statement
Loops
In the real world, a loop is something that goes around something else. Like looping a rope around a post. In PHP, some loops have an end and others do not, but they always loop around a piece of code. The purpose of a loop in PHP is to keep running that code until they reach an end condition.
The end condition is a Boolean expression, just like with the if statements. There are a few different types of loops in PHP. We will discuss the four different types of loops next.
The for Loop
The for loop uses a variable for counting that you define as part of the loop. The loop has three parts to it. It declares the start value for the variable ($i), then the Boolean expression ($i<4) allows the for loop to run. While the expression is evaluated as true, the loop will keep running.
Third is the increment ($i++), which moves your variable closer to the end condition. Here’s an example of a for loop that produces the output 0 - 3, followed by a newline:
for ($i=0; $i<4; $i++) {
echo $i . PHP_EOL;
}
Note
The variable can be called anything you like.
The increment can also reduce the variable; you could make the for loop count down to 1 from 4, with $i--.
for ($i=4; $i>0; $i--) {
echo $i . PHP_EOL;
}
Or you could change the step from 1 to 2.
for ($i=0; $i<10; $i+=2) {
echo $i . PHP_EOL;
}
Another way the for loop can be used is to step through a numeric array.
$arr = ['a', 'b', 'c'];
$count = count($arr);
for ($i=0; $i<$count; $i++) {
echo $arr[$i] . PHP_EOL;
}
This will print every element in the array. However, this will work only with numeric arrays that are in sequence.
The while Loop
The while loop typically works the same way as a for loop and can be easily used like one.
$i = 0;
while ($i<4) {
echo $i . PHP_EOL;
$i++;
}
However, the real power of the while loop is that it keeps doing an action until it results in the action you require. For example, you can use a while loop to read the contents of a file until the end of the file is reached, as shown in Listing 6-12.
PHP Script to Read and Echo the Contents of a File
In a while loop, it is possible that the expression is never evaluated as true, and the loop will never have been run. In effect, the loop will be skipped over.
The do while Loop
The do while loop is very similar to the while loop. However, the difference is that the expression is evaluated at the end of the loop. This means that the loop will always happen at least once.
$i = 0;
do {
echo $i . PHP_EOL;
$i++;
} while ($i < 4);
The foreach Loop
With the for loop, we can use the variable to loop through an array.
$arr = ['a', 'b', 'c'];
$count = count($arr);
for ($i=0; $i<$count; $i++) {
echo $arr[$i] . PHP_EOL;
}
The downside with this is you have to count the elements first. You also need to provide a numeric array. If any of the numbers are missing from the sequence, you will have errors. There are ways around this, using a method called array_keys(); however, you can also use the foreach loop.
foreach ($arr as $value) {
echo $value . PHP_EOL;
}
The foreach loop doesn’t care which type of array is iterated. It doesn’t care if there are numbers missing from the sequence. If you want to know what position a value is in the array, you can tweak the foreach syntax to give you that variable as well.
foreach ($arr as $index => $value) {
echo $value . PHP_EOL;
}
Exiting a Loop Early
If you want to break out of a loop before the end condition, you can use the break keyword.
$count = 0;
while (true) {
echo $count . PHP_EOL;
$count++;
if (rand(0, 10) === 0) {
break;
}
}
This example will keep printing numbers in ascending order until rand() returns 0. Another keyword to alter the loops is continue, which will skip to the next iteration of the loop. The example here will only output odd numbers. It skips the even numbers by using the modulo symbol (%) and continue, when $i is even.
for ($i=0; $i<10; $i++) {
if ($i % 2 === 0) {
continue;
}
echo $i . PHP_EOL;
}
Both break and continue will alter any of these loops.
Improved my_var_dump
In our attempt to make a function that worked like var_dump, we didn’t test it with an array. If we had, we would have noticed that it didn’t work as expected. Assume this is the output variable we want to dump.
$arr = [
1,
2,
'',
[],
];
var_dump would show the third element as an empty array.
var_dump($arr);
/*
array(4) {
[0]=>int(1)
[1]=>int(2)
[2]=>string(0) ""
[3]=>array(0) {}
}
*/
However, our my_var_dump function lists the third element as Array.
my_var_dump($arr);
# array(Array)
If you try to use an array as a string, PHP will treat it as a string ("Array"). We need to add more code to handle an array.
If we use an if statement, we can check if the variable is an array with is_array(). We can then iterate over the array and call my_var_dump for the elements inside.
Although this still isn’t perfect, it is good enough to show you many different concepts of programming in PHP, including recursion.
Dependencies
On macOS, Bash has a dependency manager that we installed. It’s Brew. The dependency manager is great at helping you keep all the installed commands up to date.
When building websites in PHP, developers will need to add other people’s code to their projects. These pieces of code are called libraries, components, plugins, or bundles. PHP has a dependency manager for it called Composer. Composer refers to these as dependencies as packages.
This is useful for anyone who needs to track their dependencies in their projects. This is especially important when more than one person can make changes to a project. Each new release of a package could have any combination of new features and bug fixes. Let’s install Composer.
% brew install composer
Anything that you have installed will eventually go out of date. With software, it can happen quickly due to new features or fixes for recently discovered bugs. You define your project’s dependencies in a file called composer.json.
Composer can also create this file for you with composer init in your project directory. These examples assume that your project directory is ~/php-project.
Note
The composer init command will ask you a series of questions. Each question will have square brackets, [ ], at the end which will be the defaults, when you press the Return key without typing anything.
Let’s now run through a basic composer init example but skip all dependencies, which we will add later. You can see this example in Listing 6-13.
% mkdir ~/php-project && cd ~/php-project
% composer init
Welcome to the Composer config generator
This command will guide you through creating your composer.json config.
Package name (<vendor>/<name>) [danielplatt/php-project]:
Description []: my project
Author [Daniel Platt <github@ofdan.co.uk>, n to skip]:
Minimum Stability []: stable
Package Type (e.g. library, project, metapackage, composer-plugin) []: project
License []:
Define your dependencies.
Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? no
{
"name": "danielplatt/php-project",
"authors": [
{
"name": "Daniel Platt",
"email": "github@ofdan.co.uk"
}
],
"require": {}
}
Do you confirm generation [yes]?
Listing 6-13
Example Output from composer init
The information that was entered matters only if you are sharing the project with anyone or are publishing it online.
Searching for Packages
In my PHP projects, I use a group of packages called Symfony. They are well maintained and used by many members of the PHP community. To find packages to install, you can use composer search <term>. I’m going to search for Symfony packages.
% composer search symfony
symfony/process Symfony Process Component
symfony/polyfill-mbstring Symfony polyfill for the Mbstring extension
You might prefer searching for packages using the companion website https://packagist.org/, which also provides the readme package and the usage statistics, as shown in Figure 6-7.
Figure 6-7
Searching for symfony on packagist.org
Updating Packages
Composer can update your dependencies whenever you run the update command and then saves them to a file called composer.lock.
% composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
Note
The only time the composer.lock file is updated is when you run composer update or composer require. We will cover composer require in the next section.
Installing Packages
Composer install will only install the specific dependencies from the composer.lock file. If this file doesn’t exist, Composer will treat it as if you ran composer update.
The idea is, you can give the composer.json file to anyone and it will install the latest versions of all the dependencies. If you want the other person to install exactly the same dependencies as you have, you will need to provide them with the composer.json and composer.lock files.
Then they will be able to run Composer install from the project directory, which includes these Composer files. They can then run composer install to install all the required packages and their specific versions.
% composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
Adding Packages
When I used composer init, I specifically skipped defining the dependencies. Normally, you would know what dependencies you wanted to add and would add them at that time.
However, you can use composer require to add them now and Composer will guide you through the process. It will add the dependencies to the require section of the composer.json file.
% composer require
Search for a package:
You then need to enter the name of the package your project requires.
I will use symfony/finder, which is a package that helps you find files on your filesystem.
% composer require
Search for a package:
Enter the version constraint to require (or leave blank to use the latest version):
Using version ^5.1 for symfony/finder
Version Constraint
The version constraint is a way to express which version of a package you want to be installed. There are a few different ways to express the version constraint, depending on what you are trying to achieve.
It’s worth noting that most version numbers are made of three parts, which define the differences between the different versions of a package.
Major . Minor . Revision
This is called semantic versioning. If package maintainers follow this definition, we should be free to install the latest revision of a package without it breaking anything. We should also be able to install any newer minor versions without issue, but occasionally some issues happen. The Symfony components have a backward-compatibility promise, which means the minor updates will not break applications. When a package moves its version to a major release, you know that they have made a break.
Exact Constraint
At the time of this writing, the current version of symfony/finder is 5.1.3. If you entered 5.1.3, or any other version number, you are using the exact constraint. With an exact constraint, Composer will never be able to upgrade that package to a newer release.
Wildcard Constraint
The wildcard constraint uses the asterisk symbol to tell Composer that we do mind what value that part of the constraint is. We can use the asterisk to replace the revision part, 5.1.*, or the minor part, 5.*, of the version constraint. It’s entirely acceptable to replace the whole constraint with the wildcard symbol, if you always want to install the latest version of a package. It is worth noting that it is entirely possible to match an older version of a package with the wildcard constraint by mistake.
Caret Constraint
The caret constraint is similar to the wildcard constraint, but it solves the problem of accidentally allowing an older version of the package to be installable.
An example of the caret constraint is ^5.1.3. This will allow Composer to install any version that is 5.1.3 or greater, but not allow the next major release. That means ^5.1.3 will allow version up to, but not including, 6.0.0. With the caret constraint, you do not need to specify the revision part of the version. If it is omitted, it will be treated as 0. For example, ^5.1 will be treated as ^5.1.0.
Development Dependencies
The --dev argument allows you to add dependencies that your project needs for development, but that are not needed when the website goes live.
% composer require --dev
The process is exactly the same as before, but the dependencies are added to a require-dev section of the composer.json file.
After Dependencies Have Been Specified
When you have finished adding packages, just press Enter on the Searching for Package Prompt to end this process. Composer will write the composer.json and composer.lock files.
The other thing Composer will do is download the dependencies and store them in a directory called vendor, as shown here.
Search for a package:
./composer.json has been updated
Loading composer repositories with package information
Once you have a composer.json file, you should no longer use composer init, as it will overwrite your existing composer.json and composer.lock files.
Using Packages in PHP
Now that you have Composer set up for your project and have installed a dependency, you need a way to be able to use them. Using the dependencies inside a PHP file is as simple as requiring the autoload.php script that Composer sets up:
<?php
require "vendor/autoload.php";
Web Browser Projects
Now it’s a matter of understanding how the packages work and reading the package’s readme file to take full advantage of them.
Project 6-1: Person API
We’re going to create an API that returns data about a person. Normally, we would be getting this data from a database, but because we don’t have a database, we are going to make it all up. We are going to fake it.
You Will Need
Arrays
The Faker library
json_encode
PHP web server
Faker is a PHP library that generates fake data for you. The data that it generates is helpful when you’re building a database, because it allows you to populate it with semi-realistic data. Faker will generate fake phone numbers, addresses, and much more, but also allow you to specify different regions. Let’s get the initial setup.
% mkdir ~/person-api
% cd ~/person-api
% composer require fzaninotto/faker
The script in Listing 6-14 can help you get started.
<?php
require "vendor/autoload.php";
$faker = Faker\Factory::create();
$gender = 'male';
if ($faker->boolean) {
$gender = 'female';
}
$person = [
'title' => $faker->title($gender),
];
echo json_encode($person);
Listing 6-14
index.php Starting Script
When you are ready to test your code, you will need to fire up your web server. Hint: For more information on how to use Faker, look at the project documentation at https://github.com/fzaninotto/Faker.
Expected Output
I am using curl to see the output from the script, as we can format the response nicely, and this will work in Safari.
Note
127.0.0.1 is an IP address that you can use on your own computer but its content is not visible on another computer.
% curl -s http://127.0.0.1/ | jq
{
"title": "Prof.",
"firstName": "Jonas",
"lastName": "Hauck",
"address": "46290 Liza Rest\nEast Virginiaside, NE 83960-4858",
"country": "United States Minor Outlying Islands"
"jobTitle": "Speech-Language Pathologist",
"emailAddress": "uadams@example.org",
"phoneNumber": "+1 (383) 614-3188"
}
When you are ready to test your script, you will need to start the PHP web server from within your project.
% sudo php -S 127.0.0.1:80
PHP 7.3.11 Development Server started at Thu Jul 30 09:49:47 2020
Listening on http://127.0.0.1:80
Document root is /Users/danielplatt/person-api
Press Ctrl-C to quit.
Suggested Answer
Notice in Listing 6-15 that the array keys I chose mostly match up with Faker.
<?php
require "vendor/autoload.php";
$faker = Faker\Factory::create();
$gender = 'male';
if ($faker->boolean) {
$gender = 'female';
}
$person = [
'title' => $faker->title($gender),
'firstName' => $faker->firstName($gender),
'lastName' => $faker->lastName,
'address' => $faker->address,
'country' => $faker->country,
'jobTitle' => $faker->jobTitle,
'emailAddress' => $faker->email,
'phoneNumber' => $faker->phoneNumber,
];
echo json_encode($person);
Listing 6-15
index.php Suggested Answer
Project 6-2: People API
We’re going to extend our API that returned data about a person and return multiple people based on a query parameter.
You Will Need
Person API
A loop
Array appending
$_GET
Let’s get the initial setup, which is shown in Listing 6-16.
% cp -r ~/person-api ~/people-api
<?php
require "vendor/autoload.php";
$faker = Faker\Factory::create();
$people = [];
echo json_encode($people);
Listing 6-16
index.php Starting Script
When you are ready to test your script, you will need to start the PHP web server from within your project.
% sudo php -S 127.0.0.1:80
PHP 7.3.11 Development Server started at Thu Jul 30 09:49:47 2020
In my answer in Listing 6-17, I made the count parameter optional, by checking it has been set and putting that value in $count and otherwise defaulting $count to 10. I forced $_GET['count'] to be an int, to make sure the script works as expected if someone entered something other than a number. Then I use a loop to decrement the $count variable until it reaches zero.
<?php
require "vendor/autoload.php";
$people = [];
$faker = Faker\Factory::create();
$count = 10;
if (isset($_GET['count']) && $_GET['count'] > 0) {
$count = (int) $_GET['count'];
}
while ($count > 0) {
$gender = 'male';
if ($faker->boolean) {
$gender = 'female';
}
$person = [
'title' => $faker->title($gender),
'firstName' => $faker->firstName($gender),
'lastName' => $faker->lastName,
'address' => $faker->address,
'country' => $faker->country,
'jobTitle' => $faker->jobTitle,
'emailAddress' => $faker->safeEmail,
'phoneNumber' => $faker->phoneNumber,
];
$people[] = $person;
$count--;
}
echo json_encode($people);
Listing 6-17
index.php Suggested Answer
Command-Line Projects
The following two projects involve using the command line to perform various functions.
Project 6-3: Arguments
In this project, I want you to print out the arguments and a message if no arguments have been provided. This is the same project you did for shell programming.
You Will Need
if
$_SERVER['argv']
$_SERVER['argc']
A foreach loop
Expected Output
When no arguments have been provided, you need to output the usage instructions.
% php print-args.php
Usage: print-args.php message...
When arguments have been provided, I want to you iterate over them and print them out, along with their position.
% php print-args.php "hello" "world" 1 2 3
Arg 1: hello
Arg 2: world
Arg 3: 1
Arg 4: 2
Arg 5: 3
How would you solve this?
Suggested Answer
The first thing you need to do is check if $_SERVER['argc'] is less than 2 to print out the usage instructions (see Listing 6-18). The reason this is less than 2, rather than less than 1, is because the script name is counted as an argument. Once you know that there are arguments, you can use a foreach loop to iterate over them. Make sure you skip position zero, as that is the script filename.
foreach ($_SERVER['argv'] as $position => $message) {
if ($position === 0) {
continue;
}
echo "Arg $position: $message\n";
}
Listing 6-18
print-args.php Suggested Answer
Project 6-4: Higher or Lower
In this project, we will create a simple program where you have to guess the random number the computer has picked between 1 and 10. The script will give you hints as to whether the answer is higher or lower, but limit you to five guesses. Let’s create a file called higher-or-lower.php.
You Will Need
if
while
readline($prompt)
rand($min, $max)
We haven’t used the readline function before, but it is straightforward. The $prompt that you pass to it is just a string that will be printed to the screen. Then readline will wait for the user to type something in and press the Return key to return whatever can be captured into a variable.
$guess = readline("Guess: ");
Expected Output
I’m expecting your script to start by outputting how many guesses are left. The script should then output whether each guess is lower than, higher than, or matches the answer. Finally, when all the guesses are used up, the script should end by outputting the answer.
% ./higher-or-lower.php
Higher or Lower
===============
Guess the number I am thinking of between 1 and 10.
You have 5 guesses.
Guess: 1
Higher!
Guess: 2
Higher!
Guess: 3
Higher!
Guess: 4
Higher!
Guess: 5
Higher!
Sorry, you didn't guess it correctly.
The answer was 6.
Suggested Answer
Listing 6-19 starts by generating a random number for the answer. There is a for loop with the number of guesses built into it. For every iteration, or guess, the loop will check to see if the answer is higher or lower than the random number. If the answer is guessed correctly, the script will exit. If the loop eventually exits, the script will print the answer and finish.
<?php
$guess = 0;
$randomNumber = rand(1, 10);
echo "Higher or Lower
===============
Guess the number I am thinking of between 1 and 10.
You have 5 guesses.
";
for ($i=0; $i<5; $i++) {
$guess = (int) readline("Guess: ");
if ($guess < $randomNumber) {
echo "Higher!" . PHP_EOL;
} elseif ($guess > $randomNumber) {
echo "Lower!" . PHP_EOL;
} elseif ($guess === $randomNumber) {
echo "You guess correctly!" . PHP_EOL;
exit;
}
}
echo "Sorry, you didn't guess it correctly.
The answer was $randomNumber.
";
Listing 6-19
higher-or-lower.php Suggested Answer
If you want to take this project further, try to make the limits changeable, just like you did in in the shell scripting project in Chapter 5 (Project 5-4: Higher or Lower with Changeable Limits).
Summary
In this chapter we looked at PHP, another scripting language that is available within Terminal. It’s a popular general-purpose scripting language that is perfect for web development. To help illustrate its value, we reviewed a couple of web-based command-line projects. In the next chapter, we will explore how to keep track of changes in text files and scripts with version control.