SlideShare a Scribd company logo
@asgrim
Crafting Quality PHP
Applications: an overview
James Titcumb
PHPSW 2018 meetup
$ whoami
James Titcumb
www.jamestitcumb.com
www.roave.com
@asgrim
@asgrim
@asgrim
What is “quality”?
@asgrim
photo: Rob Allen https://flic.kr/p/qmGpsq
@asgrim
photo: https://goo.gl/QHWXQL
@asgrim
photo: Rob Allen https://flic.kr/p/ecFbH8
@asgrim
Quality.
@asgrim
"The best code is no code at all”
-- Jeff Atwood
source: https://blog.codinghorror.com/the-best-code-is-no-code-at-all/
@asgrim
Trade-offs.
@asgrim
Prototyping & short-lived apps/sites
@asgrim
Products
Long-lived projects. Open source software.
@asgrim
What is quality in applications?
@asgrim
What about time/cost…?
@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/
@asgrim
Get an expert in.
@asgrim
Complexity
@asgrim
Processes
@asgrim
This talk...
● Planning
● Development
● Testing
● Continuous integration
● Code reviews
● Deployments
@asgrim
Planning
@asgrim
Planning is communication
@asgrim
Use business terminology
@asgrim
Explore and discover
@asgrim
Build a model of the business
@asgrim
@asgrim
@asgrim
The model must be fluid.
@asgrim
Development
@asgrim
Care about code
@asgrim
“There are only two hard things in Computer
Science: cache invalidation and naming things.”
-- Phil Karlton
source: https://martinfowler.com/bliki/TwoHardThings.html
@asgrim
SimpleBeanFactoryAwareAspectInstanceFactory
@asgrim
Loader
@asgrim
Describe intent
@asgrim
“Give awkward names to awkward concepts”
-- Eric Evans
source: https://skillsmatter.com/conferences/8231-ddd-exchange-2017
@asgrim
SOLID
@asgrim
KISS
@asgrim
Object Calisthenics
@asgrim
Avoid early abstraction
@asgrim
“Code for your use-case,
not for your re-use-case”
-- Marco Pivetta
source: https://ocramius.github.io/extremely-defensive-php/#/39
@asgrim
Care about your API
@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
@asgrim
Strict type declarations.
Use declare(strict_types=1); by default
@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;
}
}
@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));
@asgrim
Testing
@asgrim
Testing is NOT a separate line item
@asgrim
Testing should be an assumption
@asgrim
You’re already testing.
@asgrim
Still need convincing?
@asgrim
How?
@asgrim
Reduce complexity
@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
@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
@asgrim
More complexity = more tests
@asgrim
Test coverage
@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);
@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);
@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);
@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);
@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!!!
@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!!!
@asgrim
Prevent coverage leaking
@asgrim
Prevent coverage leaking
<?php
namespace Foo;
/**
* @covers FooBar
*/
final class BarTest extends TestCase
{
// write some tests!
}
@asgrim
Are the tests testing?
@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
@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
@asgrim
Test the tests are testing!
@asgrim
Mutation testing
function add(int $a, int $b) : int {
return $a + $b;
}
function testAdd() {
$result = add(2, 3);
// self::assertSame(5, $result);
}
@asgrim
Mutation testing
function add(int $a, int $b) : int {
return $a - $b;
}
function testAdd() {
$result = add(2, 3);
// self::assertSame(5, $result);
}
@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
}
@asgrim
What about other tests?
@asgrim
Integration tests
@asgrim
Behaviour tests
@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
@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-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
@asgrim
Why is this important?
@asgrim
Automate these tests
@asgrim
Automated tests
Application (and UI, API, etc.)
Domain / Business Logic
Infrastructure (DBAL, APIs, etc.)
@asgrim
Automated tests
Application (and UI, API, etc.)
Domain / Business Logic
Infrastructure (DBAL, APIs, etc.)
@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);
}
@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
@asgrim
Automate all the things!
@asgrim
Continuous Integration
@asgrim
Tests cost money to run
(manually)
@asgrim
Travis-CI.com
@asgrim
Jenkins
@asgrim
What to automate?
@asgrim
The goal?
@asgrim
Code reviews
@asgrim
What to look for in code review?
@asgrim
What to look for in code review?
Code style.
@asgrim
What to look for in code review?
Small, atomic changes.
@asgrim
What to look for in code review?
Composer versions.
@asgrim
What to look for in code review?
Structure & good practices.
@asgrim
What to look for in code review?
Tests.
@asgrim
What to look for in code review?
Documentation.
@asgrim
What to look for in code review?
Security.
@asgrim
What to look for in code review?
Insight.
@asgrim
It takes practice.
@asgrim
Deployments
@asgrim
Automate deployments!
@asgrim
One-click deployments
@asgrim
“Move fast and break things”
-- a stupid Facebook mantra
@asgrim
“Move fast and break things with stable infra”
-- Facebook mantra since 2014
@asgrim
Continuous Delivery & Deployment
@asgrim
Better quality
@asgrim
Better quality = Higher confidence
@asgrim
Better quality = Higher confidence = Happy customers
Any questions?
https://joind.in/talk/96cee
James Titcumb
@asgrim

More Related Content

What's hot (19)

PDF
Dip Your Toes in the Sea of Security (ConFoo YVR 2017)
James Titcumb
 
PDF
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
James Titcumb
 
PDF
Best practices for crafting high quality PHP apps (Bulgaria 2019)
James Titcumb
 
PDF
Best practices for crafting high quality PHP apps (php[world] 2019)
James Titcumb
 
PDF
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
James Titcumb
 
PDF
Beyond MVC: from Model to Domain
Jeremy Cook
 
PDF
"Managing API Complexity". Matthew Flaming, Temboo
Yandex
 
PDF
TDD with PhpSpec - Lone Star PHP 2016
CiaranMcNulty
 
PDF
Deliver Business Value Faster with AWS Step Functions
Daniel Zivkovic
 
PDF
Moving away from legacy code with BDD
Konstantin Kudryashov
 
ODP
Non Conventional Android Programming En
guest9bcef2f
 
PPTX
Understand prototype
Zhu Qi
 
ODP
Scti 2011 minicurso jquery
ciberglo
 
PDF
Effectively Testing Services - Burlington Ruby Conf
neal_kemp
 
PPTX
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Matteo Collina
 
PDF
Testing web APIs
FDConf
 
PPT
Eugene Andruszczenko: jQuery
Refresh Events
 
PDF
Rails for Beginners - Le Wagon
Alex Benoit
 
PDF
Michelle Sanver "Using the Workflow component for e-commerce"
Fwdays
 
Dip Your Toes in the Sea of Security (ConFoo YVR 2017)
James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
James Titcumb
 
Best practices for crafting high quality PHP apps (Bulgaria 2019)
James Titcumb
 
Best practices for crafting high quality PHP apps (php[world] 2019)
James Titcumb
 
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
James Titcumb
 
Beyond MVC: from Model to Domain
Jeremy Cook
 
"Managing API Complexity". Matthew Flaming, Temboo
Yandex
 
TDD with PhpSpec - Lone Star PHP 2016
CiaranMcNulty
 
Deliver Business Value Faster with AWS Step Functions
Daniel Zivkovic
 
Moving away from legacy code with BDD
Konstantin Kudryashov
 
Non Conventional Android Programming En
guest9bcef2f
 
Understand prototype
Zhu Qi
 
Scti 2011 minicurso jquery
ciberglo
 
Effectively Testing Services - Burlington Ruby Conf
neal_kemp
 
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Matteo Collina
 
Testing web APIs
FDConf
 
Eugene Andruszczenko: jQuery
Refresh Events
 
Rails for Beginners - Le Wagon
Alex Benoit
 
Michelle Sanver "Using the Workflow component for e-commerce"
Fwdays
 

Similar to Crafting Quality PHP Applications: an overview (PHPSW March 2018) (20)

PDF
Your code are my tests
Michelangelo van Dam
 
PDF
Test and API-driven development of CakePHP Behaviors
Pierre MARTIN
 
PDF
Php tests tips
Damian Sromek
 
PDF
Save time by applying clean code principles
Edorian
 
PDF
Finding the Right Testing Tool for the Job
CiaranMcNulty
 
PDF
Living the Best Life on a Legacy Project (phpday 2022).pdf
James Titcumb
 
PDF
Dutch PHP Conference 2013: Distilled
Zumba Fitness - Technology Team
 
PDF
Testing untestable code - IPC12
Stephan Hochdörfer
 
PDF
Narrow Down What to Test
Zsolt Fabok
 
PDF
Intro to PHP Testing
Ran Mizrahi
 
PPT
PHP Unit Testing
Tagged Social
 
PDF
Testing untestable code - phpconpl11
Stephan Hochdörfer
 
PPT
Unit testing
davidahaskins
 
PDF
Property Based Testing in PHP
vinaikopp
 
PDF
Testing untestable code - phpday
Stephan Hochdörfer
 
PDF
Symfony (Unit, Functional) Testing.
Basel Issmail
 
PDF
Software Quality and Test Strategies for Ruby and Rails Applications
Bhavin Javia
 
PDF
Fighting Fear-Driven-Development With PHPUnit
James Fuller
 
PDF
TDD with PhpSpec
CiaranMcNulty
 
PDF
Testing untestable code - ConFoo13
Stephan Hochdörfer
 
Your code are my tests
Michelangelo van Dam
 
Test and API-driven development of CakePHP Behaviors
Pierre MARTIN
 
Php tests tips
Damian Sromek
 
Save time by applying clean code principles
Edorian
 
Finding the Right Testing Tool for the Job
CiaranMcNulty
 
Living the Best Life on a Legacy Project (phpday 2022).pdf
James Titcumb
 
Dutch PHP Conference 2013: Distilled
Zumba Fitness - Technology Team
 
Testing untestable code - IPC12
Stephan Hochdörfer
 
Narrow Down What to Test
Zsolt Fabok
 
Intro to PHP Testing
Ran Mizrahi
 
PHP Unit Testing
Tagged Social
 
Testing untestable code - phpconpl11
Stephan Hochdörfer
 
Unit testing
davidahaskins
 
Property Based Testing in PHP
vinaikopp
 
Testing untestable code - phpday
Stephan Hochdörfer
 
Symfony (Unit, Functional) Testing.
Basel Issmail
 
Software Quality and Test Strategies for Ruby and Rails Applications
Bhavin Javia
 
Fighting Fear-Driven-Development With PHPUnit
James Fuller
 
TDD with PhpSpec
CiaranMcNulty
 
Testing untestable code - ConFoo13
Stephan Hochdörfer
 
Ad

More from James Titcumb (17)

PDF
Tips for Tackling a Legacy Codebase (ScotlandPHP 2021)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (Midwest PHP 2020)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (php[world] 2019)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (PHP Russia 2019)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (ScotlandPHP 2018)
James Titcumb
 
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP South Africa 2018)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (PHP Developer Days Dresden 2018)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (Southeast PHP 2018)
James Titcumb
 
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (PHP UK 2018)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (Forum PHP 2017)
James Titcumb
 
PDF
Dip Your Toes in the Sea of Security (IPC Fall 2017)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (IPC Fall 2017)
James Titcumb
 
PDF
Dip Your Toes in the Sea of Security (PHP South Africa 2017)
James Titcumb
 
PDF
Climbing the Abstract Syntax Tree (PHP South Africa 2017)
James Titcumb
 
PDF
Get Started with RabbitMQ (CoderCruise 2017)
James Titcumb
 
PDF
Dip Your Toes in the Sea of Security (CoderCruise 2017)
James Titcumb
 
Tips for Tackling a Legacy Codebase (ScotlandPHP 2021)
James Titcumb
 
Climbing the Abstract Syntax Tree (Midwest PHP 2020)
James Titcumb
 
Climbing the Abstract Syntax Tree (php[world] 2019)
James Titcumb
 
Climbing the Abstract Syntax Tree (PHP Russia 2019)
James Titcumb
 
Climbing the Abstract Syntax Tree (ScotlandPHP 2018)
James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (PHP South Africa 2018)
James Titcumb
 
Climbing the Abstract Syntax Tree (PHP Developer Days Dresden 2018)
James Titcumb
 
Climbing the Abstract Syntax Tree (Southeast PHP 2018)
James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)
James Titcumb
 
Climbing the Abstract Syntax Tree (PHP UK 2018)
James Titcumb
 
Climbing the Abstract Syntax Tree (Forum PHP 2017)
James Titcumb
 
Dip Your Toes in the Sea of Security (IPC Fall 2017)
James Titcumb
 
Climbing the Abstract Syntax Tree (IPC Fall 2017)
James Titcumb
 
Dip Your Toes in the Sea of Security (PHP South Africa 2017)
James Titcumb
 
Climbing the Abstract Syntax Tree (PHP South Africa 2017)
James Titcumb
 
Get Started with RabbitMQ (CoderCruise 2017)
James Titcumb
 
Dip Your Toes in the Sea of Security (CoderCruise 2017)
James Titcumb
 
Ad

Recently uploaded (20)

PDF
Market Insight : ETH Dominance Returns
CIFDAQ
 
PDF
RAT Builders - How to Catch Them All [DeepSec 2024]
malmoeb
 
PPTX
The Future of AI & Machine Learning.pptx
pritsen4700
 
PDF
TrustArc Webinar - Navigating Data Privacy in LATAM: Laws, Trends, and Compli...
TrustArc
 
PPTX
Simple and concise overview about Quantum computing..pptx
mughal641
 
PPTX
AI and Robotics for Human Well-being.pptx
JAYMIN SUTHAR
 
PPTX
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
PDF
A Strategic Analysis of the MVNO Wave in Emerging Markets.pdf
IPLOOK Networks
 
PPTX
IT Runs Better with ThousandEyes AI-driven Assurance
ThousandEyes
 
PDF
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
PDF
Economic Impact of Data Centres to the Malaysian Economy
flintglobalapac
 
PDF
OFFOFFBOX™ – A New Era for African Film | Startup Presentation
ambaicciwalkerbrian
 
PDF
MASTERDECK GRAPHSUMMIT SYDNEY (Public).pdf
Neo4j
 
PDF
Build with AI and GDG Cloud Bydgoszcz- ADK .pdf
jaroslawgajewski1
 
PDF
Data_Analytics_vs_Data_Science_vs_BI_by_CA_Suvidha_Chaplot.pdf
CA Suvidha Chaplot
 
PDF
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
PPTX
Agile Chennai 18-19 July 2025 Ideathon | AI Powered Microfinance Literacy Gui...
AgileNetwork
 
PDF
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
PPTX
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
PDF
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 
Market Insight : ETH Dominance Returns
CIFDAQ
 
RAT Builders - How to Catch Them All [DeepSec 2024]
malmoeb
 
The Future of AI & Machine Learning.pptx
pritsen4700
 
TrustArc Webinar - Navigating Data Privacy in LATAM: Laws, Trends, and Compli...
TrustArc
 
Simple and concise overview about Quantum computing..pptx
mughal641
 
AI and Robotics for Human Well-being.pptx
JAYMIN SUTHAR
 
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
A Strategic Analysis of the MVNO Wave in Emerging Markets.pdf
IPLOOK Networks
 
IT Runs Better with ThousandEyes AI-driven Assurance
ThousandEyes
 
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
Economic Impact of Data Centres to the Malaysian Economy
flintglobalapac
 
OFFOFFBOX™ – A New Era for African Film | Startup Presentation
ambaicciwalkerbrian
 
MASTERDECK GRAPHSUMMIT SYDNEY (Public).pdf
Neo4j
 
Build with AI and GDG Cloud Bydgoszcz- ADK .pdf
jaroslawgajewski1
 
Data_Analytics_vs_Data_Science_vs_BI_by_CA_Suvidha_Chaplot.pdf
CA Suvidha Chaplot
 
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
Agile Chennai 18-19 July 2025 Ideathon | AI Powered Microfinance Literacy Gui...
AgileNetwork
 
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 

Crafting Quality PHP Applications: an overview (PHPSW March 2018)