preparing code for php 7 workshop
TRANSCRIPT
Preparing for PHP 7.1 workshop
Damien Seguy @exakat Warsaw, Poland, October 2016
Full ahead to PHP 7. 1
• Changing version is always a big challenge
• Backward incompatibilities
• New features
• Learn, spot, fix, repeat
0
Speaker
• Damien Seguy
• CTO at exakat
• Static code analysis for PHP
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.1
• 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/migration70.php
• Online
• UPGRADING TO PHP 7
• 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/
• PHP has 3 phases
• syntax
• definitions
• execution
Where does code break?
Checked with phplint
Checked with tests
Checked code review
<?php
function splitNames($fullname, $fullname) { list($first, $last) = split($fullname, ' '); }
?>
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
Code focused on current versions
Backward unlintable code
Redefinition of parameter
<?php
function foo($a, $a, $a) { print $a."\n"; }
foo('x', 'y', 'z');
?>
Switch statements 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 __constructor()
• 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
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();
Migration to PHP 7.1
• 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
• PHP has 3 phases
• syntax
• definitions
• execution
Where does code break?
Checked with phplint
Checked with tests
Checked code review
Static analysis
Presentation
• Review code without executing it
• Common in C/C++, Java, Javascript
• Hot subject coming 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 resultsPhanUndeclaredProperty 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; Uses Neo4j 2.3 and Gremlin 3
• php exakat.phar project -p name
20 mins6798 results260 analysis
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"
• Avoid regex (but it does work)
PHP 7 : 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
What to replace them with ?
Incompatibilities
Incompatibilities
• Removed features
• Added features
• Collateral damages
Removed features
Removed extensions
• Extensions
• ereg
• mssql
• mysql
• sybase_ct
• mcrypt (7.1)
Removed extensions
• ext/ereg
• ereg
• ereg_replace
• split
• sql_regcase
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
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
mb_eregi?_replace also drops /e in 7.2
<?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
• 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 • assert() with a string 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 7 2 157.2 0 0 0
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()
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");
• PHP 7.1
https://wiki.php.net/rfc/octal.overload-checking
Invalid numeric are signaledPHP 7.1
Notice: A non well formed numeric value encountered in
<?php
echo "1 monkey" + "2 bananas";
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
• Almost all PHP keywords are now authorized inside classes
• Methods and constants
• Except for class, which can't be a class constant name.
<?php class foo { const instanceof = 1; function use() { $this->while(4) + foo::instanceof; } }
More strict keywords
• $this is not available anymore
• Not as argument
• Not as variable
• Not as global
• Not as static
• Not as property
• Not as reference
• Not via extract()
<?php
function foo($this) { echo $this; }
Strings may be invalid
• Upgraded to Fatal error
<?php
echo "\u{1F418}\n";
> php56 test.php \u{1F418}
> php70 test.php
🐘
<?php
echo "ni\u{119} m\u{f3}wi\u{119} p\u{f3} po\u{142}\u{15b}";
//nię mówię pó połśku
Strings may be invalid
• Upgraded to Fatal error
<?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)
Upgraded to Fatal error
<?php
print "2" + "4"; print "3 elephpants" + "4 dolphins"; print "2" + "d4 d";
6 7 2
Warning: A non-numeric value encountered
Exceptions
• Upgraded to Fatal errorThrowable
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
• Parser errors now throw a ParseError object. Error handling for eval()
<?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 }
And more catching exceptions
Parser errors now throw a ParseError object. Error handling for eval()
<?php
try { $random = random_bytes(10); } catch( TypeError $e) { // invalid parameter } catch( Error $e) { // invalid length } catch( Exception $e) { // no source of randomness }
Even more catching exceptions
<?php
try { attemptSomething(); } catch (RuntimeException $e) { fixSomething(); } catch (InvalidArgumentException $e) { fixSomething(); } catch (BadFunctioncallException $e) { fixSomethingElse(); }
Even more catching exceptionsFederate error catching in catch clause
<?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)
• Upgraded to Fatal error<?php
$string = "abcde";
print $string[-3];
print "$string[3]";
print "$string[-2]";
c
d
Parse error: syntax error, unexpected '-', expecting identifier (T_STRING) or variable (T_VARIABLE) or number (T_NUM_STRING)
PHP 7.1 : list() with keys =>
<?php $array = ['a' => 1, 'b' => 5, 'c' => 3];
// Assigns to $a, $b and $c in the same orderlist($a, $b, $c) = $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;
Short syntax for list()
<?php
$array = ['a' => 1, 'b' => 5, 'c' => 3];
["a" => $a, "b" => $b, "c" => $c] = $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
• Use the E_DEPRECATED or strict while in DEV
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
Changed behavior
Changed behavior
• Indirect expressions
func_get_arg()
• func_get_arg() and func_get_args() now return 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 should be 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; } ?>
0 1 2 3 4 5 6 7 8 9 10 11
0
PHP 5 PHP 7
Closure binding
• Code automation
• Keep it simple
• Won’t accept functioncalls
• Won't accept variables
<?php class Hello { private $hello = "Hello";
function makeClosure() { return function() { echo $this->hello; }; }
$obj = new Hello(); $closure = $obj->makeClosure(); $closure();
Closure binding
• Code automation
• Keep it simple
• Won’t accept functioncalls
• Won't accept variables
<?php
class {
private $hello = " ";
}
$obj = new Hello(); $closure = $obj->makeClosure();
$nihao = new ();
$closure2 = $closure->bindTo($nihao); $closure2();
Closure binding
• Code automation
• Keep it simple
• Won’t accept functioncalls
• Won't accept variables
<?php
$closure = function() { echo $this->hello; };
class {
private $hello = " ";
}
$nihao = new ();
$closure->call($nihao);
session_start($options)
<?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 (7.1)
• get_headers() has an extra parameter
• Passing a custom stream context
• getenv() doesn't need parameter
• all the current environment variables will be returned
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
<?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
Return type hint
<?php
function getData($login) : user { if (userExists($login)) { return userDetails($login); } else { return null; } }
• scalar, array, callable, class or interfaces
void (PHP 7.1)
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');
Minimum args in custom functions is Fatal error
scalar, array, callable, class or interfaces
void (PHP 7.1)
<?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
Omitted
• Assertions
• Grouped assertions
• opening tags that were dropped
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/
The end