For the love of code: Modernizing WordPress, plugins, and themes (full deck)

For the love of code: Modernizing WordPress, plugins, and themes (full deck)

Presented on July 11th 2019 at the WP Zwolle meetup, Zwolle, The Netherlands.
https://www.meetup.com/Zwolle-WordPress-Meetup/events/bgdwbryzkbpb/
---------------------------------------------------------------
Now that WordPress has committed to a minimum requirement of PHP 7 by the end of 2019, we can all start looking at modernizing the code we maintain. Removing hacks to support old versions is easy, but how can code be improved when it just works on PHP 7? Namespaces, generators, Intl are just a few of the features introduced since PHP 5.2, not to mention scalar type declarations and all the other awesomeness that came with PHP 7. But what does it all mean, and how can you take advantage of these goodies? Join Juliette to learn to identify where to make quick fixes, when to look into refactoring, and how to make your code faster, better and more secure by using modern PHP.

2776198ea9584b6c0d4b494293b8d635?s=128

Juliette Reinders Folmer

July 11, 2019
Tweet

Transcript

  1. For the Love of Code: Modernizing WordPress, plugins and themes

    Juliette Reinders Folmer Tweet about it: @jrf_nl #ForTheLoveOfCode #WPZwolle
  2. Olivier Gobet

  3. PHP Supported Versions

  4. WordPress versus PHP 4.2 4.3 4.4 5.0 5.1 5.2 5.3

    5.4 5.5 5.6 7.0 7.1 7.2 7.3 7.4 WP 2.0 Dec 2005 WP 2.5 Mar 2008 WP 3.2 July 2011 5.2.4 ... WP 5.2 May 2019 5.6.20 WP 5.? Dec 2019
  5. Let's Modernize All The Things!!

  6. But the code works fine on PHP 7... munozgo

  7. Lower Memory Usage Enhanced Interoperability New Features Future Maintainability Improve(d)

    Security Better Performance Why Modernize ?
  8. EricBerthe  kconnors 

  9. The fact that code runs on PHP 5.6+, does not

    make it PHP 5.6+ code BryanHanson
  10. Let's Rewrite from Scratch!

  11. Let's not  Halts Progress  Maintaining BC will be

    much more difficult quicksandala
  12. All Code is Legacy Code as soon as it is

    written
  13. mxruben

  14. What Needs to be Done ? Fix Incompati- bilities Use

    What Can Already be Used Clean Up Modernize Implement Quick Fixes Selectively Refactor
  15. Fix Existing Incompatibilities  PHP -l (lint)  PHPCompatibility 

    phan  php7cc  php7mar  php-compat-info Seemann
  16. Use What Can Already be Used PHP Native features like:

     Autoloading  Exceptions with try/catch  instanceof  Filter  SPL (Stnd PHP Library)  mysqli or PDO  Class name based type declarations  Array type declarations WP Polyfilled features like:  Sodium  CSPRNG (random_bytes(), random_int())  Various MbString functions  hash_equals()  JsonSerializable interface  spl_autoload_register()  array_replace_recursive()  is_iterable()  is_countable()
  17. Clean Up (code-level)

  18. Remove Hacks and Polyfills  version_compare()  extension_loaded()  class_exists()

     method_exist()  function_exists()  defined()  phpversion()  class_alias()  PHP_VERSION_ID  PHP_MAJOR_VERSION  PHP_MINOR_VERSION Lilla Frerichs
  19. Use case-insensitive searches.

  20. Look Behind: Code for the now, but allow for the

    past Look Forward: Code for the lowest common denominator and allow for the future.
  21. Looking Forward vs Looking Behind if ( version_compare( phpversion(), '7.0',

    '>' ) ) { // Modern code. } // Old code. if ( version_compare( PHP_VERSION_ID, '59999', '<=' ) ) { // Back-fill for missing feature. Remove when ... } // More modern code.
  22. Clean Up (application-level)

  23. Use PHPUnit 5.4+ // Works with PHPUnit 3/4/5. class MyTest

    extends PHPUnit_Framework_TestCase { } // Works with PHPUnit 5.4/6/7. use PHPUnit\Framework\TestCase; class MyTest extends TestCase { } v Compatible with: 8 PHP 7.2, 7.3, 7.4 (support ends Feb 2021) 7 PHP 7.1, 7.2, 7.3 (support ends Feb 2020) 6 PHP 7.0, 7.1, 7.2 5 PHP 5.6, 7.0, 7.1 4 PHP 5.3, 5.4, 5.5, 5.6
  24. Use Composer { "name": "organization/my-plugin", "description": "A plugin...", "keywords": ["wordpress"],

    "license": "GPL-2.0-or-later", "type": "wordpress-plugin", "require": { "php": "^5.6.20 || ^7.0", }, "require-dev": { "humbug/php-scoper": "^0.6.0" }, "autoload": { "classmap": [ "src/" ] } }  ... but prevent dependency conflicts
  25. Modernize

  26. Best Practices  Focus on function, not syntax GaborfromHungary

  27. Best Practices  Focus on function, not syntax  Test

    Driven Refactoring GaborfromHungary
  28. Test Driven Refactoring Are there tests ? Do the tests

    actually cover the code ? Are the tests not just testing the "happy path" ?
  29. Best Practices  Focus on function, not syntax  Test

    Driven Refactoring  Don't attempt to do everything at once GaborfromHungary
  30. Approaches to Modernizing Horizontal • File by file • Component-led

    Vertical • Fix by fix • PHP feature-led
  31. Best Practices  Focus on function, not syntax  Test

    Driven Refactoring  Don't attempt to do everything at once  Use the tools available GaborfromHungary
  32. Tools Psalm PHPStan Phan Exakat PHPModernizer (upcoming) Rector PHPStorm Inspections

    PHPMD (Mess Detector) and many more...
  33. Spread Operator Iterators & Generators Traits Namespaces Constant Scalar Expressions

    Quick Fixes Short Ternary & Null coalesce Type Declarations 5.3 5.4 5.6 5.3, 7.0, 7.4 5.0, 5.4, 7.0 – 7.2 5.6 5.2 - 5.5 5.3 - 7.4
  34. Namespaces Seemann 5.3

  35. Logical Grouping of Code

  36. Before You Start  Think  Autoloading ? - PSR-0

    (deprecated) - PSR-4 - Custom - Classmap ngrigor
  37. What Do Namespaces Apply to ?  Functions  Classes

     Traits  Interfaces  Constants declared with const keyword  Variables  Constants declared with define()  Non-PHP native constructs: - hook names
  38. How Does it Work ? Namespace always applied • Classes

    • Traits • Interfaces Fallback to global namespace • Functions • Constants Namespace irrelevant • Magic Constants
  39. FQN: Fully\Qualified\Name \Fully\Qualified\Name

  40. How Does it Work ? Namespace always applied • Classes

    • Traits • Interfaces Fallback to global namespace • Functions • Constants Namespace irrelevant • Magic Constants Must use FQN or use statement May use FQN or use statement N/A
  41. Namespaces class NSExample extends Base_Example { public function do_something( $input

    ) { return str_pad( $input, 10, '-', STR_PAD_LEFT ); } } $ns_example = new NSExample();
  42. Namespaces namespace MyNS; class NSExample extends Base_Example { public function

    do_something( $input ) { return str_pad( $input, 10, '-', STR_PAD_LEFT ); } } $ns_example = new NSExample();
  43. Namespaces namespace MyNS; class NSExample extends \Base_Example { public function

    do_something( $input ) { return \str_pad( $input, 10, '-', \STR_PAD_LEFT ); } } $ns_example = new NSExample();
  44. Import Use Statements namespace MyNS; use Base\Example; use function str_pad;

    use const STR_PAD_LEFT; class NSExample extends Example { public function do_something( $input ) { return str_pad( $input, 10, '-', STR_PAD_LEFT ); } } $ns_example = new NSExample(); 5.3 5.6
  45. PHP 7 Opcode Cached Functions  is_null()  is_bool() 

    is_long()  is_int()  is_integer()  is_float()  is_real()  is_string()  is_array()  is_object()  is_resource()  assert()  strlen()  defined()  call_user_func()  call_user_func_array()  boolval()  intval()  floatval()  doubleval()  strval()  chr()  ord()  in_array()  count()  get_class()  get_called_class()  gettype()  func_num_args()  func_get_args()  array_slice()  sizeof()  array_key_exists() 7.2 7.1 7.4 7.0
  46. Traits Erean 5.4

  47. Trait Inheritance vs Horizontal Composition Grand- Parent Child Child Grand-

    child Grand- child Child Class Class Grand- Parent
  48. trait MyTrait { public $property = 10; public function print_10()

    { print $this->property; } } class MyClass { use MyTrait; public function __construct() { $this->property = 20; } public function test_it() { $this->print_10(); } } (new MyClass)->test_it(); // 20.
  49. Constant Scalar Expressions FidlerJan 5.6

  50. Constant Scalar Expressions [1] class ConstScalarExprExample { const HOURS =

    5; protected $retention_period; public function __construct() { $this->retention_period = self::HOURS * HOUR_IN_SECONDS; } } class ConstScalarExprExample { const RETENTION_PERIOD = 5 * HOUR_IN_SECONDS; }
  51. Constant Scalar Expressions [2] class ConstScalarExprExample { protected $partial_file_names =

    array( 'frontend', 'admin', ); protected $file_extension = '.css'; protected $files = array(); public function __construct() { $path = dirname( __FILE__ ) . '/../css/'; $suffix = ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min' ); foreach ( $this->partial_file_names as $partial ) { $this->files[] = $path . $partial . $suffix . $this->file_extension; } } }
  52. Constant Scalar Expressions [2] class ConstScalarExprExample { const FILES =

    array( __DIR__ . '/../css/frontend' . ( SCRIPT_DEBUG ? '' : '.min' ) . '.css', __DIR__ . '/../css/admin' . ( SCRIPT_DEBUG ? '' : '.min' ) . '.css', ); }
  53. Short Ternary & Null Coalesce 5.3, 7.0, 7.4 Paul Brennan

  54. Short Ternary // $a is not set. $b = 10;

    $var = ( ! empty( $a ) ) ? $a : 'default'; // 'default' $var = ( ! empty( $b ) ) ? $b : 'default'; // 10 $var = ( ! empty( $a ) ) ?: 'default'; // 'default' $var = ( ! empty( $b ) ) ?: 'default'; // True $var = $a ?: 'default'; // Undefined var notice; 'default' $var = $b ?: 'default'; // 10 5.2 5.3
  55. Null Coalesce if ( ! isset( $var ) ) {

    $var = 'default'; } $var = isset( $var ) ? $var : 'default'; $var = $var ?? 'default'; $var ??= 'default'; 5.2 7.0 5.2 7.4
  56. Type Declarations 5.0, 5.4, 7.0 – 7.2 Sgarton

  57. Type Declarations  5.0: Parameter Type Declarations  7.0: Return

    Type Declarations  7.0: Strict Types  7.1: Nullable Types 5.0 Class/Interface name based 5.1 array 5.2 self parent 5.4 callable 7.0 bool, int, float, string 7.1 iterable, void 7.2 object
  58. Type Declarations function get_tax_posts( $taxonomies = null, $limit = 5)

    { $taxonomies = (array) $taxonomies; $limit = (int) $limit; ... return $post_data; }
  59. Type Declarations function get_tax_posts( $taxonomies = null, $limit = 5)

    { $taxonomies = (array) $taxonomies; $limit = (int) $limit; ... return $post_data; } function wp_get_tax_posts( array $taxonomies = [], int $limit = 5) : array { ... return $post_data; }
  60. Type Declarations function get_tax_posts( $taxonomies = null, $limit = 5)

    { _deprecated_function( __FUNCTION__, '5.x', 'wp_get_tax_posts' ); $taxonomies = (array) $taxonomies; $limit = (int) $limit; ... return $post_data; } function wp_get_tax_posts( array $taxonomies = [], int $limit = 5) : array { ... return $post_data; } return wp_get_tax_posts( $taxonomies, $limit );
  61. Spread Operator xandert 5.6

  62. Spread Operator [1] function current_user_can( $capability ) { $current_user =

    wp_get_current_user(); if ( empty( $current_user ) ) { return false; } $args = func_get_args(); return call_user_func_array( array( $current_user, 'has_cap' ), $args ); }
  63. Spread Operator [1] function current_user_can( $capability ) { $current_user =

    wp_get_current_user(); if ( empty( $current_user ) ) { return false; } $args = func_get_args(); return call_user_func_array( array( $current_user, 'has_cap' ), $args ); } , ...$args ) {
  64. Spread Operator [1] function current_user_can( $capability ) { $current_user =

    wp_get_current_user(); if ( empty( $current_user ) ) { return false; } return $current_user->has_cap( $capability, ...$args ); } , ...$args ) {
  65. Spread Operator [2] $content = sprintf( __( 'Track %1$s of

    %2$s.' ), number_format_i18n( $track_number[0] ), number_format_i18n( $track_number[1] ) ); $args = array( 'track_number' => number_format_i18n( $track_number[0] ), 'total_tracks' => number_format_i18n( $track_number[1] ), ); $content = sprintf( __( 'Track %1$s of %2$s.' ), ...array_values($args ) );
  66. Quick Fixes DarrenHester

  67. __DIR__ & dirname() $path = dirname( dirname( dirname( __FILE__ )

    ) ); $path = dirname( dirname( __DIR__ ) ); $path = dirname( __DIR__, 2 ); 5.2 5.3 7.0
  68. empty() $result = function_call( $a, $b ); if ( empty(

    $result ) ) { return false; } if ( empty( function_call( $a, $b ) ) ) { return false; } 5.2 5.5
  69. Function Array Dereferencing $array = function_call( $a, $b ); echo

    $array[0]; echo function_call( $a, $b )[0]; 5.2 5.4
  70. Foreach with list() foreach ( $users as $user_data ) {

    printf( __( '%1$s from %2$s' ), $user_data [0], $user_data [2] ); } foreach ( $users as list( $full_name, $gender, $country ) ) { printf( __( '%1$s from %2$s' ), $full_name, $country ); } 5.2 5.5
  71. Spaceship Operator function sort_order( $a, $b ) { return (

    $a < $b ) ? -1 : ( ( $a > $b ) ? 1 : 0 ); } function sort_order( $a, $b ) { return $a <=> $b; } 5.2 7.0
  72. ::class class Foo { public function who_am_i() { echo 'Method

    defined in ' . __CLASS__; echo 'Method called from' . get_called_class(); } } class Foo { public function who_am_i() { echo 'Method defined in ' . self::class; echo 'Method called from' . static::class; } } 5.2 5.5
  73. Iterators & Generators hotblack 5.2 - 5.5

  74. SPL Iterators AppendIterator ArrayIterator CachingIterator CallbackFilterIterator DirectoryIterator EmptyIterator FilesystemIterator FilterIterator

    GlobIterator InfiniteIterator IteratorIterator LimitIterator MultipleIterator NoRewindIterator ParentIterator RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator
  75. function traverse_directory( $path, $exts ) { $file_list = array(); $handle

    = opendir( $path ); if ( $handle ) { while ( $filename = readdir( $handle ) ) !== false ) { if ( strpos( $filename, '.' ) === 0 ) { continue; } if ( is_file( $path . $filename ) ) { $ext = substr( $filename, ( strpos( $filename, '.' ) + 1 ) ); if ( in_array ( $ext, $exts ) ) { $file_list[] = $path. $filename; } } elseif ( is_dir( $path . $filename ) ) { traverse_directory( $path . $filename, $exts ); } } closedir( $handle ); } return $file_list; } $file_list = traverse_directory( $path ); foreach ( $file_list as $file ) { // Do something. } $directory = new RecursiveDirectoryIterator( $path ); $flattened = new RecursiveIteratorIterator( $directory, RecursiveIteratorIterato $exts = array_map( 'preg_quote', $exts, array_fill( 0, count( $exts ), '`' $exts = implode( '|', $exts ); $regex = '`^(?!(.*/)?\.+$).*\.(' . $exts . ')$`Dix'; $filtered = new RegexIterator( $flattened, $regex ); foreach ( $filtered as $file ) { // Do something. }
  76. But.... There is so Much More ! Finally Garbage Collector

    Nested Exceptions Closures Goto Late Static Binding OpCache Nowdocs
  77. And Then There's PHP 7.... Expectations with assert() Unicode Escapes

    Group Use Declarations More Exception Types More Catchable Errors Uniform Variable Syntax Null Coalesce (equals) Scalar Type Hints & Return Type Hints
  78. And Yet More 7.x Goodies! Spread in arrays Multi catch

    Parameter type widening Short lists, keyed lists iterable, object & void types Nullable types Exceptions on Fatals Class constant visibility
  79. Keep Educating Yourself

  80. Thanks! Any questions ? Slides: https://speakerdeck.com/jrf Feedback: https://joind.in/talk/dafa1 @jrf_nl @jrfnl

    @jrf