Slide 1

Slide 1 text

For the Love of Code: Modernizing WordPress, plugins and themes Juliette Reinders Folmer Tweet about it: @jrf_nl #ForTheLoveOfCode #WPZwolle

Slide 2

Slide 2 text

Olivier Gobet

Slide 3

Slide 3 text

PHP Supported Versions

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Let's Modernize All The Things!!

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Lower Memory Usage Enhanced Interoperability New Features Future Maintainability Improve(d) Security Better Performance Why Modernize ?

Slide 8

Slide 8 text

EricBerthe  kconnors 

Slide 9

Slide 9 text

The fact that code runs on PHP 5.6+, does not make it PHP 5.6+ code BryanHanson

Slide 10

Slide 10 text

Let's Rewrite from Scratch!

Slide 11

Slide 11 text

Let's not  Halts Progress  Maintaining BC will be much more difficult quicksandala

Slide 12

Slide 12 text

All Code is Legacy Code as soon as it is written

Slide 13

Slide 13 text

mxruben

Slide 14

Slide 14 text

What Needs to be Done ? Fix Incompati- bilities Use What Can Already be Used Clean Up Modernize Implement Quick Fixes Selectively Refactor

Slide 15

Slide 15 text

Fix Existing Incompatibilities  PHP -l (lint)  PHPCompatibility  phan  php7cc  php7mar  php-compat-info Seemann

Slide 16

Slide 16 text

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()

Slide 17

Slide 17 text

Clean Up (code-level)

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Use case-insensitive searches.

Slide 20

Slide 20 text

Look Behind: Code for the now, but allow for the past Look Forward: Code for the lowest common denominator and allow for the future.

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

Clean Up (application-level)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Modernize

Slide 26

Slide 26 text

Best Practices  Focus on function, not syntax GaborfromHungary

Slide 27

Slide 27 text

Best Practices  Focus on function, not syntax  Test Driven Refactoring GaborfromHungary

Slide 28

Slide 28 text

Test Driven Refactoring Are there tests ? Do the tests actually cover the code ? Are the tests not just testing the "happy path" ?

Slide 29

Slide 29 text

Best Practices  Focus on function, not syntax  Test Driven Refactoring  Don't attempt to do everything at once GaborfromHungary

Slide 30

Slide 30 text

Approaches to Modernizing Horizontal • File by file • Component-led Vertical • Fix by fix • PHP feature-led

Slide 31

Slide 31 text

Best Practices  Focus on function, not syntax  Test Driven Refactoring  Don't attempt to do everything at once  Use the tools available GaborfromHungary

Slide 32

Slide 32 text

Tools Psalm PHPStan Phan Exakat PHPModernizer (upcoming) Rector PHPStorm Inspections PHPMD (Mess Detector) and many more...

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Namespaces Seemann 5.3

Slide 35

Slide 35 text

Logical Grouping of Code

Slide 36

Slide 36 text

Before You Start  Think  Autoloading ? - PSR-0 (deprecated) - PSR-4 - Custom - Classmap ngrigor

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

How Does it Work ? Namespace always applied • Classes • Traits • Interfaces Fallback to global namespace • Functions • Constants Namespace irrelevant • Magic Constants

Slide 39

Slide 39 text

FQN: Fully\Qualified\Name \Fully\Qualified\Name

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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();

Slide 43

Slide 43 text

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();

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Traits Erean 5.4

Slide 47

Slide 47 text

Trait Inheritance vs Horizontal Composition Grand- Parent Child Child Grand- child Grand- child Child Class Class Grand- Parent

Slide 48

Slide 48 text

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.

Slide 49

Slide 49 text

Constant Scalar Expressions FidlerJan 5.6

Slide 50

Slide 50 text

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; }

Slide 51

Slide 51 text

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; } } }

Slide 52

Slide 52 text

Constant Scalar Expressions [2] class ConstScalarExprExample { const FILES = array( __DIR__ . '/../css/frontend' . ( SCRIPT_DEBUG ? '' : '.min' ) . '.css', __DIR__ . '/../css/admin' . ( SCRIPT_DEBUG ? '' : '.min' ) . '.css', ); }

Slide 53

Slide 53 text

Short Ternary & Null Coalesce 5.3, 7.0, 7.4 Paul Brennan

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Type Declarations 5.0, 5.4, 7.0 – 7.2 Sgarton

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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; }

Slide 60

Slide 60 text

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 );

Slide 61

Slide 61 text

Spread Operator xandert 5.6

Slide 62

Slide 62 text

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 ); }

Slide 63

Slide 63 text

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 ) {

Slide 64

Slide 64 text

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 ) {

Slide 65

Slide 65 text

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 ) );

Slide 66

Slide 66 text

Quick Fixes DarrenHester

Slide 67

Slide 67 text

__DIR__ & dirname() $path = dirname( dirname( dirname( __FILE__ ) ) ); $path = dirname( dirname( __DIR__ ) ); $path = dirname( __DIR__, 2 ); 5.2 5.3 7.0

Slide 68

Slide 68 text

empty() $result = function_call( $a, $b ); if ( empty( $result ) ) { return false; } if ( empty( function_call( $a, $b ) ) ) { return false; } 5.2 5.5

Slide 69

Slide 69 text

Function Array Dereferencing $array = function_call( $a, $b ); echo $array[0]; echo function_call( $a, $b )[0]; 5.2 5.4

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

::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

Slide 73

Slide 73 text

Iterators & Generators hotblack 5.2 - 5.5

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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. }

Slide 76

Slide 76 text

But.... There is so Much More ! Finally Garbage Collector Nested Exceptions Closures Goto Late Static Binding OpCache Nowdocs

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

Keep Educating Yourself

Slide 80

Slide 80 text

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