last train to php 7
TRANSCRIPT
Last Train to PHP 7.1Damien Seguy @exakat
London, United Kingdom, February 2017
Full ahead to PHP 7. 2
Changing version is always a big challenge
Backward incompatibilities
New features
Learn, spot, fix, repeat
01
Speaker
Damien Seguy
CTO at exakat
Static code analysis for PHP
Retiring house for oldest elephpant
Synopsis
What is in the next version ?
Where in my code ?
How to replace this ?
Get documentation
Find issue
Fix code
Migration to PHP 7.2
Migration to PHP 7.0, 7.1 and 7.2 / 8.0
From PHP 5.6
No framework, no libraries
Tools, experience, discipline
Destination
Living on the edge
http://php.net/manual/en/migration71.php
Online
UPGRADING TO PHP 7.0
Free Book, PDF
Davey Shafik is RM for PHP 7.1
Lots of blogs and articles
Living on the bleeding edge
https://github.com/php/php-src/blob/master/UPGRADING
https://github.com/php/php-src/blob/master/NEWS
https://wiki.php.net/rfc
http://bugs.php.net/
62 opened discussions
3 opened votes
Which to believe
Books
PHP manual Migration, blogs
Wiki (Implemented)
NEWS
Bugs
Wiki (Future)
Yearly
Monthly
Weekly
As possible
As needed
Where does code break?
Checked with phplint
Checked with tests
Checked code review
PHP has 3 phases
syntax
definitions
execution
<?php
function splitNames($fullname, $fullname) { list($first, $last) = split($fullname, ' '); } echo splitNames('John Doe');?>
Where will code break?
SyntaxDefinitionsExecution
Tools for migration
• Your own experience with your code
• Lint
• Search
• Static analysis
• Logs
Linting PHP 7
PHP linting
• command line : php -l filename.php
• Spot parse errors
• Works on files only :
• For directories, see composer phplint/phplint
PHP linting
Error messagesin PHP
0
550
1100
1650
2200
5.0 5.1 5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
Total Distinct
Backward unlintable code
0
1
2
3
4
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
Current versions code
0
1.25
2.5
3.75
5
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
Backward unlintable code
0
0.25
0.5
0.75
1
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
0
1.25
2.5
3.75
5
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
0
1
2
3
4
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
0
0.75
1.5
2.25
3
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
0
1.75
3.5
5.25
7
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
0
0.25
0.5
0.75
1
5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
php -l with other versions
syntax error, unexpected 'new' (T_NEW)
Assigning the return value of new by reference is deprecated (PHP 5.6)
PHP 7 -> PHP 5.6
$o =& new Stdclass();
Redefinition of parameter
<?php
function foo($a, $a, $a) { print $a."\n"; }
foo('x', 'y', 'z');
?>
Switch statements may only contain one default clause
<?php
switch($x) { case '1' : break; default : break; default : break; case '2' : break; }
Switch statements may only contain one default clause
switch($x) { case 1 : break; case 0+1 : break; case '1' : break; case true : break; case 1.0 : break; case $y : break; }
Deprecated features
Not happening if a parent case has a __construct()
Not happening if the class is in a namespace
Use the E_DEPRECATED error level while in DEV
Methods with the same name as their class will not be constructors in a future version of PHP; foo has a deprecated constructor
Migration to PHP 7.2
Lint with
PHP 7.1
PHP 7.2 (or src)
PHP 5.6 (current), PHP 7.0
PHP 5.5, 5.4,… (manual)
PHP 7 linting
Pre-commit
Use different versions
Be ruthless with unlintable files
Where does code break?
Checked with phplint
Checked with tests
Checked code review
PHP has 3 phases
syntax
definitions
execution
Static analysis
Definition
Review code without executing it
Audit code by reading it
Common in C/C++, Java, Javascript
Hot subject to PHP
GREP/SEARCH
Any searching facility
Pro : High speed, great for keyword search, universal
Cons : Little repeat value, no PHP semantics
Grep on PHP code
1318 reports
doc/_ext/configext.py: parts = text.split("']['") js/codemirror/lib/codemirror.js: var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; po/zh_CN.po:"example: address can be split into street, city, country and zip."
libraries/Advisor.php: public static function splitJustification($rule) libraries/plugins/ImportCsv.php: $tmp = preg_split('/,( ?)/', $csv_columns); libraries/Config.php: // split file to lines
Static analysis
PHP 5 / 7 Calisthenics ClearPHP
Performance
Static analysis tools
PHP7mar
PHP7cc
Phan
Exakat
PHP inspections
PHP7mar
PHP 7 Migration Assistant Report (MAR)
Alexia : https://github.com/Alexia/php7mar
Works with regex
Produces a .md file
12 results
PHP7cc
PHP 7 Compatibility Checker
Authored by sstalle
https://github.com/sstalle/php7cc
PHP 5, works with "nikic/php-parser": "~1.4"
Display to stdout
8.506s 3 results 27 analysis
php ~/.composer/vendor/bin/php7cc library/
File: /Users/famille/Desktop/analyze/library/Analyzer/Analyzer.php> Line 231: Function argument(s) returned by "func_get_args" might have been modified func_get_args();
File: /Users/famille/Desktop/analyze/library/Analyzer/Functions/MarkCallable.php> Line 32: Nested by-reference foreach loop, make sure there is no iteration over the same array foreach ($lists as $id => &$function) { }
File: /Users/famille/Desktop/analyze/library/Tasks/Analyze.php> Line 118: Possible adding to array on the last iteration of a by-reference foreach loop $dependencies[$v] = $dep;
Checked 873 files in 8.506 seconds
PHAN
Static analysis for PHP
Inited by Rasmus, under work at Etsy
https://github.com/etsy/phan
PHP 7 only, with ext/ast
php ~/.composer/vendor/bin/phan -f phan.in -3 vendor -o phan.out
11.244s 333 results
PhanUndeclaredProperty Reference to undeclared property processedPhanUndeclaredProperty Reference to undeclared property \stdclass->results
PhanNonClassMethodCall Call to method relateTo on non-class type null
PhanStaticCallToNonStatic Static call to non-static method \loader\cypher::saveTokenCounts() defined at library//Loader/Cypher.php:179PhanAccessPropertyProtected Cannot access protected property \tokenizer\token::$alternativeEnding
PhanTypeMismatchArgument Argument 1 (atom) is string but \analyzer\structures\useconstant::atomfunctionis() takes array defined at library//Analyzer/Analyzer.php:415
PhanUndeclaredClassMethod Call to method __construct from undeclared class \reports\xmlwriterPhanUndeclaredVariable Variable $r is undeclared
84 analysis
Exakat
Static analysis engine for PHP
https://github.com/exakat/exakat
PHP 5.2 to 7.2;
php exakat.phar project -p name
20 mins6798 results290 analysis
Exakat
PHP inspections
Static analysis engine for within the IDE
Vladimir Reznichenko
https://bitbucket.org/kalessil/phpinspectionsea
Written in Java
Runs from within PHPstorm
Write your own?
https://github.com/exakat/php-static-analysis-tools.git
PHP 7 : use ext/ast
PHP 5 :"nikic/php-parser"
Better Reflexion
Avoid regex (but it does work)
PHP 7.2 : what changes?
Incompatible changes
New features
Features/Incompatibilities from PHP 5.6 => 7.2
How to spot issues?
Code knowledge
lint
Grep / Search
Static analysis
Logs / error_reporting
Unit Tests
Incompatibilities
Incompatibilities
Removed features
Added features
Collateral damages
Removed features
Removed extensions
Extensions
ereg
mssql
mysql
sybase_ct
mcrypt 7.2
Removed extensions
ext/ereg
ereg
ereg_replace
split
sql_regcase
Moved utf8_encode() and utf8_decode() to the Standard extension7.2
Removed functions
call_user_method()
call_user_method_array()
Repleacable by $funcname
Replaced by call_user_func() and call_user_func_array()
Partially replaced by variadic
png2wbmp() and jpeg2wbmp()
Deprecated
7.2
Removed variables
$HTTP_RAW_POST_DATA
Replace it by php://input
php://input is now reusable
Since PHP 5.5
Removed INI
sql.safe_mode (PHP 7.2)
mbstring.internal_encoding
mbstring.http_output
mbstring.http_input
iconv.internal_encoding
iconv.input_encoding
iconv.output_encoding
default_charset}
default_charset
htmlentities()
PHP 5.3 : ISO-8859-1
PHP 5.4 : UTF-8
PHP 5.6 : default_charset (also UTF 8)
Where to look for ?
default_charset
Search for ini_set(), ini_get(), ini_get_all(), ini_restore(), get_cfg_var()
Search in php.ini, .htaccess
Search for htmlentities(), html_entity_decode() and htmlspecialchars()
Preg_replace and /e
preg_replace(‘/ /e’, ‘evaled code’, $haystack)
replaced by
preg_replace_callback_array()
preg_replace(‘/ /e’, ‘evaled code’, $haystack)
preg_replace_callback_array([‘/ /’ => $closure], $haystack)
preg_replace_callback(‘/ /’, $closure],
$haystack)
preg_replace_callback_array
<?php
$code = "abbbb";
$spec = 'c';
echo preg_replace_callback_array( array( "/a/" => function($matches) { return strtoupper($matches[0]); }, "/b/" => function($matches) use ($spec) { static $i = 0; $i++;
return "B$i$spec"; } ), $code);
AB1cB2cB3cB4c
preg_replace()
<?php
$code = "abcde";
echo preg_replace( array( '/a/', '/b/'), array( 'f' , 'g'), $code);
fgcde
Can't call dynamically!
•$func() •call_user_func() •array_map() •or similar •assert() with a string argument
extract()
compact()
get_defined_vars()
func_get_args()
func_get_arg()
func_num_args()
parse_str() with one argument
mb_parse_str() with one argument
Added features
Added definitions
Functions Classes Constants5.3 40 2 805.4 0 9 785.5 12 11 575.6 1 10 107.0 10 10 417.1 9 3 307.2 4 0 15
Total 766 92 1163
Name impact
get_resources(), intdiv(), is_iterable(), mb_scrub()
PREG_JIT_STACKLIMIT_ERROR
class Date (from PHP 5.1)
Error (new class in PHP 7)
New functions
intdiv()
get_resources()
random_bytes(), random_int()
error_clear_last()
gc_mem_caches()
preg_replace_callback_array()
socket_getaddrinfo()
mb_ord, mb_scrub,
Collaterals
Invalid octals are invalid
Upgraded from silent to Fatal error
PHP Parse error: Invalid numeric literal in test.php
<?php
$x = 0890;
More invalid octals in strings
<?php
var_dump("\000" === "\400");
https://wiki.php.net/rfc/octal.overload-checking
More reserved keywords
bool, int, float, string, null, true, false are no more available for class / interface / traits names
mixed, numeric, object, resource are reserved for future use
void is reserved in 7.1
More relaxed keywords
<?php class foo { const instanceof = 1; function use() { $this->while(4) + foo::instanceof; } }
Strings may be invalid
<?php
echo "\u{1F418}\n";
> php56 test.php \u{1F418}
> php70 test.php
🐘
<?php
echo "\u{65B0}\u{52A0}\u{5761}\n"; //
Strings may be invalid
<?php
echo "\u{Yes}\n";
PHP Parse error: Invalid UTF-8 codepoint escape sequence in test.php on line 3
\u{
Hexadecimal numeric strings
Also, -0 !!
<?php
var_dump(1 + 0xf); var_dump(1 + "0xf");
$ php56 test.php int(16) int(16)
$ php70 test.php int(16) int(1)
Warning for strings (7.1)
<?php
print "2" + "4"; print "3 elephpants" + "4 dolphins"; print "2" + "d4 d";
6 7 2
Warning: A non-numeric value encountered
Exceptions
Throwable
Exception
LogicException RuntimeException
BadFunctionCallException
BadMethodCallException
DomainExceptionInvalidArgumentException
OutOfRangeException
OutOfBoundsException
OverflowException
RangeException
Error
ParseErrorDivisionByZeroError
AssertionError
Exceptions
\Exception is not the top exception type anymore
It is now the 'throwable' interface
Impact on Exception handler
Avoid type hinting until moved to PHP 7
Impact on Error handler
Impact on catch() clauses
More catching exceptions
<?php
try { eval($somePHPcode); } catch( ParseError $e) { log($e->getMessage()); // attempt to fix this or error handling }
More catching exceptions
Parser errors now throw a ParseError object. Error handling for eval()
<?php
try { $file = new finfo(FILEINFO_NONE,$magic_file); } catch( ParseError $e) { log($e->getMessage()); // attempt to fix this or error handling }
<?php
try { $random = random_bytes($size); } catch( TypeError $e) { // invalid parameter } catch( Error $e) { // invalid length } catch( Exception $e) { // no source of randomness }
And more catching exceptions
Even more catching exceptions
<?php
try { attemptSomething(); } catch (RuntimeException $e) { fixSomething(); } catch (InvalidArgumentException $e) { fixSomething(); } catch (BadFunctioncallException $e) { fixSomething(); }
Even more catching exceptions
Parser errors now throw a ParseError object. Error handling for eval()
<?php
try { attemptSomething(); } catch (RuntimeException| InvalidArgumentException| BadFunctioncallException $e) { fixSomething(); }
Even more catching exceptions
<?php
//try really hardertry { attemptSomething(); } catch (Exception $e) { attemptSomething(); }
Negative string offset (7.1)
<?php
$string = "abcde";
print $string[-3];
print "$string[3]";
print "$string[-2]";
c
d
d
list() with keys
Upgraded to Fatal error
<?php $array = ['a' => 1, 'b' => 5, 'c' => 3];
// Assigns to $a, $b and $c in the same orderlist($a, $b, $c) = array_values($array);
// Assigns to $a, $b and $c from the keys //"a", "b" and "c", respectively list("a" => $a, "c" => $c, "b" => $b) = $array;list("a" => $a, "b" => $b, "c" => $c) = $array;list("c" => $c, "a" => $a, "b" => $b) = $array;
list() with keys
<?php $array = ['b' => 5, 'a' => 1, 'c' => 3]; ksort($array); $array = array_values($array);
// Assigns to $a, $b and $c in the same order list($a, $b, $c) = $array;
// SELECT id, * FROM users list($userId, $name, $last) = array_values($row);
Short syntax for list()
<?php
$array = ['a' => 1, 'b' => 5, 'c' => 3];
["a" => $b, "c" => $c, "b" => $a] = $array;
// Works even when nested $array = [['a' => 1, 'b' => 5], ['c' => 3]];
[["a" => $a, "b" => $b], ["c" => $c]] = $array;
Call-time pass-by-reference
References are in the function signature
Deprecated warnings until PHP 7
Upgraded to Parse error in PHP 7
<?php
$a = 3;
function f($b) { $b++; }
f(&$a); print $a; ?>
PHP Parse error: syntax error, unexpected '&' in …
Incompatible context
<?php class A { function f() { echo get_class($this); } } A::f(); ?>
Notice: Undefined variable: $this in A
Deprecated: Non-static method A::f() should not be called statically inNotice: Undefined variable: $this in A
Easy to spot
Strict Standards: Non-static method A::f() should not be called statically in test.php on line 6
Deprecated: Non-static method A::f() should not be called statically in test.php on line 6
$this is for classes<?php class bar{ function foo($this) { static $this; global $this;
foreach ($a as $this) { try { $a = "this"; $$a = 42; } catch (Exception $this) {} } unset($this); } }
Changed behavior
Changed behavior
Indirect expressions
func_get_arg()
func_get_arg() and func_get_args() now return the current argument values
<?php
function foo($a, $b, $c) { print_r(func_get_args()); ++$a; print_r(func_get_args()); } foo(1,2,3);
Array ( [0] => 1 [1] => 2 [2] => 3 ) Array ( [0] => 2 [1] => 2 [2] => 3 )
Usort()
<?php
$array = array( 'foo', 'bar', 'php' );
usort($array, function($a, $b) { return 0; } );
print_r($array);
Array ( [0] => php [1] => bar [2] => foo )
Array ( [0] => foo [1] => bar [2] => php )
PHP 5
PHP 7
Automatically fixed
It is not safe to rely on the system's timezone settings. You are required to use the
date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are
still getting this warning, you most likely misspelled the timezone identifier.
New features
New features
Breaks backward compatibility sometimes
FUD
Search for places to apply them like for incompatibilities
New features
Fixing
Modernization
New features
Fixing
Don't hide in parentheses
<?php function getArray() { return [1, 2, 3]; }
function squareArray(array &$a) { foreach ($a as &$v) { $v **= 2; } }
// Generates a warning in PHP 7. squareArray((getArray())); ?>
Parenthesis in arguments won't mask error anymore
Constant arrays
Lots of properties could be turned to constants
<?php class Version { const SUPPORTED = ['1.0', '1.1', '2.0', '2.1']; private $an_array = [1,2,3,4];
public function isSupported($x) { return isset(Version::SUPPORTED[$x]); } }
Constant visibility
<?php class Version { private const BUILD = 123; public const VERSION = '1.2.2'; }
echo Version::UNDEFINED; echo Version::BUILD; echo Version::VERSION;
Constant arrays
Lots of properties could be turned to constants
<?php class Version { const SUPPORTED = ['1.0', '1.1', '2.0', '2.1']; private $an_array = [1,2,3,4];
public function isSupported($x) { return isset(Version::SUPPORTED[$x]); } }
Modernization
Foreach update the actual array
<?php
$array = [0]; foreach ($array as $k => &$val) { print "$k\n"; $array[] = 1; // Only for pushing, not unshifting }
0 1 2 3 4 5 6 7 8 9 10 11
0
Array/Object casting
<?php
$arr = [0 => 1, 1 => 2, 2 => 3]; $obj = (object)$arr;
// PHP 7.1 echo $obj->{"0"};
// PHP 7.2 echo $obj->{0};
7.2
Warn with COUNT()
<?php function handle_records(iterable $iterable) { if (count($iterable) === 0) { return handle_empty(); } foreach ($iterable as $value) { handle_value($value); } }
7.2
Closure binding
<?php class Hello { private $hello = "Hello";
function makeClosure() { return function() { echo $this->hello; }; }
$obj = new Hello(); $closure = $obj->makeClosure(); $closure();
Closure binding
<?php
class {
private $hello = " ";
}
$obj = new Hello(); $closure = $obj->makeClosure();
$nihao = new ();
$closure2 = $closure->bindTo($nihao); $closure2();
Request the closure from the object
Closure binding
<?php $closure = function() { echo $this->hello; };
class {
private $hello = " ";
}
$nihao = new ();
$closure->call($nihao);
Bind the object at the last moment
session_start($options)
Very Cute <=>
Replaces a lot of code
Mainly useful for usort()
<?php
// PHP 5.6 ini_set('session.name','session'); ini_set('session.gc_probability',1); ini_set('session.gc_divisor',1); session_start();
// PHP 7.0 session_start(['name' => 'session', 'gc_probability' => 1, 'gc_divisor' => 1 ] );
dirname() second argument
<?php $path = '/a/b/c/d/e/f';
// PHP 5.6 $root = dirname(dirname(dirname($x)));
// PHP 7 $root = dirname($path, 3); ?>
Parameters evolution
get_headers() has an extra parameter
Passing a custom stream context
getenv() doesn't need parameter
all the current environment variables will be returned
get_class() doesn't allow null anymore
Really new
Null-coalesce
Shorter way to give a test for NULL and failover
<?php
// PHP 5.6 $x = $_GET['x'] === null ? 'default' : $_GET['x'];
// PHP 7.0 $x = $_GET['x'] ?? 'default';
?>
Spaceship operator
Very Cute <=>
Replaces a lot of code
Mainly useful for usort()
<?php
// PHP 5.6 if ($a > $b) { echo 1; } elseif ($a < $b) { echo -1; } else { echo 0; }
// PHP 7.0 echo $a <=> $b; // 0
Generators delegation
New yield keyword
Save memory from
n down to 1 value
Good for long or infinite loops
Search for range(), for() or loops
<?php function factors($limit) { yield 2; yield 3;
yield from primeTill1000();
for ($i = 1001; $i <= $limit; $i += 2) { yield $i; } }
$prime = 1357; foreach (factors(sqrt($prime)) as $n) { echo "$n ". ($prime % $n ? ' not ' : '') . " factor\n"; }
Generators returns<?php function factors($limit) { return 'first'; yield 2; return 'second'; yield 3; return 'third'; yield 5; } $gen = factors(sqrt($prime)); foreach ($gen as $n) { echo "$n\n"; if ($n == 3) {break 1;} } print $gen->getReturn(); // second
Generators returns
The last return is accessible
The generator returns the final state
Scalar typehint
Whenever type is tested =>
<?php
function foo($x) { if (!is_string($x)) { throw new Exception('Type error while calling ' . __FUNCTION__); } ... }
<?php function foo(string $x) { ... }
Scalar typehint back in 5.6
Whenever type is tested =>
<?php
function foo(string $x) { } foo('that');
Catchable fatal error: Argument 1 passed tofoo() must be an instance of string, string given, called in file..
Various scalar typehint
int, float
string, bool
true, false, null
void (PHP 7.1)
mixed, object, resource, numeric (RFU)
<?php
function foo(?int $a, float $b, float $c) : ?int { return $a + $b + $c; }
echo foo(null, 2, 1); // 4 echo foo(1.2, 2, 1); // 4 echo foo(1, 2.2, 1); // 4 echo foo(1, 2.7, 1.4); // 5
Option for strict typing
<?php // Enable strict types declare(strict_types=1);
declare(encoding='ISO-8859-1'); declare(ticks=1);
namespace Foo\Bar; foo('that');
Return type hint
<?php
function getData($login) : user { if (userExists($login)) { return userDetails($login); } else { return null; } }
Minimum args in custom functions is Fatal error
<?php
function foo(?int $a, float $b, float $c) { return $a + $b + $c; }
echo foo(2, 1);
Minimum args number Fatal error: Uncaught Error: Too few arguments to function foo(), 2 passed in
Helping at migration
Document the evolutions between two versions
Identify anchors in the code
Link the migration to the actual code
Keywords, syntax, code structures
Suggest fallback, work-around, detection tools
Helping at migration
Backward incompatibilities
Removed / renamed features
Collaterals
Changed behaviors
Helping at migration
New features
Fixing
Totally new
Spot previous work-arounds
Summary
Check the manuals
PHP lint is your friend
Search in the code
Use static analysis tools
Thank you!Damien Seguy @exakat [email protected]
https://www.exakat.io/
https://joind.in/event/php-uk-2017/schedule/list
The end