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.
Gen AI in Business - Global Trends Report 2024.pdf
Best practices for crafting high quality PHP apps - PHP UK 2019
1. @asgrim
Best practices for crafting high
quality PHP apps
James Titcumb
PHP UK 2019
Please clone & composer install:
https://github.com/asgrim/quality-tutorial
WiFi: phpukconference / community
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/
31. @asgrim
“There are only two hard things in Computer
Science: cache invalidation and naming things.”
-- Phil Karlton
source: https://martinfowler.com/bliki/TwoHardThings.html
44. @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;
}
}
45. @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));
53. @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
54. @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
66. @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
71. @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
}
75. @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
76. @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
81. @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);
}
82. @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