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.

Best practices for crafting high quality PHP apps (Bulgaria 2019)

52 views

Published on

This prototype works, but it’s not pretty, and now it’s in production. That legacy application really needs some TLC. Where do we start? When creating long lived applications, it’s imperative to focus on good practices. The solution is to improve the whole development life cycle; from planning, better coding and testing, to automation, peer review and more. In this tutorial, we’ll take a deep dive into each of these areas, looking at how we can make positive, actionable change in our workflow.

This workshop intends to improve your skills in planning, documenting, some aspects of development, testing and delivery of software for both legacy and greenfield projects. The workshop is made up of multiple exercises, allowing dynamic exploration into the various aspects of the software development life cycle. In each practical exercise, we’ll brainstorm and investigate solutions, ensuring they are future-proofed, well tested and lead to the ultimate goal of confidence in delivering stable software.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Best practices for crafting high quality PHP apps (Bulgaria 2019)

  1. 1. @asgrim Best practices for crafting high quality PHP apps James Titcumb Bulgaria PHP 2019 Please clone & composer install: https://github.com/asgrim/quality-tutorial
  2. 2. $ whoami James Titcumb www.jamestitcumb.com www.roave.com @asgrim Please clone & composer install: https://github.com/asgrim/quality-tutorial
  3. 3. @asgrim Please clone & composer install: https://github.com/asgrim/quality-tutorial
  4. 4. @asgrim What is “quality”?
  5. 5. @asgrim photo: Rob Allen https://flic.kr/p/qmGpsq
  6. 6. @asgrim
  7. 7. @asgrim
  8. 8. @asgrim Quality.
  9. 9. @asgrim "The best code is no code at all” -- Jeff Atwood source: https://blog.codinghorror.com/the-best-code-is-no-code-at-all/
  10. 10. @asgrim Trade-offs.
  11. 11. @asgrim Prototyping & short-lived apps/sites
  12. 12. @asgrim Products Long-lived projects. Open source software.
  13. 13. @asgrim What is quality in applications?
  14. 14. @asgrim What about time/cost…?
  15. 15. @asgrim “A freelancer at $25 an hour for 100 hours still costs more than a freelancer at $150 an hour that takes 10 hours to do the same task” -- Brandon Savage source: http://www.brandonsavage.net/earning-more-money-as-a-php-freelancer/
  16. 16. @asgrim Get an expert in.
  17. 17. @asgrim Complexity
  18. 18. @asgrim Processes
  19. 19. @asgrim This talk... ● Planning ● Development ● Testing ● Continuous integration ● Code reviews ● Deployments
  20. 20. @asgrim Planning
  21. 21. @asgrim Planning is communication
  22. 22. @asgrim Use business terminology
  23. 23. @asgrim Explore and discover
  24. 24. @asgrim Build a model of the business
  25. 25. @asgrim
  26. 26. @asgrim Event Storming Practical Exercise
  27. 27. @asgrim Event Storming - Practical Exercise ● Booking a hotel room ● Customer must select dates ● Rooms must be paid for when booking ● Stripe is the chosen payment provider ● Payment must only be taken once room is reserved ● Room availability is checked & reserved via 3rd party API ● All rooms & dates cost the same (e.g. $100 per night) ● Notify the customer (e.g. email) on a successful reservation ● Customer can cancel a booking ● On cancellation, full refund if more than 48 hours before the stay
  28. 28. @asgrim
  29. 29. @asgrim The model must be fluid.
  30. 30. @asgrim Feature Planning Practical Exercise
  31. 31. @asgrim Feature Planning Feature: Booking a room for a hotel Scenario: Making a successful booking Given ... When ... Then ... Scenario: Cancelling an existing booking Given ... When ... Then ... Scenario: If a room is booked by the time payment is made, do not charge the card or reserve the room Given ... When ... Then ...
  32. 32. @asgrim “Estimates”
  33. 33. @asgrim Development
  34. 34. @asgrim Care about code
  35. 35. @asgrim “There are only two hard things in Computer Science: cache invalidation and naming things.” -- Phil Karlton source: https://martinfowler.com/bliki/TwoHardThings.html
  36. 36. @asgrim SimpleBeanFactoryAwareAspectInstanceFactory
  37. 37. @asgrim Loader
  38. 38. @asgrim Describe intent
  39. 39. @asgrim “Give awkward names to awkward concepts” -- Eric Evans source: https://skillsmatter.com/conferences/8231-ddd-exchange-2017
  40. 40. @asgrim SOLID
  41. 41. @asgrim SOLID Violation Example interface Thing { public function doStuff() : void; } final class ImplementationOfThing implements Thing { public function doStuff() : void { ... } public function moreStuff() : void; } class ConsumerOfThing { /** @var Thing */ private $thing; public function woo() { $this->thing->moreStuff(); } }
  42. 42. @asgrim KISS
  43. 43. @asgrim Object Calisthenics ● One level of indentation in a method ● Avoid “else” keyword ● Wrap primitive types (e.g. strings, int) ● First class collections (instead of Thing[]) ● One object operator (Law of Demeter) ● Avoid abbreviations (naming!) ● Keep classes small ● Avoid more than 2 instance variables ● No getters/setters
  44. 44. @asgrim Avoid early abstraction
  45. 45. @asgrim “Code for your use-case, not for your re-use-case” -- Marco Pivetta source: https://ocramius.github.io/extremely-defensive-php/#/39
  46. 46. @asgrim Care about your API
  47. 47. @asgrim A public method is like a child: once you've written it, you are going to maintain it for the rest of its life! -- Stefan Priebsch
  48. 48. @asgrim Strict type declarations. Use declare(strict_types=1); by default
  49. 49. @asgrim Immutable value objects declare(strict_types=1); use AssertAssertion; // beberlei/assert library ! final class PostalCode { private const VALIDATION_EXPRESSION = '(GIR 0AA)|((([A-Z-[QVX]][0-9][0-... /** @var string */ private $value; public function __construct(string $value) { $this->assertValidPostalCode($value); $this->value = $value; } private function assertValidPostalCode(string $value) : string { Assertion::regex($value, self::VALIDATION_EXPRESSION); } public function __toString() : string { return $this->value; } }
  50. 50. @asgrim Value objects are valid! declare(strict_types=1); final class Thing { public function assignPostalCode(PostalCode $postalCode) : void { // ... we can trust $postalCode is a valid postal code } } // 12345 is not valid - EXCEPTION! $myThing->assignPostalCode(new PostalCode('12345')); // assignPostalCode is happy $myThing->assignPostalCode(new PostalCode('PO1 1AA')); // With STRICT types, this will also FAIL $myThing->assignPostalCode(new PostalCode(12345));
  51. 51. @asgrim Types & Value Objects Practical
  52. 52. @asgrim Static Analysis with phpstan / Psalm Practical
  53. 53. @asgrim Testing
  54. 54. @asgrim Testing is NOT a separate line item
  55. 55. @asgrim Testing should be an assumption
  56. 56. @asgrim You’re already testing.
  57. 57. @asgrim Still need convincing?
  58. 58. @asgrim How?
  59. 59. @asgrim Reduce complexity
  60. 60. @asgrim Reduce complexity public function process(Stuff a, string b, int c) : MoreStuff { // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code
  61. 61. @asgrim Reduce complexity public function meaningfulThing(Stuff a, string b, int c) : More { // call nicer, meaningful methods below } private function throwStuffIntoFire(Stuff a) : Fire { // smaller, meaningful chunk of code } private function combineStringWithFire(Fire a, string b) : string { // simple, lovely code! } private function postFlowersToSpain(int c) : void
  62. 62. @asgrim More complexity = more tests
  63. 63. @asgrim Testing Legacy Code Yes it’s possible… but harder
  64. 64. @asgrim Test coverage
  65. 65. @asgrim Line coverage <?php function foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; } } // generate coverage using calls: foo(true, false); foo(false, true);
  66. 66. @asgrim Line coverage <?php function foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; } } // generate coverage using calls: foo(true, false); foo(false, true);
  67. 67. @asgrim Branch coverage <?php function foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; } } // generate coverage using calls: foo(true, false); foo(false, true);
  68. 68. @asgrim Branch coverage <?php function foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; } } // generate coverage using calls: foo(true, false); foo(false, true);
  69. 69. @asgrim Branch coverage <?php function foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; } } // generate coverage using calls: foo(true, false); foo(false, true); foo(false, false); // NEW TEST!!!
  70. 70. @asgrim Branch coverage <?php function foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; } } // generate coverage using calls: foo(true, false); foo(false, true); foo(false, false); // NEW TEST!!! foo(true, true); // NEW TEST!!!
  71. 71. @asgrim Branch vs Line Coverage
  72. 72. @asgrim Prevent coverage leaking
  73. 73. @asgrim Prevent coverage leaking <?php namespace Foo; /** * @covers FooBar */ final class BarTest extends TestCase { // write some tests! }
  74. 74. @asgrim Prevent coverage leaking
  75. 75. @asgrim Are the tests testing?
  76. 76. @asgrim Example of a test not testing… public function testPurchaseTickets() { $event = Event::create(uniq('event', true) $customer = Customer::create(uniq('name', true)); $shop->purchaseTicket(1, $event, [$customer]); } @asgrim
  77. 77. @asgrim Assert you’re asserting public function testPurchaseTickets() { $event = Event::create(uniq('event', true) $customer = Customer::create(uniq('name', true)); $receipt = $shop->purchaseTicket(1, $event, [$customer]); self::assertSame($event, $receipt->event()); $customersInReceipt = $receipt->customers(); self::assertCount(1, $customersInReceipt); self::assertContains($customer, $customersInReceipt); } @asgrim
  78. 78. @asgrim Test the tests are testing!
  79. 79. @asgrim Infection PHP
  80. 80. @asgrim Mutation testing function add(int $a, int $b) : int { return $a + $b; } function testAdd() { $result = add(2, 3); // self::assertSame(5, $result); }
  81. 81. @asgrim Mutation testing function add(int $a, int $b) : int { return $a - $b; } function testAdd() { $result = add(2, 3); // self::assertSame(5, $result); }
  82. 82. @asgrim Mutation testing function add(int $a, int $b) : int { return $a - $b; } function testAdd() { $result = add(2, 3); self::assertSame(5, $result); // /// test will now fail with mutation }
  83. 83. @asgrim What about other tests?
  84. 84. @asgrim Integration tests
  85. 85. @asgrim Behaviour tests
  86. 86. @asgrim BAD! Do not do this. Feature: Ability to print my boarding pass Scenario: A checked in passenger can print their boarding pass Given I have a flight booked And I have checked in When I visit the home page And I click the ".manage-booking" button And I enter "CJM23L" in the ".bref-ipt-fld" field And I click the "Find Booking" button Then I should see the ".booking-ref.print" button When I click the ".booking-ref.print" button Then I should see the print dialogue
  87. 87. @asgrim Better Behaviour test Feature: Ability to print my boarding pass Policies: - Boarding passes are only available when already checked in - If customer cannot print boarding pass, they can collect at The airport - more business rules etc... Scenario: A checked in passenger can print their boarding pass Given I have a flight booked for LHR-MIA on 24th May And I have previously checked in for the flight When I display my booking reference "CJM23L" Then I should be able to print my boarding pass And the boarding pass should display flight details correctly
  88. 88. @asgrim Why is this important?
  89. 89. @asgrim Automate these tests
  90. 90. @asgrim Automated tests Application (and UI, API, etc.) Domain / Business Logic Infrastructure (DBAL, APIs, etc.)
  91. 91. @asgrim Automated tests Application (and UI, API, etc.) Domain / Business Logic Infrastructure (DBAL, APIs, etc.)
  92. 92. @asgrim Testing at domain layer Application (UI, API, etc.) Domain / Business Logic Infrastructure (DB, APIs, etc.) // Testing via the UI public function iDisplayMyBookingReference(string $reference) { $page = $this->getSession()->getPage(); $page->click(".manage-booking"); $page->findField(".bref-ipt-fld")->setValue($reference); $page->click("Find Booking"); } // Using the domain layer directly in tests public function iDisplayMyBookingReference(string $reference) { $this->booking = $this->retrieveBooking($reference); }
  93. 93. @asgrim Some UI testing is okay!!! Feature: Ability to print my boarding pass Policies: - Boarding passes are only available when already checked in - If customer cannot print boarding pass, they can collect at The airport - more business rules etc... @ui Scenario: A checked in passenger can print their boarding pass Given I have a flight booked for LHR-OTP on 24th May And I have previously checked in for the flight When I display my booking reference "CJM23L" Then I should be able to print my boarding pass And the boarding pass should display flight details correctly
  94. 94. @asgrim Automate all the things!
  95. 95. @asgrim Continuous Integration
  96. 96. @asgrim Tests cost money to run (manually)
  97. 97. @asgrim Circle CI / Travis CI etc. (any hosted CI is probably fine)
  98. 98. @asgrim Jenkins
  99. 99. @asgrim What to automate?
  100. 100. @asgrim The goal?
  101. 101. @asgrim Code reviews
  102. 102. @asgrim What to look for in code review?
  103. 103. @asgrim What to look for in code review? Code style.
  104. 104. @asgrim What to look for in code review? Small, atomic changes.
  105. 105. @asgrim What to look for in code review? Composer versions.
  106. 106. @asgrim What to look for in code review? Structure & good practices.
  107. 107. @asgrim What to look for in code review? Tests.
  108. 108. @asgrim What to look for in code review? Documentation.
  109. 109. @asgrim What to look for in code review? Security.
  110. 110. @asgrim What to look for in code review? Insight.
  111. 111. @asgrim It takes practice. Some ideas: https://asgrim.github.io/code-review-checklist/
  112. 112. @asgrim Deployments
  113. 113. @asgrim Automate deployments!
  114. 114. @asgrim One-click deployments
  115. 115. @asgrim “Move fast and break things” -- a stupid Facebook mantra
  116. 116. @asgrim “Move fast and break things with stable infra” -- Facebook mantra since 2014
  117. 117. @asgrim Continuous Delivery & Deployment
  118. 118. @asgrim Better quality
  119. 119. @asgrim Better quality = Higher confidence
  120. 120. @asgrim Better quality = Higher confidence = Happy customers
  121. 121. Any questions? https://joind.in/talk/f74a3 James Titcumb @asgrim
  122. 122. @asgrim Resources ● Event Storming - Alberto Brandolini ○ http://ziobrando.blogspot.co.uk/2013/11/introducing-event-storming.html ● “Good Design is Imperfect Design” - Eric Evans ○ https://skillsmatter.com/skillscasts/9171-good-design-is-imperfect-design ● “Writing code that lasts” - Rafael Dohms ○ https://www.youtube.com/watch?v=I0y5jU61pS4 ● Object Calisthenics - William Durand ○ https://williamdurand.fr/2013/06/03/object-calisthenics/ ● “Driving Design through Examples” - Ciaran McNulty ○ https://www.youtube.com/watch?v=83GbyDpJDI4 ● Code Review Checklist ○ https://asgrim.github.io/code-review-checklist/

×