Book Image

PHP 7 Programming Cookbook

By : Doug Bierer
Book Image

PHP 7 Programming Cookbook

By: Doug Bierer

Overview of this book

PHP 7 comes with a myriad of new features and great tools to optimize your code and make your code perform faster than in previous versions. Most importantly, it allows you to maintain high traffic on your websites with low-cost hardware and servers through a multithreading web server. This book demonstrates intermediate to advanced PHP techniques with a focus on PHP 7. Each recipe is designed to solve practical, real-world problems faced by PHP developers like yourself every day. We also cover new ways of writing PHP code made possible only in version 7. In addition, we discuss backward-compatibility breaks and give you plenty of guidance on when and where PHP 5 code needs to be changed to produce the correct results when running under PHP 7. This book also incorporates the latest PHP 7.x features. By the end of the book, you will be equipped with the tools and skills required to deliver efficient applications for your websites and enterprises.
Table of Contents (22 chapters)
PHP 7 Programming Cookbook
Credits
Foreword
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Creating a PHP 5 to PHP 7 code converter


For the most part, PHP 5.x code can run unchanged on PHP 7. There are a few changes, however, that are classified as backwards incompatible. What this means is that if your PHP 5 code is written in a certain way, or uses functions that have been removed, your code will break, and you'll have a nasty error on your hands.

Getting ready

The PHP 5 to PHP 7 Code Converter does two things:

  • Scans your code file and converts PHP 5 functionality that has been removed to its equivalent in PHP 7

  • Adds comments with // WARNING where changes in language usage have occurred, but where a re-write is not possible

    Note

    Please note that after running the converter, your code is not guaranteed to work in PHP 7. You will still have to review the // WARNING tags added. At the least, this recipe will give you a good head start converting your PHP 5 code to work in PHP 7.

The core of this recipe is the new PHP 7 preg_replace_callback_array() function. What this amazing function allows you to do is to present an array of regular expressions as keys, with the value representing an independent callback. You can then pass the string through a series of transformations. Not only that, the subject of the array of callbacks can itself be an array.

How to do it...

  1. In a new class Application\Parse\Convert, we begin with a scan() method, which accepts a filename as an argument. It checks to see if the file exists. If so, it calls the PHP file() function, which loads the file into an array, with each array element representing one line:

    public function scan($filename)
    {
        if (!file_exists($filename)) {
            throw new Exception(
                self::EXCEPTION_FILE_NOT_EXISTS);
        }
        $contents = file($filename);
        echo 'Processing: ' . $filename . PHP_EOL;
        
        $result = preg_replace_callback_array( [
  2. Next, we start passing a series of key/value pairs. The key is a regular expression, which is processed against the string. Any matches are passed to the callback, which is represented as the value part of the key/value pair. We check for opening and closing tags that have been removed from PHP 7:

        // replace no-longer-supported opening tags
        '!^\<\%(\n| )!' =>
            function ($match) {
                return '<?php' . $match[1];
            },
    
        // replace no-longer-supported opening tags
        '!^\<\%=(\n| )!' =>
            function ($match) {
                return '<?php echo ' . $match[1];
            },
    
        // replace no-longer-supported closing tag
        '!\%\>!' =>
            function ($match) {
                return '?>';
            },
  3. Next is a series of warnings when certain operations are detected and there is a potential code-break between how they're handled in PHP 5 versus PHP 7. In all these cases, the code is not re-written. Instead, an inline comment with the word WARNING is added:

        // changes in how $$xxx interpretation is handled
        '!(.*?)\$\$!' =>
            function ($match) {
                return '// WARNING: variable interpolation 
                       . ' now occurs left-to-right' . PHP_EOL
                       . '// see: http://php.net/manual/en/'
                       . '// migration70.incompatible.php'
                       . $match[0];
            },
    
        // changes in how the list() operator is handled
        '!(.*?)list(\s*?)?\(!' =>
            function ($match) {
                return '// WARNING: changes have been made '
                       . 'in list() operator handling.'
                       . 'See: http://php.net/manual/en/'
                       . 'migration70.incompatible.php'
                       . $match[0];
            },
    
        // instances of \u{
        '!(.*?)\\\u\{!' =>
            function ($match) {
            return '// WARNING: \\u{xxx} is now considered '
                   . 'unicode escape syntax' . PHP_EOL
                   . '// see: http://php.net/manual/en/'
                   . 'migration70.new-features.php'
                   . '#migration70.new-features.unicode-'
                   . 'codepoint-escape-syntax' . PHP_EOL
                   . $match[0];
        },
    
        // relying upon set_error_handler()
        '!(.*?)set_error_handler(\s*?)?.*\(!' =>
            function ($match) {
                return '// WARNING: might not '
                       . 'catch all errors'
                       . '// see: http://php.net/manual/en/'
                       . '// language.errors.php7.php'
                       . $match[0];
            },
    
        // session_set_save_handler(xxx)
        '!(.*?)session_set_save_handler(\s*?)?\((.*?)\)!' =>
            function ($match) {
                if (isset($match[3])) {
                    return '// WARNING: a bug introduced in'
                           . 'PHP 5.4 which '
                           . 'affects the handler assigned by '
                           . 'session_set_save_handler() and '
                           . 'where ignore_user_abort() is TRUE 
                           . 'has been fixed in PHP 7.'
                           . 'This could potentially break '
                           . 'your code under '
                           . 'certain circumstances.' . PHP_EOL
                           . 'See: http://php.net/manual/en/'
                           . 'migration70.incompatible.php'
                           . $match[0];
                } else {
                    return $match[0];
                }
            },
  4. Any attempts to use << or >> with a negative operator, or beyond 64, is wrapped in a try { xxx } catch() { xxx } block, looking for an ArithmeticError to be thrown:

        // wraps bit shift operations in try / catch
        '!^(.*?)(\d+\s*(\<\<|\>\>)\s*-?\d+)(.*?)$!' =>
            function ($match) {
                return '// WARNING: negative and '
                       . 'out-of-range bitwise '
                       . 'shift operations will now 
                       . 'throw an ArithmeticError' . PHP_EOL
                       . 'See: http://php.net/manual/en/'
                       . 'migration70.incompatible.php'
                       . 'try {' . PHP_EOL
                       . "\t" . $match[0] . PHP_EOL
                       . '} catch (\\ArithmeticError $e) {'
                       . "\t" . 'error_log("File:" 
                       . $e->getFile() 
                       . " Message:" . $e->getMessage());'
                       . '}' . PHP_EOL;
            },

    Note

    PHP 7 has changed how errors are handled. In some cases, errors are moved into a similar classification as exceptions, and can be caught! Both the Error and the Exception class implement the Throwable interface. If you want to catch either an Error or an Exception, catch Throwable.

  5. Next, the converter rewrites any usage of call_user_method*(), which has been removed in PHP 7. These are replaced with the equivalent using call_user_func*():

        // replaces "call_user_method()" with
        // "call_user_func()"
        '!call_user_method\((.*?),(.*?)(,.*?)\)(\b|;)!' =>
            function ($match) {
                $params = $match[3] ?? '';
                return '// WARNING: call_user_method() has '
                          . 'been removed from PHP 7' . PHP_EOL
                          . 'call_user_func(['. trim($match[2]) . ',' 
                          . trim($match[1]) . ']' . $params . ');';
            },
    
        // replaces "call_user_method_array()" 
        // with "call_user_func_array()"
        '!call_user_method_array\((.*?),(.*?),(.*?)\)(\b|;)!' =>
            function ($match) {
                return '// WARNING: call_user_method_array()'
                       . 'has been removed from PHP 7'
                       . PHP_EOL
                       . 'call_user_func_array([' 
                       . trim($match[2]) . ',' 
                       . trim($match[1]) . '], ' 
                       . $match[3] . ');';
            },
  6. Finally, any attempt to use preg_replace() with the /e modifier is rewritten using a preg_replace_callback():

         '!^(.*?)preg_replace.*?/e(.*?)$!' =>
        function ($match) {
            $last = strrchr($match[2], ',');
            $arg2 = substr($match[2], 2, -1 * (strlen($last)));
            $arg1 = substr($match[0], 
                           strlen($match[1]) + 12, 
                           -1 * (strlen($arg2) + strlen($last)));
             $arg1 = trim($arg1, '(');
             $arg1 = str_replace('/e', '/', $arg1);
             $arg3 = '// WARNING: preg_replace() "/e" modifier 
                       . 'has been removed from PHP 7'
                       . PHP_EOL
                       . $match[1]
                       . 'preg_replace_callback('
                       . $arg1
                       . 'function ($m) { return ' 
                       .    str_replace('$1','$m', $match[1]) 
                       .      trim($arg2, '"\'') . '; }, '
                       .      trim($last, ',');
             return str_replace('$1', '$m', $arg3);
        },
    
            // end array
            ],
    
            // this is the target of the transformations
            $contents
        );
        // return the result as a string
        return implode('', $result);
    }

How it works...

To use the converter, run the following code from the command line. You'll need to supply the filename of the PHP 5 code to be scanned as an argument.

This block of code, chap_01_php5_to_php7_code_converter.php, run from the command line, calls the converter:

<?php
// get filename to scan from command line
$filename = $argv[1] ?? '';

if (!$filename) {
    echo 'No filename provided' . PHP_EOL;
    echo 'Usage: ' . PHP_EOL;
    echo __FILE__ . ' <filename>' . PHP_EOL;
    exit;
}

// setup class autoloading
require __DIR__ . '/../Application/Autoload/Loader.php';

// add current directory to the path
Application\Autoload\Loader::init(__DIR__ . '/..');

// get "deep scan" class
$convert = new Application\Parse\Convert();
echo $convert->scan($filename);
echo PHP_EOL;

See also

For more information on backwards incompatible changes, please refer to http://php.net/manual/en/migration70.incompatible.php.