Php 7 compliance workshop singapore

Post on 13-Apr-2017

221 views 1 download

Transcript of Php 7 compliance workshop singapore

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 dseguy@exakat.io

https://www.exakat.io/

The end