Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Last train to php 7

699 views

Published on

Everyone must migrate to PHP 7! Take advantage of exceptional performance improvements, cut your hardware use in half and enjoy the best of PHP. This workshop is for everyone that is still eyeing PHP 7 while still using PHP 5, and wants to review their 1 million LOC project before jumping to PHP 7. When migrating, we need to check old code and target only the interesting issues. This session will connect the backward incompatibilities and new features to their actual location in the code, relying on static analysis to quickly process a large code base. Based on our accumulated experience and tools, we'll review the issues, diagnose criticality, select the best fixes and prioritize the tasks. All tools are Open Source, and ready to be integrated into your project lifecycle.

Published in: Technology
  • Be the first to comment

Last train to php 7

  1. 1. Last Train to PHP 7.1 Damien Seguy @exakat London, United Kingdom, February 2017
  2. 2. Full ahead to PHP 7. 2 Changing version is always a big challenge Backward incompatibilities New features Learn, spot, fix, repeat 01
  3. 3. Speaker Damien Seguy CTO at exakat Static code analysis for PHP Retiring house for oldest 
 elephpant
  4. 4. Synopsis What is in the next version ? Where in my code ? How to replace this ? Get documentation Find issue Fix code
  5. 5. 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
  6. 6. Destination
  7. 7. 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
  8. 8. 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/
  9. 9. 62 opened discussions 3 opened votes
  10. 10. Which to believe Books PHP manual Migration, blogs Wiki (Implemented) NEWS Bugs Wiki (Future) Yearly Monthly Weekly As possible As needed
  11. 11. Where does code break? Checked with phplint Checked with tests Checked code review PHP has 3 phases syntax definitions execution
  12. 12. <?php function splitNames($fullname, $fullname) {   list($first, $last) = split($fullname, ' '); } echo splitNames('John Doe'); ?> Where will code break? SyntaxDefinitionsExecution
  13. 13. Tools for migration • Your own experience with your code • Lint • Search • Static analysis • Logs
  14. 14. Linting PHP 7
  15. 15. PHP linting • command line : php -l filename.php • Spot parse errors • Works on files only : • For directories, see composer phplint/phplint
  16. 16. PHP linting Error messages
 in 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
  17. 17. Backward unlintable code 0 1 2 3 4 5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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();
  22. 22. Redefinition of parameter <?php function foo($a, $a, $a) {   print $a."n"; } foo('x', 'y', 'z'); ?>
  23. 23. Switch statements may only contain one default clause <?php    switch($x) {        case '1' :             break;        default :             break;        default :             break;        case '2' :             break;    }   
  24. 24. 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;    }   
  25. 25. 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
  26. 26. 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)
  27. 27. PHP 7 linting Pre-commit Use different versions Be ruthless with unlintable files
  28. 28. Where does code break? Checked with phplint Checked with tests Checked code review PHP has 3 phases syntax definitions execution
  29. 29. Static analysis
  30. 30. Definition Review code without executing it Audit code by reading it Common in C/C++, Java, Javascript Hot subject to PHP
  31. 31. GREP/SEARCH Any searching facility Pro : High speed, great for keyword search, universal Cons : Little repeat value, no PHP semantics
  32. 32. Grep on PHP code 1318 reports doc/_ext/configext.py: parts = text.split("']['") js/codemirror/lib/codemirror.js: var change = {from: pos, to: p po/zh_CN.po:"example: address can be split into stre libraries/Advisor.php: public static function splitJu libraries/plugins/ImportCsv.php: $tmp = preg_split('/,( ?)/', $ libraries/Config.php: // split file to lines
  33. 33. Static analysis PHP 5 / 7 Calisthenics ClearPHP Performance     
  34. 34. Static analysis tools PHP7mar PHP7cc Phan Exakat PHP inspections
  35. 35. PHP7mar PHP 7 Migration Assistant Report (MAR) Alexia : https://github.com/Alexia/php7mar Works with regex Produces a .md file
  36. 36. 12 results
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. 11.244s 333 results PhanUndeclaredProperty Reference to undeclared property processed PhanUndeclaredProperty Reference to undeclared property stdclass->results PhanNonClassMethodCall Call to method relateTo on non-class type null PhanStaticCallToNonStatic Static call to non-static method loadercypher::saveTokenCounts() defined at libr PhanAccessPropertyProtected Cannot access protected property tokenizertoken::$alternativeEnding PhanTypeMismatchArgument Argument 1 (atom) is string but analyzerstructuresuseconstant::atomfunction PhanUndeclaredClassMethod Call to method __construct from undeclared class reportsxmlwriter PhanUndeclaredVariable Variable $r is undeclared 84 analysis
  41. 41. Exakat Static analysis engine for PHP https://github.com/exakat/exakat PHP 5.2 to 7.2; php exakat.phar project -p name
  42. 42. 20 mins6798 results290 analysis
  43. 43. Exakat
  44. 44. PHP inspections Static analysis engine for within the IDE Vladimir Reznichenko https://bitbucket.org/kalessil/phpinspectionsea Written in Java Runs from within PHPstorm
  45. 45. 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)
  46. 46. PHP 7.2 : what changes? Incompatible changes New features Features/Incompatibilities from PHP 5.6 => 7.2
  47. 47. How to spot issues? Code knowledge lint Grep / Search Static analysis Logs / error_reporting Unit Tests
  48. 48. Incompatibilities
  49. 49. Incompatibilities Removed features Added features Collateral damages
  50. 50. Removed features
  51. 51. Removed extensions Extensions ereg mssql mysql sybase_ct mcrypt 7.2
  52. 52. Removed extensions ext/ereg ereg ereg_replace split sql_regcase Moved utf8_encode() and utf8_decode() to the Standard extension 7.2
  53. 53. 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
  54. 54. Removed variables $HTTP_RAW_POST_DATA Replace it by php://input php://input is now reusable Since PHP 5.5
  55. 55. 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 }
  56. 56. default_charset htmlentities() PHP 5.3 : ISO-8859-1 PHP 5.4 : UTF-8 PHP 5.6 : default_charset (also UTF 8)
  57. 57. 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()
  58. 58. 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) 
  59. 59. 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
  60. 60. preg_replace() <?php   $code = "abcde";  echo preg_replace(      array( '/a/', '/b/'),      array( 'f' , 'g'),     $code); fgcde
  61. 61. 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
  62. 62. Added features
  63. 63. Added definitions Functions Classes Constants 5.3 40 2 80 5.4 0 9 78 5.5 12 11 57 5.6 1 10 10 7.0 10 10 41 7.1 9 3 30 7.2 4 0 15 Total 766 92 1163
  64. 64. 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)
  65. 65. 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,
  66. 66. Collaterals
  67. 67. Invalid octals are invalid Upgraded from silent to Fatal error PHP Parse error: Invalid numeric literal in test.php <?php  $x = 0890;
  68. 68. More invalid octals in strings <?php   var_dump("000" === "400"); https://wiki.php.net/rfc/octal.overload-checking
  69. 69. 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
  70. 70. More relaxed keywords <?php     class foo {    const instanceof = 1;    function use() {        $this->while(4) + foo::instanceof;    } }
  71. 71. 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"; //
  72. 72. 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{
  73. 73. 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)
  74. 74. 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
  75. 75. Exceptions Throwable Exception LogicException RuntimeException BadFunctionCall Exception BadMethodCall Exception DomainException InvalidArgument Exception OutOfRange Exception OutOfBou ndsExcep tion Overflow Exception RangeExcep tion Error ParseError DivisionBy ZeroError Assertion Error
  76. 76. 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
  77. 77. More catching exceptions <?php try {   eval($somePHPcode); } catch( ParseError $e) {    log($e->getMessage());   // attempt to fix this or error handling }
  78. 78. 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  }
  79. 79. <?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
  80. 80. Even more catching exceptions <?php try {    attemptSomething(); } catch (RuntimeException $e) {   fixSomething(); } catch (InvalidArgumentException $e) {   fixSomething(); } catch (BadFunctioncallException $e) {   fixSomething(); } 
  81. 81. Even more catching exceptions Parser errors now throw a ParseError object. Error handling for eval() <?php try {    attemptSomething(); } catch (RuntimeException|  InvalidArgumentException|  BadFunctioncallException $e) {   fixSomething(); } 
  82. 82. Even more catching exceptions <?php //try really harder try {    attemptSomething(); } catch (Exception $e) {    attemptSomething(); } 
  83. 83. Negative string offset (7.1) <?php   $string = "abcde"; print $string[-3]; print "$string[3]"; print "$string[-2]"; c d d
  84. 84. list() with keys Upgraded to Fatal error <?php     $array = ['a' => 1, 'b' => 5, 'c' => 3]; // Assigns to $a, $b and $c in the same order list($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;
  85. 85. 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);  
  86. 86. 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;
  87. 87. 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 …
  88. 88. Incompatible context <?php  class A {       function f() { echo get_class($this); }  }  A::f();  ?> Notice: Undefined variable: $this inA Deprecated: Non-static methodA::f() should not be called statically Notice: Undefined variable: $this inA
  89. 89. 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
  90. 90. $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);   } }
  91. 91. Changed behavior
  92. 92. Changed behavior Indirect expressions
  93. 93. 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 )
  94. 94. 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
  95. 95. 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.
  96. 96. New features
  97. 97. New features Breaks backward compatibility sometimes FUD Search for places to apply them like for incompatibilities
  98. 98. New features Fixing Modernization New features
  99. 99. Fixing
  100. 100. 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
  101. 101. 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]);     }  }
  102. 102. Constant visibility <?php    class Version {       private const BUILD = 123;      public const VERSION = '1.2.2';  } echo Version::UNDEFINED; echo Version::BUILD; echo Version::VERSION;
  103. 103. 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]);     }  }
  104. 104. Modernization
  105. 105. Foreach update the actual array <?php $array = [0]; foreach ($array as $k => &$val) {     print "$kn";     $array[] = 1; // Only for pushing, not unshifting } 0 1 2 3 4 5 6 7 8 9 10 11 0
  106. 106. 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
  107. 107. 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
  108. 108. Closure binding <?php class Hello {   private $hello = "Hello";   function makeClosure() {    return function() {     echo $this->hello;   }; } $obj = new Hello(); $closure = $obj->makeClosure(); $closure();
  109. 109. 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
  110. 110. Closure binding <?php $closure = function() {     echo $this->hello; }; class   {   private $hello = " "; } $nihao = new  (); $closure->call($nihao); Bind the object at the last moment
  111. 111. 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 ] );
  112. 112. 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); ?>
  113. 113. 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
  114. 114. Really new
  115. 115. 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'; ?>
  116. 116. 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
  117. 117. 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 ' : '') . " factorn";  }
  118. 118. 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 "$nn";     if ($n == 3) {break 1;} } print $gen->getReturn(); // second Generators returns The last return is accessible The generator returns the final state
  119. 119. 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) { ... }
  120. 120. Scalar typehint back in 5.6 Whenever type is tested => <?php    function foo(string $x) { } foo('that'); Catchable fatal error: Argument 1 passed to
 foo() must be an instance of string, string given, called in file..
  121. 121. 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
  122. 122. Option for strict typing <?php // Enable strict types declare(strict_types=1); declare(encoding='ISO-8859-1'); declare(ticks=1); namespace FooBar; foo('that'); 
  123. 123. Return type hint <?php function getData($login) : user {    if (userExists($login)) {      return userDetails($login);    } else {      return null;   } }
  124. 124. 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
  125. 125. 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
  126. 126. Helping at migration Backward incompatibilities Removed / renamed features Collaterals Changed behaviors
  127. 127. Helping at migration New features Fixing Totally new Spot previous work-arounds
  128. 128. Summary Check the manuals PHP lint is your friend Search in the code Use static analysis tools
  129. 129. Thank you! Damien Seguy @exakat dseguy@exakat.io https://www.exakat.io/ https://joind.in/event/php-uk-2017/schedule/list
  130. 130. The end

×