php 7 compliance workshop singapore
TRANSCRIPT
PHP 7.1 compliance workshop
Damien Seguy @exakat Singapore, August 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 edgehttp://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 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 __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 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; Uses Neo4j 2.3 and Gremlin 3
php exakat.phar project -p name
20 mins6798 results250 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.1 : 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 extensionsExtensions
ereg
mssql
mysql
sybase_ct
mycrypt (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 INIsql.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
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 functionsintdiv()
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 signaled
PHP 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 keywordsAlmost 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; } }
Upgraded to Fatal error
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
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 error
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
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
<?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
<?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 exceptions
<?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
Parse error: syntax error, unexpected '-', expecting identifier (T_STRING) or variable (T_VARIABLE) or number (T_NUM_STRING)
list() with keys => (7.1)<?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
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
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
<?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; } }
scalar, array, callable, class or interfaces
void (PHP 7.1)
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
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