Part 1 - New Features
Perhaps the most exciting part of PHP 7 are the new features! This part of the book covers these features in detail, including examples of useful applications.
Chapter 1: Scalar Type Hints
PHP 5 introduced the ability to require function parameters to be of a certain type. This provides a safeguard against invalid uses, like passing a UNIX timestamp to a method which expects a DateTime object. It also makes it clear to other developers how the function should be used. For example, compare the following method signatures with and without type hints:
// No type hint:
function getNextWeekday($date) { /*...*/ }
// Class type hint (PHP 5):
function getNextWeekday(DateTime $date) { /*...*/ }
These hints were initially limited to just classes and interfaces, but was soon expanded to allow array and callable types too. PHP 7 extends this further by allowing scalar types like int, float, string and bool:
// Scalar type hint (PHP 7):
function getNextWeekday(int $date) { /*...*/ }
Type Checking Modes
PHP’s flexible type system is one of its most-useful features, allowing numeric strings to be used as integers and vice-versa. This tradition is continued in PHP 7 but now you have the option of enabling strict type enforcement like you’d see in other languages (such as Java or C#). This setting can be enabled using the declare construct.
Weak (“Coercive”) Type Checking
“Coercive” mode is the default type checking mode in PHP 7. This is identical to how previous versions of PHP handled scalar type hints for built-in functions. For example, take a look at the method signature for the floor function:
float floor ( float $value )
When you pass in numeric strings or integers PHP auto-converts them to a float automatically. This behavior has simply been extended to userland functions as well.
Here’s a table showing which scalar types are accepted in “Coercive” mode based on the declared type:
| Type declaration | int | float | string | bool | object |
|---|---|---|---|---|---|
| int | yes | yes* | yes† | yes | no |
| float | yes | yes | yes† | yes | no |
| string | yes | yes | yes | yes | yes‡ |
| bool | yes | yes | yes | yes | no |
* Only non-NaN floats between PHP_INT_MIN and PHP_INT_MAX accepted.
† If it’s a numeric string
‡ Only if object has a __toString() method
Strong (“Strict”) Type Checking
PHP 7 introduces a new “strict” mode which is enabled by placing declare(strict_types=1); at the top of your script like so:
<?php
declare(strict_types=1);
function welcome(string $name) {
echo 'Hello ' . $name;
}
welcome('World'); // Prints: Hello World
This declaration enables strict mode for all uses in the entire file, including built-in PHP function calls and return values (see next chapter). It does not affect any included files nor any other files which include it.
Strict mode essentially requires you to pass in exact types declared. If you don’t, a TypeError will be thrown. For example, in strict mode, you cannot use numeric strings when a float or int is expected (like you can in weak/coercive mode):
<?php
declare(strict_types=1);
function welcome(string $name) {
echo 'Hello ' . $name;
}
welcome(3);
// Fatal error: Uncaught TypeError: Argument 1 passed to welcome() must be of th\
e type string, integer given
There’s one exception to this rule though, which is that float declarations can accept int values:
<?php
declare(strict_types=1);
function add(float $a, float $b): float {
return $a + $b;
}
add(1, 2); // float(3)
| Type declaration | int | float | string | bool | object |
|---|---|---|---|---|---|
| int | yes | no | no | no | no |
| float | yes* | yes | no | no | no |
| string | no | no | yes | no | no |
| bool | no | no | no | yes | no |
* Allowed due to widening primitive conversion
Mixing Modes
Because the directive is set per-file, it’s entirely possible to mix modes in your application. For example, your strictly-checked app could use a weakly-checked library (or vice versa) without any issues or complications.
Backwards Compatibility
You may no longer create classes named int, float, string or bool as these would conflict with the new scalar type hints.
Further Reading
Chapter 3: Combined Comparison (Spaceship) Operator
PHP 7 introduces a new three-way comparison operator <=> (T_SPACESHIP) which takes two expressions: (expr) <=> (expr). It compares both sides of the expression and, depending on the result, returns one of three values:
| 0 | If both expressions are equal |
| 1 | If the left is greater |
| -1 | If the right is greater |
You may be familiar with this if you’ve worked with existing comparison functions like strcmp before.
It has the same precedence as the equality operator (==, ===, !=, !==) and behaves identically to the existing comparison operators (<, <=, ==, >=, >).
Comparing Values
You can compare everything from scalar values (like ints and floats) to arrays and even objects too. Here are some examples from the relevant RFC:
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
echo "a" <=> "aa"; // -1
echo "zz" <=> "aa"; // 1
// Arrays
echo [] <=> []; // 0
echo [1, 2, 3] <=> [1, 2, 3]; // 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1
// Objects
$a = (object) ["a" => "b"];
$b = (object) ["a" => "b"];
echo $a <=> $b; // 0
$a = (object) ["a" => "b"];
$b = (object) ["a" => "c"];
echo $a <=> $b; // -1
$a = (object) ["a" => "c"];
$b = (object) ["a" => "b"];
echo $a <=> $b; // 1
// only values are compared
$a = (object) ["a" => "b"];
$b = (object) ["b" => "b"];
echo $a <=> $b; // 0
Sorting
Perhaps the best application of this operator is to simplify sorting, as functions like usort expect you to perform a comparison and return -1, 0, or 1 accordingly:
This simplification is especially apparent when comparing objects by some property value:
class Spaceship {
public $name;
public $maxSpeed;
public function __construct($name, $maxSpeed) {
$this->name = $name;
$this->maxSpeed = $maxSpeed;
}
}
$spaceships = [
new Spaceship('Rebel Transport', 20),
new Spaceship('Millenium Falcon', 80),
new Spaceship('X-Wing Starfighter', 80),
new Spaceship('TIE Bomber', 60),
new Spaceship('TIE Fighter', 100),
new Spaceship('Imperial Star Destroyer', 60),
];
// Sort the spaceships by name (in ascending order)
usort($spaceships, function ($ship1, $ship2) {
return $ship1->name <=> $ship2->name;
});
echo $spaceships[0]->name; // "Imperial Star Destroyer"
// Sort the spaceships by speed (in descending order)
// Notice how we switch the position of $ship1 and $ship2
usort($spaceships, function ($ship1, $ship2) {
return $ship2->maxSpeed <=> $ship1->maxSpeed;
});
echo $spaceships[0]->name; // "TIE Fighter"
Without the comparison operator, these functions would be much more complex:
usort($spaceships, function ($ship1, $ship2) {
if ($ship1->maxSpeed == $ship2->maxSpeed) {
return 0;
} elseif ($ship1->maxSpeed < $ship2->maxSpeed) {
return 1;
} else {
return -1;
}
});
Sorting by multiple values
You can take advantage of the array comparison behavior to easily sort by multiple fields:
// Sort by speed (asc), then by name (asc)
usort($spaceships, function ($ship1, $ship2) {
return [$ship1->maxSpeed, $ship1->name] <=> [$ship2->maxSpeed, $ship2->name];
});
foreach ($spaceships as $ship) {
printf("%3d is the max speed of a(n) %s\n", $ship->maxSpeed, $ship->name);
}
// Outputs:
// 20 is the max speed of a(n) Rebel Transport
// 60 is the max speed of a(n) Imperial Star Destroyer
// 60 is the max speed of a(n) TIE Bomber
// 80 is the max speed of a(n) Millenium Falcon
// 80 is the max speed of a(n) X-Wing Starfighter
// 100 is the max speed of a(n) TIE Fighter
Further Reading
Chapter 5: Unicode Codepoint Escape Syntax
PHP’s lack of native Unicode support can make things difficult when coding for the web. While libraries like iconv and mbstring have simplified working with strings, there were no simple mechanisms available to create Unicode characters or strings without converting them from an HTML or JSON representation:
$char = html_entity_decode('☃', 0, 'UTF-8');
$char = mb_convert_encoding('☃', 'UTF-8', 'HTML-ENTITIES');
$char = json_decode('"\\u2603"');
PHP 7 finally introduces native support for Unicode character escape sequences within strings, just like you’d see in Ruby or ECMAScript 6:
$char = "\u{2603}";
This makes it much easier (and quicker) to embed Unicode characters, especially ones that aren’t easily typed. These can be used in strings alongside other characters too. For example, here’s the U+202E RIGHT-TO-LEFT OVERRIDE character being used to display the string in reverse:
echo "\u{202E}This is backwards"; // displays: sdrawkcab si sihT
You can omit leading 0s if you’d like:
echo "\u{58}"; // "X"
echo "\u{0058}"; // "X"
Why the {}s?
Some other languages (C/C++/Java) use a format without the {} characters: \uXXXX. Unfortunately this limits their use to the Basic Multilingual Plane (U+0000 to U+FFFF). However, Unicode supports other characters beyond 16 bits.
For example, if we wanted to represent the U+1F427 PENGUIN emoji, our escape sequence would look something like this: \u1F427. Most languages would intepret this as U+1F42 GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA plus a 7, which is not what we want. In these languages, you’d have to encode it using two 16-bit sequences like this: \uD83D\uDC27. This isn’t very clear though.
Wrapping with {} characters allows us to easily go beyond that 16-bit limitation without sacrficing clarity: \u{1F427}
Limitations
This feature follows the behavior of all other escape sequences in PHP - they can only be used within double-quoted strings and heredocs:
$foo = "\u{2109}\u{2134}\u{2134}";
// ℉ℴℴ
$bar = <<<EOT
\u{212C}\u{212B}\u{211D}
EOT;
// ℬÅℝ
And like other sequences such as \t, they will not be expanded when they occur in single-quoted strings or nowdocs:
$foo = '\u{2109}\u{2134}\u{2134}';
// \u{2109}\u{2134}\u{2134}
$bar = <<<'EOT'
\u{212C}\u{212B}\u{211D}
EOT;
// \u{212C}\u{212B}\u{211D}
Backwards Compatibility
Double-quoted strings and heredocs containing \u{ followed by an invalid sequence will now result in an error. This can be avoided by escaping the leading backslash with another backslash (\\u{).