TL;DR: Many PHP applications are still running on PHP 5.x, not ready to take full advantage of the awesome features that PHP 7 offers. A lot of developers have not made the switch because of certain fears of compatibility issues, migration challenges and the strange awkward feeling that migrating will take away a big chunk of their time. In the first part of this tutorial we learned how to set up a PHP 7 development environment. In the second part of this tutorial, we discussed extensively about all the new features PHP 7 offers and the language constructs and features that have been either removed or deprecated. This time, we'll show you how you can leverage all the new PHP 7 features when migrating and also the tools that will help to make the process painless!
You need to be aware that for the most part, PHP 5.x code can run on PHP 7. In PHP 7, there are some backwards incompatible changes, so applications built with PHP 5.x that use functions and language constructs that have been removed or have the internal implementation changed drastically will spit out errors while trying to run on PHP 7.
Tools to Aid Migration
One of the most frustating part of our jobs as software developers is having to work on large old codebases. In a situation where you are tasked with migrating a large PHP 5.x application that has probably been in existence for about 10 years, how would you go about it?
The easiest and most obvious way of migrating is to initially clone the app on your local machine, install PHP 7 and run the app. You can walk through the errors and deperaction warnings shown in the terminal, and manually fix them step-by-step by incorporating PHP 7 features. This can be very challenging and time consuming. Why can't we automate this process?
Currently there is no tool out there that performs a 100% automatic conversion of your PHP 5.x codebase to PHP 7, but these tools below will help in making your migration painless.
PHP 7 MAR
php7mar is a command-line tool that generates reports on PHP 5.x codebase based on PHP 7 compatibility. The reports contain line numbers, issues noted, and suggested fixes along with documentation links.
Note: The tool does not fix code. It only gives you reports about all the PHP files in your codebase. Happy fixing!
PHP 7 Compatibility Checker
php7cc is a command-line tool designed to make migration from PHP 5.3 - 5.6 to PHP 7 really easy. php7cc reports:
- Errors: Fatal, Syntax, Notice. These are highlighted in red.
- Warnings: These are highlighted in yellow.
Phan
phan is a static analyzer for PHP that attempts to prove incorrectness rather than correctness. Phan looks for common issues and verifies type compatibility on various operations when type information is available or can be deduced. Phan checks for lots of things including PHP7/PHP5 backward compatibility.
phpto7aid
phpto7aid is a tool that is used to identify PHP 5 code that will not work in PHP 7. It tries to aid you as much as possible in resolving these issues, by either providing the exact solution or giving hints on how to solve the issue.
PhpStorm PHP 7 Compatibility Inspection
PhpStorm is a very smart PHP IDE, developed by Jetbrains.
Source: Jetbrains.com
PhpStorm 10 comes with a PHP 7 Compatibility Inspection tool that can show you exactly what code is going to cause errors if you are running PHP7.
Source: Jetbrains.com
The image below shows a typical example of an application that has classes with names that are reserved in PHP 7. Selecting Run Inspection By Name option from the Code menu, and then selecting the PHP 7 Compatibility section will give you results like this one below:
Source: Jetbrains.com
Building a PHP5 App
We will build the first simple PHP 5 app very quickly. This is the scope of the app:
- A user will be able to register on the app.
- A user will be able to log into the app.
- A user will be assigned a random Star Wars Code Name.
- A user will be able to log out of the app.
Building this app will require us to set up a database to store the users, write our registration and login code and manage the users session. Now, we won't employ the use of any framework because we don't want any form of overhead. Ordinarily, building this app would take a lot of time and setup but there is a service we can use to eliminate the hassle. Oh, yeah, Auth0 to the rescue!
Auth0 provides the simplest and easiest to use User interface tools to help administrators manage user identities including password resets, creating and provisioning, blocking and deleting users.
Create and Configure Auth0 Client
First thing we'll need to do is sign up for a free Auth0 account and configure a new client.
Auth0 offers a generous free tier to get started with modern authentication.
Now head over to clients tab and create a new one choosing 'Regular web Application' as the client type. Let's name it as something like 'Basic PHP WebApp'.
Now that we have our client created, we need to take note of three properties:
Domain
, Client ID
and Client Secret
. All of them can be found on the Settings
tab of the client that we've just created.The last configuration that we need to do, before updating our code, is to add
http://localhost:3000
as an Allowed Callback URLs
on our Auth0 client.Build the App
Create a
composer.json
file in a new directory and add this to it like so:{ "name": "basic php webapp", "description": "Basic sample for securing a WebApp with Auth0", "require": { "vlucas/phpdotenv": "2.3.0", "auth0/auth0-php": "~4.0" }, "license": "MIT" }
composer.json
All we need is the
phpdotenv
package for reading environment variables and the auth0-php
package that makes it easy to use the Auth0 service.Create a
public
folder inside the directory add the file app.css
in it.body { font-family: "proxima-nova", sans-serif; text-align: center; font-size: 300%; font-weight: 100; } input[type=checkbox], input[type=radio] { position: absolute; opacity: 0; } input[type=checkbox] + label, input[type=radio] + label { display: inline-block; } input[type=checkbox] + label:before, input[type=radio] + label:before { content: ""; display: inline-block; vertical-align: -0.2em; width: 1em; height: 1em; border: 0.15em solid #0074d9; border-radius: 0.2em; margin-right: 0.3em; background-color: white; } input[type=radio] + label:before { border-radius: 50%; } input[type=radio]:checked + label:before, input[type=checkbox]:checked + label:before { background-color: #0074d9; box-shadow: inset 0 0 0 0.15em white; } input[type=radio]:focus + label:before, input[type=checkbox]:focus + label:before { outline: 0; } .btn { font-size: 140%; text-transform: uppercase; letter-spacing: 1px; border: 0; background-color: #16214D; color: white; } .btn:hover { background-color: #44C7F4; } .btn:focus { outline: none !important; } .btn.btn-lg { padding: 20px 30px; } .btn:disabled { background-color: #333; color: #666; } h1, h2, h3 { font-weight: 100; } #logo img { width: 300px; margin-bottom: 60px; } .home-description { font-weight: 100; margin: 100px 0; } h2 { margin-top: 30px; margin-bottom: 40px; font-size: 200%; } label { font-size: 100%; font-weight: 300; } .btn-next { margin-top: 30px; } .answer { width: 70%; margin: auto; text-align: left; padding-left: 10%; margin-bottom: 20px; } .login-page .login-box { padding: 5px 0; }
app.css
Go ahead and create a
.htaccess
file inside the directory like so:RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php [L]
Create a
.env
file. This file will contain our Auth0 credentials.AUTH0_DOMAIN='blahabababababa.auth0.com' AUTH0_CLIENT_ID='xxxxxxxxx' AUTH0_CLIENT_SECRET='xxxxxxxxx' AUTH0_CALLBACK_URL='http://localhost:3000'
Note: Replace these values with the
client_id
, client_secret
and domain
from your Auth0 dashboard.Add the value of
callback_url
to the Allowed Callback URLs in your Settings on the dashboard.Auth0 dashboard: Allowed Callback Urls
Also, do not forget to add the same value to the Allowed Origins(CORS) in your Settings on the dashboard.
We need a file to invoke the
dotenv
library and load the values that we have deposited in the .env
file. Create a new file, dotenv-loader.php like so:<?php // Read .env try { $dotenv = new Dotenv\Dotenv(__DIR__); $dotenv->load(); } catch(InvalidArgumentException $ex) { // Ignore if no dotenv }
dotenv-loader.php
Finally, let's create the
index.php
file where all our app logic will reside. Like I mentioned earlier, it's just a basic app so don't be worried about separation of concerns.This is how the file should look like:
<?php // Require composer autoloader require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/dotenv-loader.php'; use Auth0\SDK\API\Authentication; $domain = getenv('AUTH0_DOMAIN'); $client_id = getenv('AUTH0_CLIENT_ID'); $client_secret = getenv('AUTH0_CLIENT_SECRET'); $redirect_uri = getenv('AUTH0_CALLBACK_URL'); $auth0 = new Authentication($domain, $client_id); $auth0Oauth = $auth0->get_oauth_client($client_secret, $redirect_uri, [ 'persist_id_token' => true, 'persist_refresh_token' => true, ]); $authorizationURL = sprintf( "https://%s/authorize?client_id=%s&response_type=code&redirect_uri=%s", $domain, $client_id, $redirect_uri); $starWarsNames = ['Darth Vader', 'Ahsoka Tano', 'Kylo Ren', 'Obi-Wan Kenobi', 'R2-D2', 'Snoke']; $userInfo = $auth0Oauth->getUser(); if (isset($_REQUEST['logout'])) { $auth0Oauth->logout(); session_destroy(); header("Location: /"); } ?> <html> <head> <script src="http://code.jquery.com/jquery-3.0.0.min.js" type="text/javascript"></script> <script type="text/javascript" src="//use.typekit.net/iws6ohy.js"></script> <script type="text/javascript">try{Typekit.load();}catch(e){}</script> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"> <!-- font awesome from BootstrapCDN --> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet"> <link href="public/app.css" rel="stylesheet"> </head> <body class="home"> <div class="container"> <div class="login-page clearfix"> <?php if(!$userInfo): ?> <div class="login-box auth0-box before"> <img src="https://images.ctfassets.net/23aumh6u8s0i/3HNmqlr6oTNkMBE2dhodOU/0866266d4f67c2dcca42fd60f5d019aa/star_warsapp" /> <p>Heard you don't want to migrate to PHP 7? Dare us!</p> <a href="<?php echo $authorizationURL ?>" class="btn btn-primary btn-login">SignIn</a> </div> <?php else: ?> <div class="logged-in-box auth0-box logged-in"> <h1 id="logo">Star Wars Welcomes You to the Family!</h1> <img class="avatar" width="200" src="<?php echo $userInfo['picture'] ?/>"/> <h2>Welcome <span class="nickname"><?php echo $userInfo['nickname'] ?></span></h2> <h2> Assigned Codename : <b><?php echo $starWarsNames[rand(0, 6)]; ?></b> </h2> <a class="btn btn-primary btn-lg" href="?logout">Logout</a> </div> <?php endif ?> </div> </div> </body> </html>
Relax, let's analyze the code together.
// Require composer autoloader require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/dotenv-loader.php';
This is where we require the dotenv loader and composer autoloader. The autoloader makes it possible for us to import any class from the PHP packages installed in the app.
use Auth0\SDK\API\Authentication; $domain = getenv('AUTH0_DOMAIN'); $client_id = getenv('AUTH0_CLIENT_ID'); $client_secret = getenv('AUTH0_CLIENT_SECRET'); $redirect_uri = getenv('AUTH0_CALLBACK_URL'); $auth0 = new Authentication($domain, $client_id); $auth0Oauth = $auth0->get_oauth_client($client_secret, $redirect_uri, [ 'persist_id_token' => true, 'persist_refresh_token' => true, ]); $authorizationURL = sprintf( "https://%s/authorize?client_id=%s&response_type=code&redirect_uri=%s", $domain, $client_id, $redirect_uri); $starWarsNames = ['Darth Vader', 'Ahsoka Tano', 'Kylo Ren', 'Obi-Wan Kenobi', 'R2-D2', 'Snoke']; $userInfo = $auth0Oauth->getUser();
Auth0\SDK\API\Authentication
is the Auth0 authentication class. It has the methods to retrieve a user's profile when logged in. $domain
, $client_id
, $client_secret
, $redirect_uri
are variables that will house the values gotten from the .env
file with the aid of the getenv
method.Then, we moved on to instantiating the
Authentication
class.The
$auth0->get_oauth_client()
method by default stores user information in the PHP session, and we also instructed it to save the refresh_token
and id_token
.$authorizationURL
is the authorization URL.$starWarsNames
array contains some characters from Star Wars. Later in the code, a user will be assigned a random code name from this array.$auth0Oauth->getUser()
retrieves the user information.if (isset($_REQUEST['logout'])) { $auth0Oauth->logout(); session_destroy(); header("Location: /"); }
This checks if the user submitted a request to log out, clears the session and redirects the user back to the homepage.
<script src="http://code.jquery.com/jquery-3.0.0.min.js" type="text/javascript"></script>
We are making use of Auth0's Centralized Login Page, and we also using jQuery to call the methods and handle button click event.
Auth0's Centralized Login Page is the most secure way to easily authenticate users for your applications.
<!-- font awesome from BootstrapCDN --> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet">
Pulled in bootstrap and font-awesome for beautification.
<div class="container"> <div class="login-page clearfix"> <?php if(!$userInfo): ?> <div class="login-box auth0-box before"> <img src="https://images.ctfassets.net/23aumh6u8s0i/3HNmqlr6oTNkMBE2dhodOU/0866266d4f67c2dcca42fd60f5d019aa/star_warsapp" /> <p>Heard you don't want to migrate to PHP 7? Dare us!</p> <a href="<?php echo $authorizationURL ?>" class="btn btn-primary btn-login">SignIn</a> </div> <?php else: ?> <div class="logged-in-box auth0-box logged-in"> <h1 id="logo">Star Wars Welcomes You to the Family!</h1> <img class="avatar" width="200" src="<?php echo $userInfo['picture'] ?/>"/> <h2>Welcome <span class="nickname"><?php echo $userInfo['nickname'] ?></span></h2> <h2> Assigned Codename : <b><?php echo $starWarsNames[rand(0, 6)]; ?></b> </h2> <a class="btn btn-primary btn-lg" href="?logout">Logout</a> </div> <?php endif ?> </div>
In the code above, if the
$userInfo
is not set, then it means the user has not logged in yet, so we display the signin button. If the user has signed in, then we grab the user's info and display it along with the logout
button.Important Security Note: If you have an API for your application, the
should not be used to secure the API; instead use an id_token
access_token
with the appropriate configuration. You can read about how to implement API authentication with Auth0 with implicit grant.Run The App
Go to your terminal and run
composer install
to install the dependencies. Next, run your PHP 5.x server. If your PHP server is accessible from the terminal, then you can run it via php -S localhost:3000
.Open your browser and test the app. The index page should look like this:
Index Page
Now, signup & signin.
Sign In
When you are logged in, you should be assigned a Star Wars codename like so:
Logged In
Our app is now running successfully on a PHP 5.x server. You can grab the source code from Github to ensure that everything works as expected.
Migrating our PHP5 App to PHP7
We are currently running a PHP 5.x app. Let's migrate it to PHP 7. The good thing is that most times you might not have to change anything in the codebase. Let's see if that holds true for this app.
Upgrade your server to at least PHP 7.0.0 and run this app again.
PHP 7 Server running
App running on PHP 7 without any errors
Awesome, now our first app is running on PHP 7 successfully!
Work with Second App
The second PHP app we will go through is an API. It is a simple Chuck Norris API. It has been built already with PHP 5 in mind.
Clone it from Github and run
composer install
to install all the dependencies. Then run the app on a PHP 5.x server.Open up Postman and test the API like so:
Run
http://localhost:3000/jokes/categories
like so:API showing categories
Run
http://localhost:3000/jokes/random
like so:API showing random jokes
The app is working fine, no errors!
Use PHP 7 features in Second App
Let's refactor this app and integrate some PHP 7 features.
This is the directory structure of our API app at the moment:
----basic-api | ----src | | | ----Main.php | ----vendor | ----.gitignore | ----.htaccess | ----composer.json | ----composer.lock | ----index.php | ----README.md
This is how our
Main.php
file looks like right now:<?php namespace App; use Exception; class Main { public function getCategories() { return $this->getCategoryData(); } private function getCategoryData() { return [ "explicit", "dev", "movie", "food", "celebrity", "science", "political", "sport", "religion", "animal", "music", "history", "travel", "career", "money", "fashion" ]; } public function getRandomJokes($randomNumber) { if( !is_integer($randomNumber)) { throw new Exception("The random number should be an integer. Please try again."); } $jokes = [ "Jon Skeet’s code doesn’t follow a coding convention. It is the coding convention.", "Jon Skeet can divide by Zero.", "Jon Skeet points to null, null quakes in fear.", "Jon Skeet is the traveling salesman. Only he knows the shortest route.", "When Jon pushes a value onto a stack, it stays pushed.", "Drivers think twice before they dare interrupt Jon’s code.", "Jon Skeet does not sleep…. He waits.", "Jon Skeet can stop an infinite loop just by thinking about it.", "Jon Skeet uses Visual Studio to burn CDs.", "Jon Skeet has the key to Open Source. He just doesn’t want to close it." ]; return $jokes[$randomNumber]; } }
Let's start by adding PHP 7 return type declarations to the methods in this class like so:
<?php namespace App; class Main { public function getCategories(): array { return $this->getCategoryData(); } private function getCategoryData(): array { return [ "explicit", "dev", "movie", "food", "celebrity", "science", "political", "sport", "religion", "animal", "music", "history", "travel", "career", "money", "fashion" ]; } public function getRandomJokes($randomNumber): string { if( !is_integer($randomNumber)) { throw new Exception("The random number should be an integer. Please try again."); } $jokes = [ "Jon Skeet’s code doesn’t follow a coding convention. It is the coding convention.", "Jon Skeet can divide by Zero.", "Jon Skeet points to null, null quakes in fear.", "Jon Skeet is the traveling salesman. Only he knows the shortest route.", "When Jon pushes a value onto a stack, it stays pushed.", "Drivers think twice before they dare interrupt Jon’s code.", "Jon Skeet does not sleep…. He waits.", "Jon Skeet can stop an infinite loop just by thinking about it.", "Jon Skeet uses Visual Studio to burn CDs.", "Jon Skeet has the key to Open Source. He just doesn’t want to close it." ]; return $jokes[$randomNumber]; } }
PHP 7 Return Type Declarations added in Main.php
Another PHP 7 feature we can add is function parameter typehinting. We have a method,
getRandomJokes($randomNumber)
that accepts a $randomNumber
which is an integer.Let's refactor that method,
getRandomJokes()
. We'll eliminate the if
condition and just typehint the $randomNumber
parameter like so:public function getRandomJokes(int $randomNumber): string { $jokes = [ "Jon Skeet’s code doesn’t follow a coding convention. It is the coding convention.", "Jon Skeet can divide by Zero.", "Jon Skeet points to null, null quakes in fear.", "Jon Skeet is the traveling salesman. Only he knows the shortest route.", "When Jon pushes a value onto a stack, it stays pushed.", "Drivers think twice before they dare interrupt Jon’s code.", "Jon Skeet does not sleep…. He waits.", "Jon Skeet can stop an infinite loop just by thinking about it.", "Jon Skeet uses Visual Studio to burn CDs.", "Jon Skeet has the key to Open Source. He just doesn’t want to close it." ]; return $jokes[$randomNumber]; }
Now if you try to pass in a value asides an integer like so:
$router->get('/jokes/random', function() use ($app){ echo json_encode($app->getRandomJokes("dsdsds")); });
index.php
PHP 7 will throw a Type Error like so:
PHP 7 TypeError
We have been able to add some PHP 7 features. The app also runs on a PHP 7 server and everything just works fine!
The source code of the PHP 7 version of the API can be found on the
on GitHub.php7 branch
Performance
PHP 7 runs on the new Zend engine 3.0, thus making your apps see up to 2x faster performance and 50% better memory consumption than PHP 5.6. It also allows you to serve more concurrent users without adding any hardware.
Rasmus Ledorf, Creator of PHP and inventor of the SQL LIMIT clause did some benchmarking with a few popular PHP projects with the various versions of PHP from PHP 5.4 up until PHP 7.0 and also benchmarked against HHVM 3.6.1.
Let's take a good look at the benchmarks. The test box specs Rasmus used are:
- Gigabyte Z87X-UD3H i7-4771 4 cores @ 3.50GHz w/ 16G of Ram @ 1600MHz
- Hyperthreading enabled for a total of 8 virtual cores
- Toshiba THNSNHH256GBST SSD
- Linux debian 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt9-2 (2015-04-13) x86_64 GNU/Linux
- MySQL 5.6.24
- Nginx-1.6.2 + php-fpm for all tests unless indicated otherwise
- Quiet local 100Mbps network
- Siege benchmark tool run from a separate machine
ZenCart 1.5.4
Moodle 2.9-dev
Cachet
Traq 3.5.2
Geeklog 2.1.0
Wardrobe CMS 1.2.0
Opencart 2.0.2.0
MediaWiki 1.24.1
phpBB 3.1.3
Wordpress 4.1.1
Drupal 8
From the results above, you can see that we can make double the amount of requests in less time in PHP 7 than PHP 5.
These specs can be found in the
Speeding Up The Web With PHP 7
talk he gave at Fluent Conf, 2015.Check out the following benchmarks:
- php7-benchmarks
- php7 final version vs hhvm benchmark
- hhvm vs php7 performance show down - Wordpress, Nginx
Conclusion
We have successfully covered how to upgrade your development and server environments from PHP 5 to PHP 7, gone through the features PHP 7 offers and also migrated two apps from PHP 5 to PHP 7.
Woot! Woot! It's been quite a journey highlighting everything PHP 7 has to offer. PHP has grown tremendously over the years from a toy language to a full-blown fast and enterprise language.
The PHP Manual and RFC documents remain the most complete go-to reference for any of the new PHP 7 features. You can always leverage them for more information.
About the author
Prosper Otemuyiwa
Former Auth0 Employee