5 New Features in PHP 7
So what makes PHP 7 so special? What does this mean for you as a developer?
We’ll take a look at the top 5 features here. If you’d like a deeper dive, check out my workshop, Introduction to PHP7, or my course, Build a Basic PHP Website.
1. SPEED!
The developers worked very hard to refactor the PHP codebase in order to reduce memory consumption and increase performance. And they certainly succeeded.
Benchmarks for PHP 7 consistently show speeds twice as fast as PHP 5.6 and many times even faster! Although these results are not guaranteed for your project, the benchmarks were tested against major projects, Drupal and WordPress, so these numbers don’t come from abstract performance tests.
With statistics that show 25% of the web being run on WordPress, this is a great thing for everyone.
2. Type Declarations
Type declarations simply means specifying which type of variable is being set instead of allowing PHP to set this automatically. PHP is considered to be a weak typed language. In essence, this means that PHP does not require you to declare data types. Variables still have data types associated with them but you can do radical things like adding a string to an integer without resulting in an error. Type declarations can help you define what should occur so that you get the expected results. This can also make your code easier to read. We’ll look at some specific examples shortly.
Since PHP 5, you can use type hinting to specify the expected data type of an argument in a function declaration, but only in the declaration. When you call the function, PHP will check whether or not the arguments are of the specified type. If not, the run-time will raise an error and execution will be halted. Besides only being used in function declarations, we were also limited to basically 2 types. A class name or an array.
Here’s an example:
function enroll(Student $student, array $classes) {foreach ($classes as $class) {echo "Enrolling " . $student->name . " in " . $class;}}
enroll("name",array("class 1", "class 2")); // Catchable fatal error: Argument 1 passed to enroll() must be an instance of Student, string given
enroll($student,"class"); // Catchable fatal error: Argument 2 passed to enroll() must be of the type array, string given
enroll($student, array("class 1", "class 2"));
If we were to create a function for enrolling students, we could require that the first argument be an object of the student class and the second argument to be an array of classes. If we tried to pass just the name instead of an object we would get a fatal error. If we were to pass a single class instead of an array, we would also get an error. We are required to pass a student object and an array.
function stringTest(string $string) {echo $string;}
stringTest("definitely a string");
If we were to try to check for a scalar variable such as a string, PHP 5 expects it to be an object of the class string, not the variable type string. This means you’ll get a Fatal error: Argument 1 passed to stringTest() must be an instance of string, string given.
Scalar Type Hints
With PHP 7 we now have added Scalar types. Specifically: int, float, string, and bool.
By adding scalar type hints and enabling strict requirements, it is hoped that more correct and self-documenting PHP programs can be written. It also gives you more control over your code and can make the code easier to read.
By default, scalar type-declarations are non-strict, which means they will attempt to change the original type to match the type specified by the type-declaration. In other words, if you pass a string that starts with a number into a function that requires a float, it will grab the number from the beginning and remove everything else. Passing a float into a function that requires an int will become int(1).
Strict Example
function getTotal(float $a, float $b) {return $a + $b;}
getTotal(2, "1 week");// int(2) changed to float(2.0) and string “1 week” changed to float(1.0) but you will get a “Notice: A non well formed numeric value encountered”//returns float(3)
getTotal(2.8, "3.2");// string "3.2" changed to float(3.2) no notice//returns float(6)
getTotal(2.5, 1);// int(1) changed to float(1.0)//returns float(3.5)
The getTotal function receives 2 floats and adds them together while it returns the sum.
Without strict types turned on, PHP attempts to cast, or change, these arguments to match the type specified in the function.
So when we call getTotal with non-strict types using an int of 2 and a string of “1 week”, PHP converts these to floats. The first argument would be changed to 2.0 and the second argument would be changed to 1.0. However, you will get a Notice: because this is not a well formed numeric value. It will then return a value of 3. Which would be completely wrong if we were trying to add days.
When we call getTotal with the float 2.8 and the string of “3.2”, PHP converts the string into the float 3.2. with no notice because it was a smooth conversion. It then returns a value of 6
When we call getTotal with non-strict types using the float 2.5 and the integer 1. The integer gets converted to the float 1.0 and the function returns 3.5
Strict Example
Additionally, PHP 7 gives us the opportunity to enable strict mode on a file by file basis. We do this by declare(strict_types=1); at the top of any given file. This MUST be the very first line, even before namespaces. Declaring strict typing will ensure that any function calls made in that file strictly adhere to the types specified.
Strict is determined by the file in which the call to a function is made, not the file in which the function is defined.
If a type-declaration mismatch occurs, a “Fatal Error” is thrown and we know that something is not functioning as desired, instead of allowing PHP to simply guess at what we want to happen, which can cause seemingly random and hard to diagnose issues. We’ll look at catching and handling errors in the next section. But for now, let’s look at an example using strict types turned on.
declare(strict_types=1);
function getTotal(float $a, float $b) {return $a + $b;}
getTotal(2, "1 week");// Fatal error: Uncaught TypeError: Argument 2 passed to getTotal() must be of the type float, string given
getTotal(2.8, "3.2");// Fatal error: Uncaught TypeError: Argument 2 passed to getTotal() must be of the type float, string given
getTotal(2.5, 1);// int(1) change to float(1.0)//returns float(3.5)
When the declare strict_type has been turned on, the first two calls that pass a string will produce a Fatal error: Uncaught TypeError: Argument 2 passed to getTotal() must be of the type float, string given.
The exception to strict typing with shown in the third call. If you pass an int as an argument that is looking for a float, PHP will perform what is called “widening”, by adding .0 to the end and the function returns 3.5
Return Type Declarations
PHP 7 also supports Return Type Declarations which support all the same types as arguments. To specify the return type, we add a colon and then the type right before the opening curly bracket.
function getTotal(float $a, float $b) : float {
If we specify the return type of float, it will work exactly like it has been in the previous 2 examples since the type being returned was already a float. Adding the return type allows you to to be sure your function returns what is expected as well as making it easy to see upfront how the function works.
Non-strict int
If we specify the return type as int without strict types set, everything will work the same as it did without a return type, the only difference is that it will force the return to be an int. In the third call the return value will truncate to 3 because the floating point will be dropped
function getTotal(float $a, float $b) : int {return $a + $b;}
getTotal(2, "1 week");// changes int(2) to float(2.0) & string(“1 more”) to float(1.0)// returns int(3);
getTotal(2.8, "3.2");// changes string "3.2" to float(3.2)// returns int(6)
getTotal(2.5, 1);// changes int(1) to float(1.0)// returns int(3)
Strict int
If we turn strict types on, we’ll get a Fatal error: Uncaught TypeError: Return value of getTotal() must be of the type integer, float returned. In this case we’ll need to specifically cast our return value as an int. This will then return the truncated value.
declare(strict_types=1);
function getTotal(float $a, float $b) : int {// return $a + $b;// Fatal error: Uncaught TypeError: Return value of getTotal() must be of the type integer, float returnedreturn (int)($a + $b); // truncate float like non-strict}
getTotal(2.5, 1); // changes int(1) to float(1.0) and returns int(3)
Why?
The new Type Declarations can make code easier to read and forces things to be used in the way they were intended. Some people prefer to use unit testing to check for intended use instead. Having automated tests for your code is highly recommended, but you can use both unit tests and Type Declarations. Either way, PHP does not require you to declare types but it can definitely make code easier to read. You can see right at the start of a function, what is required and what is returned.
3. Error Handling
The next feature we going to cover are the changes to Error Handling. Handling fatal errors in the past has been next to impossible in PHP. A fatal error would not invoke the error handler and would simply stop your script. On a production server, this usually means showing a blank white screen, which confuses the user and causes your credibility to drop. It can also cause issues with resources that were never closed properly and are still in use or even locked.
In PHP 7, an exception will be thrown when a fatal and recoverable error occurs, rather than just stopping the script. Fatal errors still exist for certain conditions, such as running out of memory, and still behave as before by immediately stopping the script. An uncaught exception will also continue to be a fatal error in PHP 7. This means if an exception thrown from an error that was fatal in PHP 5 goes uncaught, it will still be a fatal error in PHP 7.
I want to point out that other types of errors such as warnings and notices remain unchanged in PHP 7. Only fatal and recoverable errors throw exceptions.
In PHP 7, Error and Exception both implement the new Throwable class. What that means is that they basically work the same way. And also, you can now use Throwable in try/catch blocks to catch both Exception and Error objects. Remember that it is better practice to catch more specific exception classes and handle each accordingly. However, some situations warrant catching any exception (such as for logging or framework error handling). In PHP 7, these catch-all blocks should catch Throwable instead of Exception.
New Hierarchy
|- Exception implements Throwable|- …|- TypeError extends Error|- Error implements Throwable|- ArithmeticError extends Error|- ParseError extends Error|- AssertionError extends Error|- DivisionByZeroError extends ArithmeticError
The Throwable interface is implemented by both Exception and Error. Under Error, we now have some more specific error. TypeError, ParseError, A couple arithmetic errors and an AssertionError.
Throwable Interface
If Throwable was defined in PHP 7 code, it would look like this
interface Throwable{public function getMessage(): string;public function getCode(): int;public function getFile(): string;public function getLine(): int;public function getTrace(): array;public function getTraceAsString(): string;public function getPrevious(): Throwable;public function __toString(): string;}
If you’ve worked with Exceptions at all, this interface should look familiar. Throwable specifies methods nearly identical to those of Exception. The only difference is that Throwable::getPrevious() can return any instance of Throwable instead of just an Exception.
Here’s what a simple catch-all block looks like:
try {// Code that may throw an Exception or Error.} catch (Throwable $t) {// Executed only in PHP 7, will not match in PHP 5} catch (Exception $e) {// Executed only in PHP 5, will not be reached in PHP 7}
To catch any exception in PHP 5.x and 7 with the same code, you would need to add a catch block for Exception AFTER catching Throwable first. Once PHP 5.x support is no longer needed, the block catching Exception can be removed.
Virtually all errors in PHP 5 that were fatal, now throw instances of Error in PHP 7.
Type Errors
A TypeError instance is thrown when a function argument or return value does not match a type declaration. In this function, we’ve specified that the argument should be an int, but we’re passing in strings that can’t even be converted to ints. So the code is going to throw a TypeError.
function add(int $left, int $right) {return $left + $right;}
try {echo add('left','right');} catch (\TypeError $e) {// Log error and end gracefullyecho $e->getMessage(), "\n";// Argument 1 passed to add() must be of the type integer, string given}
This could be used for adding shipping and handling to a shopping cart. If we passed a string with the shipping carrier name, instead of the shipping cost, our final total would be wrong and we would chance losing money on the sale.
Parse Errors
A ParseError is thrown when an included/required file or eval()’d code contains a syntax error. In the first try we’ll get a ParseError because we called the undefined function var_dup instead of var_dump. In the second try, we’ll get a ParseError because the required file has a syntax error.
try {$result = eval("var_dup(1);");} catch (\Error $e) {echo $e->getMessage(), "\n";//Call to undefined function var_dup()}
try {require 'file-with-parse-error.php';} catch (ParseError $e) {echo $e->getMessage(), "\n";//syntax error, unexpected end of file, expecting ',' or ';'}
Let’s say we check if a user is logged in, and if so, we want to include a file that contains a set of navigation links, or a special offer. If there is an issue with that include file, catching the ParseError will allow us to notify someone that that file needs to be fixed. Without catching the ParseError, the user may not even know they are missing something.
4. New Operators
Spaceship Operator
PHP 7 also brings us some new operators. The first one we’re going to explore is the spaceship operator. With a name like that, who doesn’t want to use it? The spaceship operator, or Combined Comparison Operator, is a nice addition to the language, complementing the greater-than and less-than operators.
Spaceship Operator< = >
$compare = 2 <=> 12 < 1? return -12 = 1? return 02 > 1? return 1
The spaceship operator is put together using three individual operators, less than, equal, and greater than. Essentially what it does is check the each operator individually. First, less than. If the value on the left is less than the value on the right, the spaceship operator will return -1. If not, it will move on to test if the value on the left is EQUAL to the value on the right. If so, it will return 0. If not it will move on to the final test. If the value on the left is GREATER THAN the value on the right. Which, if the other 2 haven’t passed, this one must be true. And it will return 1.
The most common usage for this operator is in sorting.
Null Coalesce Operator
Another new operator, the Null Coalesce Operator, is effectively the fabled if-set-or. It will return the left operand if it is not NULL, otherwise it will return the right. The important thing is that it will not raise a notice if the left operand is a non-existent variable.
$name = $firstName ?? "Guest";
For example, name equals the variable firstName, double question marks, the string “Guest”.
If the variable firstName is set and is not null, it will assign that value to the variable name. Or else it will assign “Guest” the the variable name.
Before PHP 7, you could write something like
if (!empty($firstName)) $name = $firstName;else $name = "Guest";
What makes this even more powerful, is that you can stack these! This operation will check each item from left to right and when if finds one that is not null it will use that value.
$name = $firstName ?? $username ?? $placeholder ?? “Guest”;
This operator looks explicitly for null or does not exist. It will pick up an empty string.
5. Easy User-land CSPRNG
What is Easy User-land CSPRNG?
User-land refers to an application space that is external to the kernel and is protected by privilege separation, API for an easy to use and reliable Cryptographically Secure PseudoRandom Number Generator in PHP.
Essentially secure way of generating random data. There are random number generators in PHP, rand() for instance, but none of the options in version 5 are very secure. In PHP 7, they put together a system interface to the operating system’s random number generator. Because we can now use the operating system’s random number generator, if that gets hacked we have bigger problems. It probably means your entire system is compromised and there is a flaw in the operating system itself.
Secure random numbers are especially useful when generating random passwords or password salt.
What does this look like for you as a developer? You now have 2 new functions to use: random_int and random_bytes.
Random Bytes
When using random_bytes, you supply a single argument, length, which is the length of the random string that should be returned in bytes. random_bytes then returns a string containing the requested number of cryptographically secure random bytes. If we combine this with something like bin2hex, we can get the hexadecimal representation.
$bytes = random_bytes(5); // length in bytes
var_dump(bin2hex($bytes));
// output similar to: string(10) "385e33f741"
These are bytes not integers. If you are looking to return a random number, or integer, you should use the random_int function.
Random Int
When using random_int you supply 2 arguments, min and max. This is the minimum and maximum numbers you want to use.
For example:
random_int(1,20);
Would return a random number between 1 and 20, including the possibility of 1 and 20.
*If you are using the rand function for anything even remotely secure, you’ll want to change the rand function to random_int.
Conclusion
There are quite a few other features added in PHP 7, like unicode support for emoji and international characters.
echo "\u{1F60D}"; // outputs
But this should give you a taste of what’s changing in PHP.
Another big area that could cause trouble, are features that have been removed. This should really only be an issue if you’re working with an older code base, because the features that have been removed are primarily ones that have been deprecated for a long time. If you’ve been putting off making these necessary changes, the huge advantage in speed with PHP 7 should help convince you, or management, to take the time needed to update your code.
Comments
Post a Comment