25 Years of PHP

DrupalCamp

London

Mar.1, 2019

http://talks.php.net/drupalcamp

Rasmus Lerdorf
@rasmus

1980s

1990s

1993

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define ishex(x) (((x) >= '0' && (x) <= '9') || ((x) >= 'a' && \
                   (x) <= 'f') || ((x) >= 'A' && (x) <= 'F'))

int htoi(char *s) {
	int     value;
	char    c;

	c = s[0];
	if(isupper(c)) c = tolower(c);
	value=(c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;

	c = s[1];
	if(isupper(c)) c = tolower(c);
	value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;

	return(value);
}

void main(int argc, char *argv[]) {
	char *params, *data, *dest, *s, *tmp;
	char *name, *age;

	puts("Content-type: text/html\r\n");
	puts("<HTML><HEAD><TITLE>Form Example</TITLE></HEAD>");
	puts("<BODY><H1>My Example Form</H1>");
	puts("<FORM action=\"form.cgi\" method=\"GET\">");
	puts("Name: <INPUT type=\"text\" name=\"name\">");
	puts("Age: <INPUT type=\"text\" name=\"age\">");
	puts("<BR><INPUT type=\"submit\">");
	puts("</FORM>");

	data = getenv("QUERY_STRING");
	if(data && *data) {
		params = data; dest = data;
    	while(*data) {
			if(*data=='+') *dest=' ';
			else if(*data == '%' && ishex(*(data+1))&&ishex(*(data+2))) {
				*dest = (char) htoi(data + 1);
				data+=2;
			} else *dest = *data;
			data++;
			dest++;
		}
		*dest = '\0';
		s = strtok(params,"&");
		do {
			tmp = strchr(s,'=');
			if(tmp) {
				*tmp = '\0';
				if(!strcmp(s,"name")) name = tmp+1;
				else if(!strcmp(s,"age")) age = tmp+1;
			}
		} while(s=strtok(NULL,"&"));

		printf("Hi %s, you are %s years old\n",name,age);
	}
	puts("</BODY></HTML>");
}

1993

use CGI qw(:standard);
print header;
print start_html('Form Example'),
    h1('My Example Form'),
    start_form,
    "Name: ", textfield('name'),
    p,
    "Age: ", textfield('age'),
    p,
    submit,
    end_form;
if(param()) {
    print "Hi ",em(param('name')),
        "You are ",em(param('age')),
        " years old";
}
print end_html;

1994-1995

<html><head><title>Form Example</title></head>
<body><h1>My Example Form</h1>
<form action="form.phtml" method="POST">
Name: <input type="text" name="name">
Age: <input type="text" name="age">
<br><input type="submit">
</form>
<?if($name):?>
Hi <?echo $name?>, you are <?echo $age?> years old
<?endif?>
</body></html>

Focus on the Ecosystem

  • LAMP wasn't an accident
  • Robustness, Performance and Security
  • shared hosting ISPs

Scale

  • Scaling up is expected
  • Scaling down is surprisingly hard
  • Doing both is rocket science

Version Support

Active Support Regular releases and security fixes
Security Fixes Only security fixes
End of Life No longer supported

Saving the Planet?

  • Around 2 billion sites on the web
  • On 10 million physical machines
  • PHP drives at least 50%
  • Currently ~50% PHP 7 Adoption
  • which is about 2.5M physical servers
  • 3000 KWH/year per server costs approx. US$400
  • Data center cooling doubles that
  • 0.5kg CO2 per KWH

At 50% Adoption

  • US $2B savings
  • 7.5B KWH Savings
  • 3.75B kg less CO2

Let's double that to 100%!

  • $4B savings
  • 15B KWH Savings
  • 7.5B kg less CO2

Do your part

Upgrade to PHP 7!

Static Analysis



github.com/phan/phan

Install with composer

$ composer require --dev phan/phan

Create .phan/config.php

return [
    'target_php_version' => '7.2',
    'directory_list' => [ 'src/' ],
    "exclude_analysis_directory_list" => [ 'vendor/' ],
];
$ ./vendor/bin/phan

Checks

  • enhanced phpdoc type annotations
  • Everything is defined and accessible
  • Type safety
  • PHP version compatibility
  • No-ops
  • Unreachable code
  • Unused use statements
  • Redefinitions
  • Signature compatibility and final on inheritance
  • and many more

Enhanced PHPDoc type annotations

class C {
  /**
   * @param string|int $union
   * @param int[] $generic
   * @param array{mode:string,max:int} $shaped
   */
  static function fn($union, array $generic, $shaped) { }
}
C::fn("test", [1,2,3], ['mode'=>"test", 'max'=>10]);
C::fn(1, [1,2,3], ['mode'=>"test", 'max'=>10]);
C::fn("test", [1,2,3], ['max'=>10,'mode'=>"test"]);

C::fn([1], [1,2,3], ['mode'=>"test", 'max'=>10]);
// PhanTypeMismatchArgument Argument 1 (union) is array{0:1}
// but \C::fn() takes int|string

C::fn("test", [1,2,3], ['max'=>10]);
// PhanTypeMismatchArgument Argument 3 (shaped) is array{max:10}
// but \C::fn() takes array{mode:string,max:int}

User plugins

Writing Plugins for Phan

Included sample plugins

  • Demo Plugin
  • Preg Regex Checker
  • Printf Checker
  • Unreachable Code
  • DollarDollar
  • NonBool Branch
  • Numerical Comparison
  • Has PHPDoc
  • Duplicate Expression

Daemon mode

$ phan --daemonize-tcp-port default &
[1] 28610
Listening for Phan analysis requests at tcp://127.0.0.1:4846
Awaiting analysis requests for directory '/home/rasmus/phan_demo'

$ vi src/script.php
$ phan_client -l src/script.php
Phan error: TypeError: PhanTypeMismatchArgument: Argument 1 (union) is array{0:1} but \C::fn() takes int|string defined at src/script.php:8 in src/script.php on line 14
Phan error: TypeError: PhanTypeMismatchArgument: Argument 3 (shaped) is array{max:10} but \C::fn() takes array{mode:string,max:int} defined at src/script.php:8 in src/script.php on line 16

vim integration

phpspy

Low-overhead sampling profiler

https://github.com/adsr/phpspy

Sample frequency in nanoseconds (or Hz)

$ phpspy -s 200000000 -- php -r 'sleep(1);' 
0 sleep <internal>:-1
1 <main> <internal>:-1

0 sleep <internal>:-1
1 <main> <internal>:-1

0 sleep <internal>:-1
1 <main> <internal>:-1

0 sleep <internal>:-1
1 <main> <internal>:-1

0 sleep <internal>:-1
1 <main> <internal>:-1

process_vm_readv: No such process

Attach to a running process

$ sudo phpspy -r -p $(pgrep -n php-fpm)

0 wp_installing /var/www/wordpress/wp-includes/load.php:944
1 wp_load_alloptions /var/www/wordpress/wp-includes/option.php:189
2 get_option /var/www/wordpress/wp-includes/option.php:90
3 create_initial_taxonomies /var/www/wordpress/wp-includes/taxonomy.php:43
4 WP_Hook::apply_filters /var/www/wordpress/wp-includes/class-wp-hook.php:286
5 WP_Hook::do_action /var/www/wordpress/wp-includes/class-wp-hook.php:310
6 do_action /var/www/wordpress/wp-includes/plugin.php:453
7 <main> /var/www/wordpress/wp-settings.php:450
8 <main> /var/www/wordpress/wp-config.php:89
9 <main> /var/www/wordpress/wp-load.php:37
10 <main> /var/www/wordpress/wp-blog-header.php:13
11 <main> /var/www/wordpress/index.php:17
# 1537119612.459615 /index.php p=1 /var/www/wordpress/index.php -

0 mysqli_query <internal>:-1
1 wpdb::_do_query /var/www/wordpress/wp-includes/wp-db.php:1924
2 wpdb::query /var/www/wordpress/wp-includes/wp-db.php:1813
3 wpdb::get_results /var/www/wordpress/wp-includes/wp-db.php:2488
4 _prime_comment_caches /var/www/wordpress/wp-includes/comment.php:2871
5 WP_Comment_Query::get_comments /var/www/wordpress/wp-includes/class-wp-comment-query.php:427
6 WP_Comment_Query::query /var/www/wordpress/wp-includes/class-wp-comment-query.php:346
7 get_comments /var/www/wordpress/wp-includes/comment.php:226
8 WP_Widget_Recent_Comments::widget /var/www/wordpress/wp-includes/widgets/class-wp-widget-recent-comments.php:99
9 WP_Widget::display_callback /var/www/wordpress/wp-includes/class-wp-widget.php:372
10 dynamic_sidebar /var/www/wordpress/wp-includes/widgets.php:743
11 <main> /var/www/wordpress/wp-content/themes/twentyfifteen/sidebar.php:41
12 load_template /var/www/wordpress/wp-includes/template.php:688
13 locate_template /var/www/wordpress/wp-includes/template.php:647
14 get_sidebar /var/www/wordpress/wp-includes/general-template.php:110
15 <main> /var/www/wordpress/wp-content/themes/twentyfifteen/header.php:49
16 load_template /var/www/wordpress/wp-includes/template.php:688
17 locate_template /var/www/wordpress/wp-includes/template.php:647
18 get_header /var/www/wordpress/wp-includes/general-template.php:41
19 <main> /var/www/wordpress/wp-content/themes/twentyfifteen/single.php:10
20 <main> /var/www/wordpress/wp-includes/template-loader.php:74
21 <main> /var/www/wordpress/wp-blog-header.php:19
22 <main> /var/www/wordpress/index.php:17
# 1537119612.459615 /index.php p=1 /var/www/wordpress/index.php -

Memory usage on stack frames

$ sudo phpspy -m php src/phan.php

0 Phan\Analysis::parseNodeInContext /home/rasmus/phan/src/Phan/Analysis.php:176
1 Phan\Analysis::parseNodeInContext /home/rasmus/phan/src/Phan/Analysis.php:176
2 Phan\Analysis::parseNodeInContext /home/rasmus/phan/src/Phan/Analysis.php:176
3 Phan\Analysis::parseNodeInContext /home/rasmus/phan/src/Phan/Analysis.php:176
4 Phan\Analysis::parseFile /home/rasmus/phan/src/Phan/Analysis.php:63
5 Phan\Phan::analyzeFileList /home/rasmus/phan/src/Phan/Phan.php:94
6 <main> /home/rasmus/phan/src/phan.php:1
# mem 119159776 123721960

0 ast\parse_code <internal>:-1
1 Phan\AST\Parser::parseCode /home/rasmus/phan/src/Phan/AST/Parser.php:42
2 Phan\Analysis::parseFile /home/rasmus/phan/src/Phan/Analysis.php:63
3 Phan\Phan::analyzeFileList /home/rasmus/phan/src/Phan/Phan.php:94
4 <main> /home/rasmus/phan/src/phan.php:1
# mem 82471616 123721960

perf/callgrind output support soon, hopefully

Generate a flame graph

$ phpspy phan > /tmp/output
$ cat /tmp/output | stackcollapse-phpspy.pl | flamegraph.pl > flame.svg
Use a newer browser, please

PHP 7.4

Typed Properties

class User {
    public int $id;
    public string $name;
 
    public function __construct(int $id, string $name) {
        $this->id = $id;
        $this->name = $name;
    }
}

Null Coalescing Assignment Operator

$this->config['value']   = $this->config['value'] ?? 'default_value';
$this->config['value'] ??= 'default_value';

Without Opcache Preloading

class A {
    function __construct() {
        echo "A";
    }
}
spl_autoload_register('__load');
function __load($c) {
    echo "Autoloader called for $c\n";
    require "/home/rasmus/".strtolower($c).".php";
}

new A;
$ php script.php 
Autoloader called for A
A

With Opcache Preloading

function preload($filename) {
    if (!opcache_compile_file($filename)) {
        trigger_error("Preloading Failed", E_USER_ERROR);
    }
}

preload("/home/rasmus/a.php");
$ php -d opcache.preload=preload.php script.php 
A

FFI - Foreign Function Interface

// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef(
    "int printf(const char *format, ...);",
    "libc.so.6");
// call C printf()
$ffi->printf("Hello %s!\n", "world");
<?php
    $ffi = FFI::load("php_gifenc.h");

    $w = 240; $h = 180;
    $cols = $ffi->new("uint8_t[12]");
    /* 4 colours: 000000, FF0000, 00FF00, 0000FF */
    $cols[3] = 0xFF; $cols[7] = 0xFF; $cols[11] = 0xFF;

    $gif = $ffi->ge_new_gif("test.gif", $w, $h, $cols, 2, 0);

    for($i = 0; $i < 16; $i++) {
        for ($j = 0; $j < $w*$h; $j++) {
            $gif->frame[$j] = ($i*6 + $j) / 12 % 8;
        }
        echo "Add frame $i\n";
        $ffi->ge_add_frame($gif, 5);
    }
    $ffi->ge_close_gif($gif);
#define FFI_SCOPE "gifenc"
#define FFI_LIB "libgifenc.so"

typedef struct ge_GIF {
    uint16_t w, h;
    int depth;
    int fd;
    int offset;
    int nframes;
    uint8_t *frame, *back;
    uint32_t partial;
    uint8_t buffer[0xFF];
} ge_GIF;

ge_GIF *ge_new_gif(
    const char *fname, uint16_t width, uint16_t height,
    uint8_t *palette, int depth, int loop
);
void ge_add_frame(ge_GIF *gif, uint16_t delay);
void ge_close_gif(ge_GIF* gif);

Thank You

http://talks.php.net/drupalcamp
https://github.com/phan/phan
https://github.com/adsr/phpspy
https://github.com/php/php-src/blob/PHP-7.3/UPGRADING
https://bugs.php.net



Report Bugs

Useful bug reports, please!