Rasmus Lerdorf
@rasmus
✔ engine improvements
✔ 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
✔ 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:
✔ 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(-336) string(20) "bda9540051b2dab8fefa"
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)
$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
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
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
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!
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
For the full list see
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
% 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
Atomic
No performance hit
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
Avoid hardcoding full paths
Watch your include_path setting
incpath extension can resolve your include_path for you
Version all static assets
DB Schema changes need special care
At Etsy we use irc
Channel: #push Topic: <prod> *joe *steve frank|bob
devbot: Swapping symlinks. Your code is about to start taking production traffic
pushbot: joe steve frank : Your code is live. Time to watch graphs: http://et.sy/abcd
Rasmus: .join
*** pushbot has changed the topic on #push to <prod> joe steve frank|bob Rasmus
frank: .good
*** pushbot has changed the topic on #push to <prod> *joe *steve *frank|bob Rasmus
joe: .done
*** pushbot has changed the topic on #push to <prod> bob Rasmus
pushbot: bob Rasmus: You're up
bob: .in
*** pushbot has changed the topic on #push *bob Rasmus
Rasmus: .in
*** pushbot has changed the topic on #push *bob *Rasmus
pushbot commands
Channel: #push Topic: <princess> bob Rasmus
Jenkins: Starting build #36803 for job qa
Jenkins: Starting build #38784 for job princess
Jenkins: Project qa build #36803: SUCCESS in 6 min 19 sec: http://ci.etsy/job/qa/36803/
pushbot: bob Rasmus : qa tests have passed
devbot: [who_tried] Everyone in this push has run Try recently. w00t!
Jenkins: Project princess build #38784: SUCCESS in 1 min 10 sec: http://ci.etsy/job/princess/38784/
pushbot: bob Rasmus : princess tests have passed
bob: .good
Rasmus: .good
*** pushbot has changed the topic on #push to <princess> *bob *Rasmus
pushbot: bob Rasmus : everyone is ready, checking on Jenkins...
Jenkins: qa: last build: 36803 (9 min 5 sec ago): SUCCESS: http://ci.etsy/job/qa/36803/
Jenkins: princess: last build: 38784 (2 min 54 sec ago): SUCCESS: http://ci.etsy/job/princess/38784/
Deploy to Production:
Channel: #push Topic: <prod> bob Rasmus
devbot: Swapping symlinks. Your code is about to start taking production traffic
pushbot: bob Rasmus : Your code is live. Time to watch graphs: http://etsy/et5cp
Jenkins: Starting build #39452 for job prod
pushbot: bob Rasmus : prod tests have passed
Jenkins: Project prod build #39452: SUCCESS in 30 sec: http://ci.etsy/job/prod/39452/
bob: .good
Rasmus: .good
*** pushbot has changed the topic on #push to <prod> *bob *Rasmus
pushbot: bob Rasmus : everyone is ready, checking on Jenkins...
Jenkins: prod: last build: 39452 (1 min 39 sec ago): SUCCESS: http://ci.etsy/job/prod/39452/
bob: .done
pushbot: clear
*** pushbot has changed the topic on #push to clear
Graph Everything!
Log Everything!
Blameless post-mortems
{
"gearman_servers_list":
[
"localhost"
],
"pools_list":
{
"ShopStats":
{
"job_processing_uri": "http://localhost/job.php",
"worker_count": 20,
"jobs_list":
[
"ShopStats"
]
},
"Newsfeed":
{
"job_processing_uri": "http://localhost/job.php",
"worker_count": 10,
"jobs_list":
[
"Newsfeed"
]
},
"Regular":
{
"job_processing_uri": "http://localhost/job.php",
"worker_count": 5,
"jobs_list":
[
"Sum3",
"Sum",
"Sum2"
]
}
}
}
Full Vagrant Debian Dev Environment
Or if you prefer Docker:
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
Report Bugs
Useful bug reports, please!