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.

implements Hello_Dolly

98 views

Published on

We start with everyone’s favorite WordPress plugin, "Hello, Dolly". With a dramatic wave of our hands, we speak the magical word: "Refactor!" Let’s see how far we can go.

WordCamp Orlando, Nov. 12, 2016

Published in: Software
  • Be the first to comment

  • Be the first to like this

implements Hello_Dolly

  1. 1. implements Hello_Dolly Image Credit: Ky on Flickr • https://flic.kr/p/8x4bVQ Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  2. 2. Jonathan Brinley Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  3. 3. "Hello, Dolly!" Image Credit: Classic Film on Flickr • https://flic.kr/p/KD7WWE Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  4. 4. "Hello, Dolly!" Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  5. 5. hello.php /** * @package Hello_Dolly * @version 1.6 */ /* Plugin Name: Hello Dolly Plugin URI: http://wordpress.org/plugins/hello-dolly/ Description: This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in Author: Matt Mullenweg Version: 1.6 Author URI: http://ma.tt/ */ function hello_dolly_get_lyric() { /** These are the lyrics to Hello Dolly */ $lyrics = "Hello, Dolly Well, hello, Dolly It's so nice to have you back where you belong You're lookin' swell, Dolly I can tell, Dolly You're still glowin', you're still crowin' You're still goin' strong We feel the room swayin' Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  6. 6. Refactor! github.com/flightless/implements-hello-dolly Image Credit: Classic Film on Flickr • https://flic.kr/p/LxazKW Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  7. 7. implements-hello-dolly.php /* Plugin Name: implements Hello_Dolly Description: We start with everyone’s favorite WordPress plugin, “Hello, Dolly”. With a dramatic wave of our hands, w Author: Jonathan Brinley Version: 2.0 Contributors: Matt Mullenweg */ namespace Hello_Dolly; // Start the plugin add_action( 'plugins_loaded', function () { require_once __DIR__ . '/vendor/autoload.php'; Hello_Dolly_Plugin::init(); do_action( 'hello_dolly/init' ); }, 1, 0 ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  8. 8. Composer Autoloading Libraries getcomposer.org Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  9. 9. Composer composer init { "name": "flightless/implements-hello-dolly", "description": "We start with everyone’s favorite WordPress plugin, “Hello, Dolly”. With a dramatic wave of our ha "type": "wordpress-plugin", "license": "GPL-2.0", "authors": [ { "name": "Jonathan Brinley", "email": "jonathan@tri.be" } ] } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  10. 10. composer.json { "name": "flightless/implements-hello-dolly", "description": "We start with everyone’s favorite WordPress plugin, “Hello, Dolly”. With a dramatic wave of our ha "type": "wordpress-plugin", "license": "GPL-2.0", "authors": [ { "name": "Jonathan Brinley", "email": "jonathan@tri.be" } ], "autoload": { "psr-4": { "Hello_Dolly": "src/" } } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  11. 11. Codeception composer require --dev lucatume/wp-browser Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  12. 12. Codeception composer install --no-dev { "name": "flightless/implements-hello-dolly", /* ... */ "autoload": { "psr-4": { "Hello_Dolly": "src/" } }, "require-dev": { "lucatume/wp-browser": "^1.16" } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  13. 13. WPLoader vendor/bin/wpcept bootstrap https://github.com/lucatume/wp-browser Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  14. 14. tests/integration/Hello_Dolly/ Hello_Dolly_Plugin_Test.php namespace Hello_Dolly; use CodeceptionTestCaseWPTestCase; class Hello_Dolly_Plugin_Test extends WPTestCase { public function test_get_instance() { $instance = Hello_Dolly_Plugin::instance(); $this->assertInstanceOf( 'Hello_DollyHello_Dolly_Plugin', $instance ); } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  15. 15. implements-hello-dolly.php namespace Hello_Dolly; // Start the plugin add_action( 'plugins_loaded', function () { require_once __DIR__ . '/vendor/autoload.php'; Hello_Dolly_Plugin::init(); do_action( 'hello_dolly/init' ); }, 1, 0 ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  16. 16. src/Hello_Dolly_Plugin.php namespace Hello_Dolly; /** * Initializes the Hello, Dolly plugin */ class Hello_Dolly_Plugin { /** @var static */ private static $instance; /** * Initialize the plugin */ public static function init() { self::instance(); } /** * Get the global instance of the class * @return static */ public static function instance() { if ( empty( static::$instance ) ) { static::$instance = new static(); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  17. 17. First Five Principles Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  18. 18. Single responsibility principle Turn a string of lyrics into an array Get a random line from the lyrics array Texturize a line of lyrics Wrap lyrics in a paragraph tag Build language-appropriate CSS rules Print the lyrics to the browser Print the style rules to the browser Hook into WordPress Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  19. 19. LyricsLyric_Collection Responsibility Fetch a single line from an array of lyrics Why would it change? If the algorithm to fetch a line changed Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  20. 20. tests/integration/Hello_Dolly/ Lyrics/Lyric_Collection_Test.php namespace Hello_DollyLyrics; use CodeceptionTestCaseWPTestCase; class Lyric_Collection_Test extends WPTestCase { public function test_get_lyric() { $lyrics = []; for ( $i = 0 ; $i < 10 ; $i++ ) { $lyrics[] = rand_str( mt_rand( 0, 32 ) ) . ' ' . rand_str( mt_rand( 0, 32 ) ) . ' ' . rand_str( mt_rand( 0, 32 } $collection = new Lyric_Collection( $lyrics ); for ( $i = 0 ; $i < 20 ; $i++ ) { $this->assertContains( $collection->get_lyric(), $lyrics ); } } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  21. 21. LyricsLyric_Collection namespace Hello_DollyLyrics; class Lyric_Collection implements Lyric_Collection_Interface { /** @var string[] */ protected $lyrics; /** @var int */ protected $count = 0; /** * Lyric_Collection constructor. * * @param array $lyrics An array of single-line strings */ public function __construct( array $lyrics ) { $this->lyrics = array_values( $lyrics ); $this->count = count( $this->lyrics ); } public function get_lyric() { return $this->get_random_lyric(); } /** Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  22. 22. LyricsLyric_Collection_Interface namespace Hello_DollyLyrics; /** * Interface Lyric_Collection_Interface * * Implementors will return a single line of lyrics */ interface Lyric_Collection_Interface { /** * @return string A single line of lyrics */ public function get_lyric(); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  23. 23. LyricsLyric_Collection_Factory Responsibility Transform a string of lyrics into a Lyric_Collection Why would it change? If the return value should be a different class Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  24. 24. tests/integration/Hello_Dolly/ Lyrics/Lyric_Collection_Factory_Test.php namespace Hello_DollyLyrics; use CodeceptionTestCaseWPTestCase; class Lyric_Collection_Factory_Test extends WPTestCase { public function test_create_collection() { $lyrics = []; for ( $i = 0 ; $i < 10 ; $i++ ) { $lyrics[] = rand_str( mt_rand( 0, 32 ) ) . ' ' . rand_str( mt_rand( 0, 32 ) ) . ' ' . rand_str( mt_rand( 0, 32 } $string = implode( "n", $lyrics ); $factory = new Lyric_Collection_Factory(); $collection = $factory->create_collection( $string ); $this->assertInstanceOf( 'Hello_DollyLyricsLyric_Collection', $collection ); } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  25. 25. LyricsLyric_Collection_Factory namespace Hello_DollyLyrics; /** * Class Lyric_Collection_Factory * * Creates a Lyric_Collection instance from * a string of lyrics. */ class Lyric_Collection_Factory implements Lyric_Collection_Factory_Interface { /** * @param string $lyrics * @return Lyric_Collection */ public function create_collection( $lyrics ) { $lyrics = explode( "n", $lyrics ); return new Lyric_Collection( $lyrics ); } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  26. 26. LyricsLyric_Collection_Factory_Interface namespace Hello_DollyLyrics; /** * Interface Lyric_Collection_Factory_Interface * * Creates a Lyric_Collection_Interface from a * multi-line string of lyrics. */ interface Lyric_Collection_Factory_Interface { /** * @param string $lyrics A multi-line string of lyrics * @return Lyric_Collection_Interface */ public function create_collection( $lyrics ); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  27. 27. Formatting Responsibility Apply wptexturize() to a string Wrap a string in a paragraph tag with an ID Why would it change? The formatting algorithm changes The HTML element changes Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  28. 28. Decorator format(string) String_Formatter format(string) String_Formatter format(string) String_Formatter_Interface format(string) String_Formatter_Interface html_id __construct(formatter, html_id) decorate(string) Paragraph_Tag html_id __construct(formatter, html_id) decorate(string) Paragraph_Tag decorate(string) WP_Texturize_Formatter decorate(string) WP_Texturize_Formatter formatter __construct(formatter) format(string) decorate(string) String_Formatter_Decorator formatter __construct(formatter) format(string) decorate(string) String_Formatter_Decorator Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  29. 29. FormattingString_Formatter_Interface namespace Hello_DollyFormatting; /** * Interface String_Formatter_Interface * * Implementors will format and return a string. */ interface String_Formatter_Interface { /** * @param string $string The string to be formatted * @return string The formatted string */ public function format( $string ); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  30. 30. FormattingString_Formatter namespace Hello_DollyFormatting; use CodeceptionTestCaseWPTestCase; class String_Formatter_Test extends WPTestCase { public function test_passthrough() { $string = rand_str(); $passthrough = new String_Formatter(); $this->assertEquals( $string, $passthrough->format( $string ) ); } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  31. 31. FormattingString_Formatter namespace Hello_DollyFormatting; /** * Class String_Formatter * * A passthrough formatter that returns the given string */ class String_Formatter implements String_Formatter_Interface { public function format( $string ) { return $string; } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  32. 32. FormattingString_Formatter_Decorator namespace Hello_DollyFormatting; /** * Class String_Formatter_Decorator * * Base class for string decorators. */ abstract class String_Formatter_Decorator implements String_Formatter_Interface { /** @var String_Formatter_Interface */ protected $formatter; public function __construct( String_Formatter_Interface $formatter ) { $this->formatter = $formatter; } public function format( $string ) { $string = $this->formatter->format( $string ); return $this->decorate( $string ); } abstract protected function decorate( $string ); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  33. 33. FormattingWP_Texturize_Formatter namespace Hello_DollyFormatting; use CodeceptionTestCaseWPTestCase; class WP_Texturize_Formatter_Test extends WPTestCase { public function test_texturization() { $string = "You're lookin' swell, Dolly"; $texturized_string = wptexturize( $string ); $this->assertNotEquals( $string, $texturized_string ); $formatter = new WP_Texturize_Formatter( new String_Formatter() ); $this->assertEquals( $texturized_string, $formatter->format( $string ) ); } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  34. 34. FormattingWP_Texturize_Formatter namespace Hello_DollyFormatting; /** * Class WP_Texturize_Formatter * * Formats a string with wptexturize() * @see wptexturize() */ class WP_Texturize_Formatter extends String_Formatter_Decorator { protected function decorate( $string ) { return wptexturize( $string ); } } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  35. 35. FormattingParagraph_Tag namespace Hello_DollyFormatting; use CodeceptionTestCaseWPTestCase; class Paragraph_Tag_Test extends WPTestCase { public function test_paragraph_tag() { $string = rand_str(); $id = rand_str(); $formatter = new Paragraph_Tag( new String_Formatter(), $id ); $expected = sprintf( '<p id="%s">%s</p>', $id, $string ); $this->assertEquals( $expected, $formatter->format( $string ) ); } public function test_escaped_id() { $string = rand_str(); $id = rand_str(8) . ' ' . rand_str(8); $escaped_id = sanitize_html_class( $id ); $this->assertNotEquals( $id, $escaped_id ); $formatter = new Paragraph_Tag( new String_Formatter(), $id ); $expected = sprintf( '<p id="%s">%s</p>', $escaped_id, $string ); $this->assertEquals( $expected, $formatter->format( $string ) ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  36. 36. FormattingParagraph_Tag namespace Hello_DollyFormatting; /** * Class Paragraph_Tag * * Wraps a string in a paragraph tag with an ID */ class Paragraph_Tag extends String_Formatter_Decorator { /** @var string */ protected $html_id; /** * Paragraph_Tag constructor. * * @param String_Formatter_Interface $formatter * @param $html_id */ public function __construct( String_Formatter_Interface $formatter, $html_id ) { parent::__construct( $formatter ); $this->html_id = $html_id; } protected function decorate( $string ) { return sprintf( '<p id="%s">%s</p>', sanitize_html_class( $this->html_id), $string ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  37. 37. <p id="dolly">You’re lookin’ swell, Dolly</p> $formatter = new Paragraph_Tag( new WP_Texturize_Formatter( new String_Formatter() ), 'dolly' ); return $formatter->format( "You're lookin' swell, Dolly" ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  38. 38. StylesStyle_Rules_Interface namespace Hello_DollyStyles; /** * Interface Style_Rules_Interface * * Generates a list of CSS properties */ interface Style_Rules_Interface { /** * @return string */ public function get_styles(); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  39. 39. StylesDirectional_Style_Rules namespace Hello_DollyStyles; /** * Class Directional_Style_Rules * * Generates styles for the given text direction */ class Directional_Style_Rules implements Style_Rules_Interface { protected $side; /** * Directional_Style_Rules constructor. * * @param string $side The side text floats to. 'left' or 'right' */ public function __construct( $side ) { if ( empty( $side ) ) { throw new InvalidArgumentException( __( '$this->side must be set to a non-empty value' ) ); } $this->side = $side; } public function get_styles() { return " Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  40. 40. StylesStyle_Printer_Interface namespace Hello_DollyStyles; /** * Interface Style_Printer_Interface * * Prints CSS styles */ interface Style_Printer_Interface { /** * @param Style_Rules_Interface $styles * @return void */ public function print_styles( Style_Rules_Interface $styles ); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  41. 41. StylesStyle_Printer namespace Hello_DollyStyles; /** * Class Style_Printer * * Prints the given style rules for the element with the given ID */ class Style_Printer implements Style_Printer_Interface { protected $html_id; /** * Style_Printer constructor. * * @param string $html_id The ID of the targeted DOM element */ public function __construct( $html_id ) { $this->html_id = $html_id; } /** * @param Style_Rules_Interface $styles * @return void */ public function print_styles( Style_Rules_Interface $styles ) { Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  42. 42. src/Hello_Dolly_Plugin.php private function hooks() { add_action( 'admin_head', [ $this, 'print_admin_css' ] ); add_action( 'admin_notices', [ $this, 'print_admin_notice' ] ); } public function print_admin_css() { $style_rules = $this->get_style_rules(); $printer = $this->get_style_printer(); $printer->print_styles( $style_rules ); } public function print_admin_notice() { $lyrics = $this->get_lyric_collection(); $printer = $this->get_lyric_printer(); $printer->render( $lyrics->get_lyric() ); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  43. 43. src/Hello_Dolly_Plugin.php /** * @return Style_Rules_Interface */ private function get_style_rules() { if ( is_rtl() ) { $style_rules = $this->container[ 'style_rules.rtl' ]; } else { $style_rules = $this->container[ 'style_rules.ltr' ]; } return apply_filters( 'hello_dolly/style_rules', $style_rules ); } /** * @return Style_Printer_Interface */ private function get_style_printer() { $printer = $this->container[ 'style_printer' ]; return apply_filters( 'hello_dolly/style_printer', $printer ); } /** * @return Lyric_Collection_Interface */ private function get_lyric_collection() { Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  44. 44. src/Hello_Dolly_Plugin.php /** * Initialize the plugin * * @return void */ public static function init( Container $container ) { $instance = self::instance(); $instance->container = $container; $instance->hooks(); } Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  45. 45. Dependency Injection Container Aura.Di PHP-DI Pimple SymphonyDependencyInjection ZendDi Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  46. 46. Pimple composer require pimple/pimple Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  47. 47. implements-hello-dolly.php namespace Hello_Dolly; use PimpleContainer; // Start the plugin add_action( 'plugins_loaded', function () { require_once __DIR__ . '/vendor/autoload.php'; $container = new Container(); $container->register( new Service_Provider() ); Hello_Dolly_Plugin::init( $container ); do_action( 'hello_dolly/init', Hello_Dolly_Plugin::instance(), $container ); }, 1, 0 ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  48. 48. src/Service_Provider.php namespace Hello_Dolly; use Hello_DollyFormattingParagraph_Tag; use Hello_DollyFormattingString_Formatter; use Hello_DollyFormattingWP_Texturize_Formatter; use Hello_DollyLyricsLyric_Collection_Factory; use Hello_DollyPrintingFormatted_Printer; use Hello_DollyStylesDirectional_Style_Rules; use Hello_DollyStylesStyle_Printer; use PimpleContainer; use PimpleServiceProviderInterface; class Service_Provider implements ServiceProviderInterface { public function register( Container $container ) { $container[ 'html_id' ] = 'dolly'; $container[ 'lyrics' ] = "Hello, Dolly Well, hello, Dolly It's so nice to have you back where you belong You're lookin' swell, Dolly I can tell, Dolly You're still glowin', you're still crowin' Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  49. 49. implements-hello-dolly.php namespace Hello_Dolly; use PimpleContainer; // Start the plugin add_action( 'plugins_loaded', function () { require_once __DIR__ . '/vendor/autoload.php'; $container = new Container(); $container->register( new Service_Provider() ); Hello_Dolly_Plugin::init( $container ); do_action( 'hello_dolly/init', Hello_Dolly_Plugin::instance(), $container ); }, 1, 0 ); Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  50. 50. Resources implements Hello_Dolly github.com/flightless/implements-hello-dolly The Principles of OOD / Robert C. Martin butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod Inversion of Control Containers and the Dependency Injection pattern / Martin Fowler martinfowler.com/articles/injection.html WPBrowser / Luca Tumedei github.com/lucatume/wp-browser theaveragedev.com/tag/wp-browser/ Image Credit: New York Sunday News Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016
  51. 51. Jonathan Brinley @jbrinley • jonathan@tri.be Jonathan Brinley • • #wcorl • Slides: @jbrinley flightless.us/wcorl2016

×