Deploying PHP 7

Code as Craft

Brooklyn

Nov.17, 2015

http://talks.php.net/etsy15

Rasmus Lerdorf
@rasmus

✔ engine improvements

  • 100%+ performance gain on most real-world applications
  • Lower memory usage, sometimes drastically lower

✔ Persistent secondary file-based cache for OPCache

; --enable-opcache-file
; php.ini
opcache.file_cache=/var/tmp

; php-cli.ini
opcache.enable_cli=1
opcache.file_cache=/var/tmp
opcache.file_cache_only=1
$ time composer >/dev/null
real	0m0.040s
user	0m0.032s
sys	0m0.004s

$ time composer >/dev/null
real	0m0.019s
user	0m0.016s
sys	0m0.000s

$ time php -d opcache.enable=0 /usr/local/bin/composer >/dev/null
real	0m0.033s
user	0m0.032s
sys	0m0.000s
/var/tmp
├── 7eeb6fe88104116c27c5650ddd83abf0
│   └── usr
│       └── local
│           └── bin
│               └── composer.bin
└── 7eeb6fe88104116c27c5650ddd83abf0phar:
    └── usr
        └── local
            └── bin
                └── composer
                    ├── bin
                    │   └── composer.bin
                    ├── src
                    │   ├── bootstrap.php.bin
                    │   └── Composer
                    │       ├── Command
                    │       │   ├── AboutCommand.php.bin
                    │       │   ├── ArchiveCommand.php.bin
                    │       │   ├── ClearCacheCommand.php.bin
                    │       │   ├── Command.php.bin
                    │       │   ├── ConfigCommand.php.bin
                    │       │   ├── CreateProjectCommand.php.bin
                    │       │   ├── DependsCommand.php.bin
                    │       │   ├── DiagnoseCommand.php.bin
                    │       │   ├── DumpAutoloadCommand.php.bin
                    │       │   ├── GlobalCommand.php.bin
                    │       │   ├── Helper
                    │       │   │   └── DialogHelper.php.bin
                    │       │   ├── HomeCommand.php.bin
                    │       │   ├── InitCommand.php.bin
                    │       │   ├── InstallCommand.php.bin
                    │       │   ├── LicensesCommand.php.bin
                    │       │   ├── RemoveCommand.php.bin
                    │       │   ├── RequireCommand.php.bin
                    │       │   ├── RunScriptCommand.php.bin
                    │       │   ├── SearchCommand.php.bin
                    │       │   ├── SelfUpdateCommand.php.bin
                    │       │   ├── ShowCommand.php.bin
                    │       │   ├── StatusCommand.php.bin
                    │       │   ├── UpdateCommand.php.bin
                    │       │   └── ValidateCommand.php.bin
                    │       ├── Composer.php.bin
                    │       ├── Console
                    │       │   └── Application.php.bin
                    │       ├── Factory.php.bin
                    │       ├── IO
                    │       │   ├── BaseIO.php.bin
                    │       │   ├── ConsoleIO.php.bin
                    │       │   └── IOInterface.php.bin
                    │       ├── Package
                    │       │   ├── BasePackage.php.bin
                    │       │   └── PackageInterface.php.bin
                    │       ├── Script
                    │       │   └── ScriptEvents.php.bin
                    │       └── Util
                    │           └── ErrorHandler.php.bin
                    └── vendor
                        ├── autoload.php.bin
                        ├── composer
                        │   ├── autoload_classmap.php.bin
                        │   ├── autoload_namespaces.php.bin
                        │   ├── autoload_psr4.php.bin
                        │   ├── autoload_real.php.bin
                        │   └── ClassLoader.php.bin
                        └── symfony
                            └── console
                                └── Symfony
                                    └── Component
                                        └── Console
                                            ├── Application.php.bin
                                            ├── Command
                                            │   ├── Command.php.bin
                                            │   ├── HelpCommand.php.bin
                                            │   └── ListCommand.php.bin
                                            ├── Descriptor
                                            │   ├── ApplicationDescription.php.bin
                                            │   ├── DescriptorInterface.php.bin
                                            │   ├── Descriptor.php.bin
                                            │   ├── JsonDescriptor.php.bin
                                            │   ├── MarkdownDescriptor.php.bin
                                            │   ├── TextDescriptor.php.bin
                                            │   └── XmlDescriptor.php.bin
                                            ├── Formatter
                                            │   ├── OutputFormatterInterface.php.bin
                                            │   ├── OutputFormatter.php.bin
                                            │   ├── OutputFormatterStyleInterface.php.bin
                                            │   ├── OutputFormatterStyle.php.bin
                                            │   └── OutputFormatterStyleStack.php.bin
                                            ├── Helper
                                            │   ├── DebugFormatterHelper.php.bin
                                            │   ├── DescriptorHelper.php.bin
                                            │   ├── DialogHelper.php.bin
                                            │   ├── FormatterHelper.php.bin
                                            │   ├── HelperInterface.php.bin
                                            │   ├── Helper.php.bin
                                            │   ├── HelperSet.php.bin
                                            │   ├── InputAwareHelper.php.bin
                                            │   ├── ProcessHelper.php.bin
                                            │   ├── ProgressHelper.php.bin
                                            │   ├── QuestionHelper.php.bin
                                            │   ├── TableHelper.php.bin
                                            │   ├── Table.php.bin
                                            │   └── TableStyle.php.bin
                                            ├── Input
                                            │   ├── ArgvInput.php.bin
                                            │   ├── ArrayInput.php.bin
                                            │   ├── InputArgument.php.bin
                                            │   ├── InputAwareInterface.php.bin
                                            │   ├── InputDefinition.php.bin
                                            │   ├── InputInterface.php.bin
                                            │   ├── InputOption.php.bin
                                            │   └── Input.php.bin
                                            └── Output
                                                ├── ConsoleOutputInterface.php.bin
                                                ├── ConsoleOutput.php.bin
                                                ├── NullOutput.php.bin
                                                ├── OutputInterface.php.bin
                                                ├── Output.php.bin
                                                └── StreamOutput.php.bin

32 directories, 87 files

✔ Abstract Syntax Tree!!

echo substr("abc", [1,2]);
% phan -a test.php
AST_STMT_LIST @ 1
    0: AST_STMT_LIST @ 2
        0: AST_ECHO @ 2
            0: AST_CALL @ 2
                0: AST_NAME @ 2
                    flags: NAME_NOT_FQ (1)
                    0: "substr"
                1: AST_ARG_LIST @ 2
                    0: "abc"
                    1: AST_ARRAY @ 2
                        0: AST_ARRAY_ELEM @ 2
                            flags: 0
                            0: 1
                            1: null
                        1: AST_ARRAY_ELEM @ 2
                            flags: 0
                            0: 2
                            1: null
% phan test.php
test.php:2 TypeError arg#2(start) is int[] but substr() takes int

✔ Exceptions on Fatals

function call_method($obj) {
    $obj->method();
}
call_method(null);
Fatal error: Uncaught Error: Call to a member function method() on null in file:2
Stack trace:
#0 file(4): call_method(NULL)
#1 {main}
  thrown in file on line 2
try {
    call_method(null);
} catch (Error $e) {
    echo "Caught Exception: {$e->getMessage()}\n";
}
Caught Exception: Call to a member function method() on null

PHP 7 Exception Hierarchy


  • Throwable
  • Exception implements Throwable
  • Error implements Throwable
  • TypeError extends Error
  • ParseError extends Error

✔ Return Types

function get_config(): array {
    return 42;
}
get_config();
Fatal error: Uncaught TypeError: Return value of get_config() must be
of the type array, integer returned in file:2
Stack trace:
#0 file(4): get_config()
#1 {main}
  thrown in file on line 2

✔ Coercive Scalar Types

function logmsg(string $msg, int $level, float $severity) {
    var_dump($msg);      // string(1) "1"
    var_dump($level);    // int(2)
    var_dump($severity); // float(3)
}
logmsg(1, "2.5 bananas", 3);
Notice: A non well formed numeric value encountered in file on line 2

✔ Strict Scalar Types

declare(strict_types=1);
...
logmsg(1, "2.5", 3);
Fatal error: Uncaught TypeError: Argument 1 passed to logmsg() must be of the
type string, integer given, called in file on line 7 and defined in file:3
Stack trace:
#0 file(7): logmsg(1, '2.5', 3)
#1 {main}
  thrown in file on line 2

✔ Anonymous Classes

return new class($controller) implements Page {
    public function __construct($controller) {
        /* ... */
    }
    /* ... */
};

class MyObject extends MyStuff {
    public function getInterface() {
        return new class implements MyInterface {
            /* ... */
        };
    }
}

✔ Coalesce Operator

$a = NULL;
$b = 0;
$c = 2;

echo $a ?? $b; // 0
echo $c ?? $b; // 2
echo $a ?? $b ?? $c; // 0
echo $a ?? $x ?? $c; // 2

✔ Spaceship Operator

|=|  Tie Fighter
k=k  Tie Interceptor
<==> Tie Bomber  
<=>  Tie Advanced X1 ✔
function cmp_php5($a, $b) {
    return ($a < $b) ? -1 : (($a >$b) ? 1 : 0);
}

function cmp_php7($a, $b) {
    return $a <=> $b;
}

✔ Zero-cost Assertions

function test($arg) {
    assert($arg > 20 && $arg < 110, "$arg is invalid");
}
ini_set('zend.assertions',0); test(16);
ini_set('zend.assertions',1); test(17);
ini_set('assert.exception',1); test(18);
Warning: assert(): 17 is invalid failed in file on line 2
Fatal error: Uncaught AssertionError: 18 is invalid in file:2
Stack trace:
#0 file(2): assert(false, '18 is invalid')
#1 file(6): test(18)
#2 {main}
  thrown in file on line 2
; Completely skip compiling assert() calls
; (can only be set in php.ini)
zend.assertions = -1

✔ Added Closure::call()

$f = function () {
    return $this->n;
};
class MyClass {
    private $n = 42;
}
$myC = new MyClass;
$c = $f->bindTo($myC, "MyClass");
$c();
$f = function () {
    return $this->n;
};
class MyClass {
    private $n = 42;
}
$myC = new MyClass;
$f->call($myC);

✔ Removal of many deprecated features
     (Your PHP4 code will break!)

- ext/ereg (use ext/pcre instead)
- preg_replace() eval modifier (use preg_replace_callback() instead)
- ext/mysql (use ext/mysqli or ext/pdo_mysql instead)
- Assignment of new by reference
- Scoped calls of non-static methods from incompatible $this context

- dl() in php-fpm
- set_magic_quotes_runtime() and magic_quotes_runtime()
- set_socket_blocking() (use stream_set_blocking() instead)
- mcrypt_generic_end() (use mcrypt_generic_deinit() instead)
- mcrypt_ecb, mcrypt_cbc, mcrypt_cfb and mcrypt_ofb 
  (use mcrypt_encrypt() and mcrypt_decrypt() instead)
- datefmt_set_timezone_id() and IntlDateFormatter::setTimeZoneID() 
  (use datefmt_set_timezone() or IntlDateFormatter::setTimeZone() instead)

- xsl.security_prefs (use XsltProcessor::setSecurityPrefs() instead)
- iconv.input_encoding, iconv.output_encoding, iconv.internal_encoding,
  mbstring.http_input, mbstring.http_output and mbstring.internal_encoding
  (use php.input_encoding, php.internal_encoding and php.output_encoding instead)

- $is_dst parameter of the mktime() and gmmktime() functions
- # style comments in ini files (use ; style comments instead)
- String category names in setlocale() (use LC_* constants instead)
- Unsafe curl file uploads (use CurlFile instead)
- PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT driver option 
  (use PDO::ATTR_EMULATE_PREPARES instead)
- CN_match and SNI_server_name stream context option (use peer_name instead)

✔ New reserved words:

  • bool
  • int
  • float
  • string
  • null
  • false
  • true
  • resource
  • object
  • mixed
  • numeric

✔ 64-bit integer support on Windows

✔ Cleanup edge-case integer overflow/underflow

✔ Support for strings with length >= 2^31 bytes in 64 bit builds.

✔ Parse error on invalid numeric literals

$mask = 0855;  // Parse error: Invalid numeric literal

✔ Uniform variable syntax

// left-to-right
$this->$belongs_to['column']
// vs.
$this->{$belongs_to['column']}

// support missing combinations of operations
$foo()['bar']()
[$obj1, $obj2][0]->prop
getStr(){0}
 
// support nested ::
$foo['bar']::$baz
$foo::$bar::$baz
$foo->bar()::baz()
 
// support nested ()
foo()()
$foo->bar()()
Foo::bar()()
$foo()()
 
// support operations on arbitrary (...) expressions
(...)['foo']
(...)->foo
(...)->foo()
(...)::$foo
(...)::foo()
(...)()
 
// two more practical examples for the last point
(function() { ... })()
($obj->closure)()
 
// support all operations on dereferencable scalars
// (not very useful)
"string"->toLower()
[$obj, 'method']()
'Foo'::$bar

✔ Unicode Codepoint Escape Syntax

echo "\u{202E}Right-to-left text";

echo "\u{1F602}";
‮Right-to-left text😂		

✔ ICU IntlChar class added to intl extension

✔ CSPRNG

$int   = random_int(-500, 500);
$bytes = random_bytes(10);

var_dump( $int );
var_dump( bin2hex($bytes) );
int(401)
string(20) "d46f28769226172c66d2"
		

GA release scheduled for Dec.3

Time and number of machine instructions for 100 requests against Wordpress-3.6.0 front page

(click on legend items to show/hide data sets)

  • zval size reduced from 24 to 16 bytes
  • Hashtable size reduced from 72 to 56 bytes
  • Hashtable bucket size reduced from 72 to 32 bytes
  • Immutable array optimization
$a = [];
for($i=0; $i < 100000;$i++) {
    $a[] = ['abc','def','ghi','jkl','mno','pqr'];
}
echo memory_get_usage(true);

// PHP 5.x  109M
// PHP 7.0   42M no opcache
// PHP 7.0    6M with opcache enabled
  • Much more cpu cache friendly
  • New memory allocator similar to jemalloc
  • Faster hashtable iteration API
  • Array duplication optimization
  • PCRE JIT enabled by default
  • Fast ZPP (ZendParseParameters) implementation
  • Faster stack-allocated zvals (instead of heap)
  • Optimized VM calling
  • Global register variables with gcc 4.8+
  • plus hundreds of micro-optimizations

HugePage support in Opcache

./configure --enable-huge-code-pages
opcache.memory_consumption=256
opcache.huge_code_pages=1
% sysctl -w vm.nr_hugepages=256
% service php-fpm start
% cat /proc/meminfo | grep Huge
HugePages_Total:     256
HugePages_Free:      231
HugePages_Rsvd:      119
HugePages_Surp:        0
Hugepagesize:       2048 kB

JIT?

Lies, damn lies and benchmarks

Test Box Specs


  • 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-ckt11-1+deb8u5 (2015-10-09) x86_64 GNU/Linux
  • MySQL 5.6.27
  • nginx-1.6.2 + php-fpm for all tests unless indicated otherwise
  • Quiet local 100Mbps network
  • siege benchmark tool run from a separate machine

All versions of PHP compiled locally using gcc-4.9.2 -O2

configure flags

./configure --disable-debug       --with-apxs2=/usr/bin/apxs2 \
            --enable-zend-signals --with-gd \
            --without-pear        --with-jpeg-dir=/usr \
            --with-png-dir=/usr   --with-vpx-dir=/usr \
            --with-t1lib=/usr     --with-freetype-dir=/usr \
            --enable-exif         --enable-gd-native-ttf \
            --with-zlib           --with-mysql=/usr \
            --with-gmp            --with-zlib-dir=/usr \
            --with-gettext        --with-kerberos \
            --with-imap-ssl       --with-mcrypt=/usr/local \
            --with-iconv          --enable-sockets \
            --with-openssl        --with-pspell \
            --with-pdo-sqlite     --with-pdo-mysql=mysqlnd \
            --enable-soap         --enable-xmlreader \
            --enable-phar=shared  --with-xsl \
            --enable-ftp          --enable-cgi \
            --with-curl=/usr      --with-tidy \
            --with-xmlrpc         --enable-mbstring \
            --enable-sysvsem      --enable-sysvshm \
            --enable-shmop        --with-readline \
            --enable-pcntl        --enable-fpm \
            --enable-intl         --enable-zip \
            --with-imap           --with-mysqli=mysqlnd \
            --enable-calendar     --prefix=/usr/local \
            --enable-huge-code-pages \
            --with-mysql-sock=/var/run/mysqld/mysqld.sock \
            --with-config-file-scan-dir=/etc/php7/conf.d \
            --with-config-file-path=/etc/php7

php.ini

[PHP]
zend.multibyte=On
date.timezone="America/Los_Angeles"
display_startup_errors=On
zend.enable_gc=Off
include_path="/usr/local/lib/php"
default_charset="UTF-8"
error_reporting=-1

variables_order=GPCS
sendmail_path=""

max_execution_time=60
memory_limit=512M
post_max_size=1024M
cgi.force_redirect=0
cgi.fix_pathinfo=1

magic_quotes=0
magic_quotes_gpc=0
user_ini.filename=
realpath_cache_size=2M
cgi.check_shebang_line=0

max_input_vars=1000
max_file_uploads=50

zend_extension=opcache.so
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.huge_code_pages=1

php-fpm.d/www.conf

[www]
user = www-data
group = www-data
listen = /var/run/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = static
pm.max_children = 10
pm.status_path = /status
ping.path = /ping
ping.response = pong

nginx.conf

user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events {
        worker_connections 768;
}

http {
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        gzip on;
        gzip_disable "msie6";

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

php.conf

location ~ \.php {
  include                  fastcgi_params;
  fastcgi_keep_conn        on;
  fastcgi_index            index.php;
  fastcgi_split_path_info  ^(.+\.php)(/.+)$;
  fastcgi_param PATH_INFO  $fastcgi_path_info;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_intercept_errors on;
  fastcgi_pass             unix:/var/run/php-fpm.sock;
}

hhvm.conf

location ~ \.php$ {
  include           fastcgi_params;
  fastcgi_keep_conn on;
  fastcgi_pass      unix:/var/run/hhvm/server.sock;
  fastcgi_index     index.php;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

sites-enabled/wordpress

server {
    listen 80;
    server_name wordpress;
    root /var/www/wordpress;
    access_log /var/log/nginx/wordpress-access.log;
    error_log /var/log/nginx/wordpress-error.log;

    location / {
        index     index.html index.htm index.php;
        autoindex on;
    }

    location ~ /\.  { return 403; }

    include backend.conf;
}

HipHop VM 3.10.1 (rel) from http://dl.hhvm.com/ubuntu

server.ini

pid = /var/run/hhvm/pid
hhvm.server.file_socket = /var/run/hhvm/server.sock
hhvm.server.type = fastcgi
hhvm.server.default_document = index.php
hhvm.log.use_log_file = true
hhvm.log.file = /var/log/hhvm/error.log
hhvm.repo.central.path = /var/run/hhvm/hhvm.hhbc

php.ini

session.save_handler = files
session.save_path = /var/lib/php5
session.gc_maxlifetime = 1440

hhvm.log.level = Warning
hhvm.log.always_log_unhandled_exceptions = true
hhvm.log.runtime_error_reporting_level = 8191
hhvm.mysql.typed_results = false

GCC Feedback-Directed Optimization (FDO)

$ make clean
$ make -j8 prof-gen
...
$ sapi/cgi/php-cgi -T 1000 /var/www/wordpress/index.php > /dev/null
$ make prof-clean
$ make -j8 prof-use

Had to fix one line of code in the Avalon database library:

diff --git a/database/model.php b/database/model.php
index 6c5f7da..c93e726 100644
--- a/database/model.php
+++ b/database/model.php
@@ -397,7 +397,7 @@ public function __get($var) {
           $belongs_to['column'] = $var . '_id';
       }
       $model = $belongs_to['model'];
-      return $this->$var = $model::find($belongs_to['foreign_key'], $this->$belongs_to['column']);
+      return $this->$var = $model::find($belongs_to['foreign_key'], $this->{$belongs_to['column']});
   } else {
       $val = $this->$var;

Measuring Memory Use

  • VSZ - Virtual Memory Size (swapped out, shared libs, everything)
  • RSS - Resident Set Size (everything not swapped out)
  • SIZE - Data segment of the process
debian:/home/rasmus# ps ef -o command,vsize,rss,size -C php-fpm
COMMAND                        VSZ   RSS  SIZE
php-fpm: master process (/u 459152  9912  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304
 \_ php-fpm: pool www       459152  7732  3304

top isn't helpful either

debian:/home/rasmus# top -p $(pgrep -d , php-fpm)
top - 07:04:14 up 19 days, 15:21,  1 user,  load average: 0.00, 0.04, 0.51
Tasks:  11 total,   0 running,  11 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:  16355868 total, 15789244 used,   566624 free,   493148 buffers
KiB Swap:        0 total,        0 used,        0 free. 13514376 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                    
13950 root      20   0  459152   9912   6016 S   0.0  0.1   0:00.02 php-fpm                                                                    
13951 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13952 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13953 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13954 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13955 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13956 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13957 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13958 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13959 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm                                                                    
13960 www-data  20   0  459152   7732   3836 S   0.0  0.0   0:00.00 php-fpm

smem!

  • USS - Unique Set Size
  • PSS - Proportional Set Size
  • RSS - Resident Set Size
debian:/home/rasmus# smem -t -k -U www-data -P ".*php-fpm"
  PID User     Command                         Swap      USS      PSS      RSS 
12570 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12571 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12572 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12573 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12574 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12575 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12576 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12577 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12578 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
12579 www-data php-fpm: pool www                  0   156.0K   621.0K     7.6M 
-------------------------------------------------------------------------------
   10 1                                           0     1.5M     6.1M    75.9M

Serving Drupal 8

debian:/home/rasmus# smem -t -k -U www-data -P ".*php-fpm"
  PID User     Command                         Swap      USS      PSS      RSS 
13233 www-data php-fpm: pool www                  0   792.0K     1.3M     9.2M 
13231 www-data php-fpm: pool www                  0   792.0K     1.3M     9.2M 
13235 www-data php-fpm: pool www                  0   792.0K     1.3M     9.2M 
13232 www-data php-fpm: pool www                  0   796.0K     1.3M     9.2M 
13234 www-data php-fpm: pool www                  0   792.0K     1.3M     9.3M 
13227 www-data php-fpm: pool www                  0   808.0K     1.3M     9.2M 
13229 www-data php-fpm: pool www                  0   808.0K     1.3M     9.2M 
13230 www-data php-fpm: pool www                  0   808.0K     1.3M     9.2M 
13226 www-data php-fpm: pool www                  0   808.0K     1.3M     9.2M 
13228 www-data php-fpm: pool www                  0   808.0K     1.3M     9.3M 
-------------------------------------------------------------------------------
   10 1                                           0     7.8M    12.9M    92.4M

Top-3 Things that might bite you




For the full list see

php.net/migration70

Left-to-right semantics for complicated expressions

$$foo['bar']['baz'] // interpreted as ($$foo)['bar']['baz']
$foo->$bar['baz']   // interpreted as ($foo->$bar)['baz']
$foo->$bar['baz']() // interpreted as ($foo->$bar)['baz']()
Foo::$bar['baz']()  // interpreted as (Foo::$bar)['baz']()

To restore the previous behaviour add explicit curly braces:

${$foo['bar']['baz']}
$foo->{$bar['baz']}
$foo->{$bar['baz']}()
Foo::{$bar['baz']}()

Detection: phan or unit test failures

Removed support for /e (PREG_REPLACE_EVAL) modifier

echo preg_replace('/:-:(.*?):-:/e', '$this->pres->\\1', $text);

Change to:

echo preg_replace_callback(
  '/:-:(.*?):-:/', 
  function($matches) {
    return $this->pres->{$matches[1]}; // Careful!
  },
  $text);

Detection: grep, warnings in logs or unit test failures

Invalid octal literals now produce a parse error

echo 05678; // PHP 5.x outputs 375
Parse error: Invalid numeric literal in file.php on line 2		

Detecting parse errors is easy: php -l

Static Analysis




github.com/rlerdorf/phan
% phan -h
Usage: phan [options] [files...]
  -f <filename>   A file containing a list of PHP files to be analyzed
  -q              Quick mode - doesn't recurse into all function calls
  -b              Check for potential PHP 5 -> PHP 7 BC issues
  -i              Ignore undeclared functions and classes
  -c              Comma-separated list of classes that require parent::__construct() to be called
  -m <mode>       Output mode: verbose, short, json, csv
  -o <filename>   Output filename
  -p              Show progress bar
  -g [node_name]  Output a graphviz (.gv) file of the class hierarchy
  -a              Dump AST of provides files (for debugging)
  -s              Dump scope tree (for debugging)
  -u              Dump user defined functions (for debugging)
  -h			  This help
% phan -b display.php
Files scanned: 1
Time:		0.13s
Classes:	8
Methods:	55
Functions:	5
Closures:	5
Traits:		0
Conditionals:	307
Issues found:	1

display.php:416 CompatError expression may not be PHP 7 compatible
echo preg_replace('/:-:(.*?):-:/e', '$this->pres->\\1', $text);
echo preg_replace_callback(
    '/:-:(.*?):-:/', 
    function($matches) {
      return $this->pres->$matches[1]; // Oops!
    },
    $text);
echo preg_replace_callback(
    '/:-:(.*?):-:/', 
    function($matches) {
      return $this->pres->{$matches[1]}; // Ok
    },
    $text);
% git clone https://github.com/Seldaek/monolog.git
% cd monolog
% find . -name '*.php' | grep -v test > filelist.txt
% phan -i -b -f filelist.txt
Files scanned: 81
Time:		0.22s
Classes:	81
Methods:	437
Functions:	0
Closures:	13
Traits:		0
Conditionals:	363
Issues found:	21

./src/Monolog/Logger.php:320 CompatError expression may not be PHP 7 compatible
./src/Monolog/Logger.php:445 CompatError expression may not be PHP 7 compatible
./src/Monolog/Registry.php:119 CompatError expression may not be PHP 7 compatible
./src/Monolog/Handler/NewRelicHandler.php:55 TypeError Default value for string $level can't be int
./src/Monolog/Handler/RedisHandler.php:41 TypeError Default value for integer $capSize can't be bool
./src/Monolog/Formatter/NormalizerFormatter.php:62 TypeError arg#1(val) is array but is_infinite() takes float
./src/Monolog/Formatter/NormalizerFormatter.php:65 TypeError arg#1(val) is array but is_nan() takes float
./src/Monolog/Formatter/NormalizerFormatter.php:94 TypeError arg#1(e) is array but Monolog\Formatter\NormalizerFormatter::normalizeException() takes Except
ion defined at ./src/Monolog/Formatter/NormalizerFormatter.php:115
./src/Monolog/Formatter/NormalizerFormatter.php:98 TypeError arg#1(object) is array but method_exists() takes object|string
./src/Monolog/Formatter/NormalizerFormatter.php:105 TypeError arg#1(object) is array but get_class() takes object
./src/Monolog/Formatter/GelfMessageFormatter.php:60 TypeError property is declared to be string but was assigned null
./src/Monolog/Handler/StreamHandler.php:124 TypeError return void but getDirFromStream() is declared to return null|string
./src/Monolog/Handler/SocketHandler.php:219 TypeError arg#2(seconds) is float but stream_set_timeout() takes int
./src/Monolog/Handler/SocketHandler.php:219 TypeError arg#3(microseconds) is float but stream_set_timeout() takes int
./src/Monolog/Handler/FirePHPHandler.php:81 TypeError return array but createRecordHeader() is declared to return string
./src/Monolog/Handler/FirePHPHandler.php:153 TypeError arg#1(array_arg) is string but current() takes array
./src/Monolog/Handler/FirePHPHandler.php:154 TypeError arg#1(array_arg) is string but key() takes array
./src/Monolog/Handler/FirePHPHandler.php:154 TypeError arg#1(array_arg) is string but current() takes array
./src/Monolog/Handler/SwiftMailerHandler.php:83 TypeError arg#1(content) is null but Monolog\Handler\SwiftMailerHandler::buildMessage() takes string define
d at ./src/Monolog/Handler/SwiftMailerHandler.php:55
./src/Monolog/Handler/NewRelicHandler.php:66 TypeError property is declared to be string but was assigned null
./src/Monolog/Handler/ChromePHPHandler.php:178 TypeError return int but headersAccepted() is declared to return boolean

Let's deploy it!

Atomic

No performance hit

  • No restarts
  • No LB removal
  • No thundering herd
  • Cache reuse

Must be able to serve two versions of the site concurrently!

Requests that begin on DocumentRoot A must finish on A

Set the DocumentRoot to symlink target!

Easy with nginx

fastcgi_param DOCUMENT_ROOT $realpath_root

Apache

github.com/etsy/mod_realdoc

Avoid hardcoding full paths

Watch your include_path setting

incpath extension can resolve your include_path for you

https://github.com/etsy/incpath

Version all static assets

DB Schema changes need special care

Test your applications

Test your extensions

It is really easy!

Full Vagrant Debian Dev Environment

Or if you prefer Docker:

github.com/janatzend/docker-php7-nightly-build

Install Vagrant and Virtualbox

Then:

$ git clone https://github.com/rlerdorf/php7dev.git

$ cd php7dev

$ vagrant up
... (takes a bit - it is downloading 1.5G)

$ vagrant ssh

It will NAT, DHCP and also has a fixed address of 192.168.7.7

http://192.168.7.7/ will show you the PHP 7 phpinfo() page

Now you have a working Rasmus-approved dev box on your network

Switching PHP versions is trivial

vagrant@php7dev:~$ newphp 56
Activating PHP 5.6.6-dev and restarting php-fpm

vagrant@php7dev:~$ newphp 7 debug
Activating PHP 7.0.0-dev and restarting php-fpm

24 pre-compiled versions

/usr/local/php53            /usr/local/php55            /usr/local/php70
/usr/local/php53-debug      /usr/local/php55-debug      /usr/local/php70-debug
/usr/local/php53-debug-zts  /usr/local/php55-debug-zts  /usr/local/php70-debug-zts
/usr/local/php53-zts        /usr/local/php55-zts        /usr/local/php70-zts
/usr/local/php54            /usr/local/php56            /usr/local/php71
/usr/local/php54-debug      /usr/local/php56-debug      /usr/local/php71-debug
/usr/local/php54-debug-zts  /usr/local/php56-debug-zts  /usr/local/php71-debug-zts
/usr/local/php54-zts        /usr/local/php56-zts        /usr/local/php71-zts

Build any version

$ makephp 7
Build log in /tmp/build.log
Building PHP 7.0
configuring...
compiling...
installing...
done
Building PHP 7.0-debug
configuring...
compiling...
installing...
done

or manually

$ cd php-src
$ git checkout PHP-5.6
$ git pull -r
$ make distclean
$ ./buildconf -f
$ ./cn56
$ make
$ sudo make install

Thank You

https://github.com/rlerdorf/php7dev
https://github.com/rlerdorf/phan
https://bugs.php.net
http://talks.php.net/etsy15



Report Bugs

Useful bug reports, please!