Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Juliette Reinders Folmer

July 11, 2019
Tweet

More Decks by Juliette Reinders Folmer

Other Decks in Programming

Transcript

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

    Juliette Reinders Folmer Tweet about it: @jrf_nl #ForTheLoveOfCode #WPZwolle
  2. 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
  3. The fact that code runs on PHP 5.6+, does not

    make it PHP 5.6+ code BryanHanson
  4. What Needs to be Done ? Fix Incompati- bilities Use

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

    phan  php7cc  php7mar  php-compat-info Seemann
  6. 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()
  7. 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
  8. Look Behind: Code for the now, but allow for the

    past Look Forward: Code for the lowest common denominator and allow for the future.
  9. 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.
  10. 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
  11. 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
  12. Best Practices  Focus on function, not syntax  Test

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

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

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

    Driven Refactoring  Don't attempt to do everything at once  Use the tools available GaborfromHungary
  16. 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
  17. Before You Start  Think  Autoloading ? - PSR-0

    (deprecated) - PSR-4 - Custom - Classmap ngrigor
  18. 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
  19. How Does it Work ? Namespace always applied • Classes

    • Traits • Interfaces Fallback to global namespace • Functions • Constants Namespace irrelevant • Magic Constants
  20. 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
  21. Namespaces class NSExample extends Base_Example { public function do_something( $input

    ) { return str_pad( $input, 10, '-', STR_PAD_LEFT ); } } $ns_example = new NSExample();
  22. 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();
  23. 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();
  24. 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
  25. 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
  26. Trait Inheritance vs Horizontal Composition Grand- Parent Child Child Grand-

    child Grand- child Child Class Class Grand- Parent
  27. 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.
  28. 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; }
  29. 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; } } }
  30. Constant Scalar Expressions [2] class ConstScalarExprExample { const FILES =

    array( __DIR__ . '/../css/frontend' . ( SCRIPT_DEBUG ? '' : '.min' ) . '.css', __DIR__ . '/../css/admin' . ( SCRIPT_DEBUG ? '' : '.min' ) . '.css', ); }
  31. 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
  32. 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
  33. 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
  34. Type Declarations function get_tax_posts( $taxonomies = null, $limit = 5)

    { $taxonomies = (array) $taxonomies; $limit = (int) $limit; ... return $post_data; }
  35. 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; }
  36. 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 );
  37. 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 ); }
  38. 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 ) {
  39. 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 ) {
  40. 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 ) );
  41. __DIR__ & dirname() $path = dirname( dirname( dirname( __FILE__ )

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

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

    $array[0]; echo function_call( $a, $b )[0]; 5.2 5.4
  44. 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
  45. 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
  46. ::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
  47. 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
  48. 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. }
  49. But.... There is so Much More ! Finally Garbage Collector

    Nested Exceptions Closures Goto Late Static Binding OpCache Nowdocs
  50. 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
  51. 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