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

Immutability to Save an Ever-Changing World - P...

Immutability to Save an Ever-Changing World - PHP Benelux 2018

https://conference.phpbenelux.eu/2018/sessions/immutability-to-save-an-ever-changing-world/

Want to build software that is more testable, easier to modify, and has fewer lines of code? Architecting with more immutable objects that are always in a valid state is the most important lesson I have learned in building better software applications. Using immutable value objects will lead to less checking, fewer bugs, more DRY code, and help avoid the “spooky action at a distance” problem in PHP. We will also learn how to use immutable objects and immutable collections to improve design of our mutable entities. Lastly, we’ll see how immutable objects and functional programming can reduce complexity.

Andrew Cassell

January 27, 2018
Tweet

More Decks by Andrew Cassell

Other Decks in Technology

Transcript

  1. • POPOs • No setters, all parameters are passed into

    constructor • Declare all class properties as private • No properties that are mutable objects Immutable Objects in PHP
  2. • POPOs • No setters, all parameters are passed into

    constructor • Declare all class properties as private • No properties that are mutable objects Immutable Objects in PHP
  3. • Immutable Objects • Always in a valid state •

    Validate in constructor • Behavioral methods return new object Value Objects in PHP
  4. Always Valid Value Object Immutable No Identity (Only Values) Recipe

    Name Weight Temperature Entity Identifiable Mutable Lifecycle Contains Value Objects Line Item Brewer Recipe
  5. First Name Last Name Full Name Email Address Recipe Name

    Date Brewed On Recipe ID Temperature Liters Gallons Kilograms Pounds
  6. cassell:beeriously cassell$ docker run --rm --interactive --tty --network beeriously_default --volume

    `pwd`:/app --user : --workdir /app beeriously_php-fpm /app/vendor/bin/phpunit --configuration /app/src/Tests/Unit/ phpunit.xml.dist PHPUnit 6.4.3 by Sebastian Bergmann and contributors. FEE 3 / 3 (100%) Time: 2.34 seconds, Memory: 4.00MB There were 2 errors: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testGetter Error: Class 'Beeriously\Domain\Recipe\RecipeName' not found /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:20 2) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testToString Error: Class 'Beeriously\Domain\Recipe\RecipeName' not found /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:26 -- There was 1 failure: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testEmptyFails Failed asserting that exception of type "Error" matches expected exception "Beeriously\Domain\Recipe\InvalidRecipeNameException". Message was: "Class 'Beeriously\Domain\Recipe\RecipeName' not found" at /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:15 . ERRORS! Tests: 3, Assertions: 1, Errors: 2, Failures: 1.
  7. cassell:beeriously cassell$ docker run --rm --interactive --tty --network beeriously_default --volume

    `pwd`:/app --user : --workdir /app beeriously_php-fpm /app/ vendor/bin/phpunit --configuration /app/src/Tests/Unit/phpunit.xml.dist PHPUnit 6.4.3 by Sebastian Bergmann and contributors. .EE 3 / 3 (100%) Time: 1.56 seconds, Memory: 4.00MB There were 2 errors: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testGetter Error: Call to undefined method Beeriously\Domain\Recipe\RecipeName::getValue() /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:21 2) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testToString Object of class Beeriously\Domain\Recipe\RecipeName could not be converted to string /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:27 ERRORS! Tests: 3, Assertions: 2, Errors: 2. cassell:beeriously cassell$
  8. docker run --rm --interactive --tty --network beeriously_default --volume `pwd`:/app --

    user : --workdir /app beeriously_php-fpm /app/vendor/bin/phpunit --configuration /app/ src/Tests/Unit/phpunit.xml.dist PHPUnit 6.4.3 by Sebastian Bergmann and contributors. ..E 2 / 3 (67%) Time: 292 ms, Memory: 4.00MB There was 1 error: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testToString Object of class Beeriously\Domain\Recipe\RecipeName could not be converted to string /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:27 ERRORS! Tests: 35, Assertions: 78, Errors: 1. make: *** [unit] Error 2 cassell:beeriously cassell$
  9. cassell:beeriously cassell$ docker run --rm --interactive --tty --network beeriously_default --volume

    `pwd`:/app --user : --workdir /app beeriously_php-fpm /app/vendor/bin/phpunit --configuration /app/src/Tests/ Unit/phpunit.xml.dist PHPUnit 6.4.3 by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 192 ms, Memory: 4.00MB OK (3 tests, 4 assertions)
  10. <?php var_dump(-0.0 > 0); // false var_dump(-0.0 < 0); //

    false var_dump(-0.0 === 0.0); // true (float) var_dump(-0.0 === 0); // false (int) var_dump(-0.0 == false); // true (bool) var_dump(-0.0 == null); // true (bool) var_dump(-0.0 == null); // true (string) var_dump(-0.0 == "negatviezero"); // true (string)
  11. if(!is_numeric($a)) { throw new \Exception(…) } if(!is_numeric($b)) { throw new

    \Exception(…) } if($a < $b) { throw new \Exception(…) } $pounds = $a - $b;
  12. Sugar + Yeast = Alcohol + CO 2 C H

    O ->2C H OH + 2CO 6 12 6 2 5 2
  13. Identifiable Have State and are Mutable (Lifecycle) Operate using Value

    Objects No Security or Permission Checks *(Ideally) Storage Agnostic *(Ideally) Never in an Invalid State Entities
  14. $book->setPrice(16.96); $amount = new Amount(16.96, new Currency(‘USD’); $price = new

    BookPrice($book, $amount); $price->change(new Amount(15.99, new Currency(‘USD’)); Mutable Immutable
  15. $recipe = new Recipe($time + //…); $recipe->addThisIngredient($time + //…); $recipe->addWater($time

    + //…); $recipe->addThatIngredient($time + //…); $recipe->boil($time + //…); $recipe->getEstimatedABV($time);
  16. $recipe = new Recipe($history + //…); $recipe->addThisIngredient($history + //…); $recipe->addWater($history

    + //…); $recipe->addThatIngredient($history + //…); $recipe->boil($history + //…); $recipe->getEstimatedABV($history);
  17. PHP BEER BREWING SOFTWARE FOR BITTER DEVELOPERS WHO WANT TO

    BREW EVEN MORE BITTER BEERS And never heard of not-invented-here and wanna learn and do other good stuff too.