Make your code
do your job

Presented by Larry Garfield (@Crell)

@Crell

Larry implements Huggable
  • Director of Runtimes and Integrations, Platform.sh
  • Drupal 8 Web Services Lead
  • Drupal Representative, PHP-FIG
  • implements Huggable

Humans got smarter by making tools
do our work for us.

Make your tools do your job

Your job is...

To write code

To write correct (error-free) code

Be smarter

Make your tools find the errors for you

Static analysis

I can tell it's wrong just by looking at it

My tools can tell it's wrong just by looking at it

E_ALL

PHP Mess Detector

Add information to analyze

Approximate your code

Abstracting values


function sum_square($a, $b) {
  return ($a * $a) + ($b * $b);
}
        

Number {
  POSITIVE,
  NEGATIVE,
  ZERO,
  UNKNOWN
}
          

POSITIVE * POSITIVE = POSITIVE;          POSITIVE + POSITIVE = POSITIVE;
POSITIVE * NEGATIVE = NEGATIVE;          NEGATIVE + NEGATIVE = NEGATIVE;
NEGATIVE * NEGATIVE = POSITIVE;          POSITIVE + NEGATIVE = UNKNOWN;
POSITIVE * ZERO = ZERO;                  POSITIVE + ZERO = POSITIVE;
NEGATIVE * ZERO = ZERO;                  NEGATIVE + ZERO = NEGATIVE;
UNKNOWN  * ZERO = ZERO;                  POSITIVE + UNKNOWN = UNKNOWN;
UNKNOWN  * POSITIVE = UNKNOWN;           NEGATIVE + UNKNOWN = UNKNOWN;
UNKNOWN  * NEGATIVE = UNKNOWN;           UNKNOWN  + ZERO = UNKNOWN;
        

          The result will always be POSITIVE or ZERO, never NEGATIVE.
        

Errors


function bigger($x, $y) {
  return ($x > $y) ? $x : 0;
}

function compute($a, $b, $c) {
  return ($a + $b) / ($b * $c);
}
        

compute($a, $b, bigger($x, $y));
        

What do we know?

Errors


function bigger($x, $y) {
  return ($x > $y) ? "the first" : "the second";
}

function compute($a, $b, $c) {
  return ($a + $b) / ($b * $c);
}

compute($a, $b, bigger($x, $y));
        

What do we know?

Put the abstract version in code


function bigger(number $x, number $y) returns number or string {
  return ($x > $y) ? $x : 'the second';
}
function compute(number $a, number $b, number $c) returns a number {
  return ($a + $b) / ($b * $c);
}
        

May or may not work


function bigger(number $x, number $y) returns a string {
  return ($x > $y) ? "the first" : "the second";
}
function compute(number $a, number $b, number $c) returns a number {
  return ($a + $b) / ($b * $c);
}
        

Obviously wrong!


function bigger(number $x, number $y) returns a number {
  return ($x > $y) ? 1 : 2;
}
function compute(number $a, number $b, number $c) returns a number {
  return ($a + $b) / ($b * $c);
}
        

Cannot fail!

Type systems

A type system is a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute.

—Benjamin Pierce, Types and Programming Languages

Semantic metadata about code we can analyze to find bugs

Typing crash course

Static types
Specified ahead of time, analyzed by the compiler
Dynamic types
Derived by the compiler/runtime on the fly
Explicit types
Specified in the source code by a human
Implicit types
Compiler derives types from context
Strict types (PHP)
Variables only change type explicitly
Weak types (PHP)
Variables can change type implicitly where possible

Types in PHPractice


function compute_shipping($src, $user) {
  $dest = $user['addres'];

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

function distance_between($src, $dest) {
  // ...
}

compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

HTTP Referer header, anyone?


function compute_shipping($src, $user) {
  $dest = $user['address'];

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

function compute_shipping($src, $user) {
  $dest = $user['address'];

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

$user = 123;
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

function compute_shipping($src, array $user) {
  $dest = $user['address'];

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

$user = 123;
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

function compute_shipping($src, array $user) {
  $dest = $user['address'];

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

$user = ['id' => 123];
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

class User {
  public $id;
  public $address;

  public function __construct($id, $address) {
    $this->id = $id;
    $this->address = $address;
  }
}

function compute_shipping($src, User $user) {
  $dest = $user->address;

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

$user = new User(/*...*/);

compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

function compute_shipping($src, User $user) {
  $dest = $user->address;

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

$user = new User(123, [
  'street' => '456 Main',
  'city' => 'Chicago',
  'state' => 'IL',
  'zip' => 60614,
]);

compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
        

Does this still work? Up to distance_between()


class User {
  public $id;
  public $address;

  public function __construct($id, Address $address) {
    $this->id = $id;
    $this->address = $address;
  }
}

class Address {
  public $street;
  public $city;
  public $state;
  public $zip;
}

function distance_between(Address $src, Address $dest) { /* ... */ }

function compute_shipping($src, User $user) {
  $dest = $user->address;
  $distance = distance_between($src, $dest);
  return $distance * .5; // 50 cents/km
}

$u_address = new Address();
$u_address->street = '456 Main St.';
$u_address->city = 'Chicago';
$u_address->state = 'IL';
$u_address->zip = 60614;

$user = new User(123, $u_address);
$src = new Address(); // ...
compute_shipping($src, $user);
        

class User {
  protected $id;
  protected $address;

  public function __construct($id, Address $address) {
    $this->id = $id;
    $this->address = $address;
  }

  public function getAddress() : Address {
    return $this->address;
  }
}

function compute_shipping($src, User $user) {
  $dest = $user->getAddress();

  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

$u_address = new Address(); // ...

$user = new User(123, $u_address);

$src = new Address(); // ...

compute_shipping($src, $user);
        

Return types

  • New in PHP 7!
  • Not nullable (good) (until 7.1)

$r = new UserRepository();

print $r->findById(123)->getAddress()->getStreet() . PHP_EOL;
        

          Method called on non-object
        

No NULL means:

  • Exception
  • "Empty" value object

For Repositories etc.


class UserRepository {
  // ...

  public function findById($id) : User {
    if (/* can't find the user */) {
     throw new InvalidArgumentException('No such User: ' . $id);
    }
    return $user;
  }

}
        

For Value objects


interface AddressInterface {
  public function getStreet();
  public function getCity();
  public function getState();
  public function getZip();
}

class EmptyAddress implements AddressInterface {
  public function getStreet() { return ''; }
  public function getCity() { return ''; }
  public function getState() { return ''; }
  public function getZip() { return ''; }
}

class Address implements AddressInterface {
    // ...
}
        

Returning false on error is a
big 'screw you' to your users


class User {
  protected $id;
  protected $address;

  public function __construct($id, AddressInterface $address) {
    $this->id = $id;
    $this->address = $address;
  }

  public function getAddress() : AddressInterface {
    return $this->address;
  }
}

function compute_shipping(AddressInterface $src, User $user) {
  $dest = $user->getAddress();
  $distance = distance_between($src, $dest);
  return $distance * .5; // 50 cents/km
}

function distance_between(AddressInterface $src, AddressInterface $dest) {
  // ...
}

$u_address = new Address(); // ...
$user = new User(123, $u_address);

$src = new Address(); // ...
compute_shipping($src, $user);
        

function compute_shipping(AddressInterface $src, AddressInterface $dest) {
  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

function distance_between(AddressInterface $src, AddressInterface $dest) {
  // ...
}

$u_address = new Address(); // ...
$user = new User(123, $u_address);

$src = new Address(); // ...
compute_shipping($src, $user->getAddress());
        

What does distance_between() return?


function compute_shipping(AddressInterface $src, AddressInterface $dest) : float {
  $distance = distance_between($src, $dest);

  return $distance * .5; // 50 cents/km
}

function distance_between(AddressInterface $src, AddressInterface $dest) : int {
  // ...
}

$u_address = new Address(); // ...
$user = new User(123, $u_address);

$src = new Address(); // ...
compute_shipping($src, $user->getAddress());
        

Scalar typing

Everyone here owes
Andrea Faulds and Anthony Ferrara a drink

PHP 7 type list

  • int
  • float
  • string
  • bool
  • array
  • Any class name
  • callable
  • iterable (PHP 7.1)
  • void (PHP 7.1, Return only)

interface AddressInterface {
  public function getStreet();
  public function getCity();
  public function getState();
  public function getZip();
}
        

Is getStreet() a string or object?


interface AddressInterface {
  public function getStreet() : string;
  public function getCity() : string;
  public function getState() : string;
  public function getZip() : string;
}
        

class Address implements AddressInterface {
  public function __construct(string $street, string $city, string $state, string $zip) {
    // ...
  }
  // ...
}
        

$u_address = new Address('45 Hull St', 'Boston', 'MA', 02113);
$user = new User(123, $u_address);

$src = new Address('2211 N. Elston Ave', 'Chicago', 'IL', 60614);
compute_shipping($src, $user->getAddress());
        

Strict vs Weak mode

Weak by default

* string -> int may E_NOTICE

Strict mode

Address.php


declare(strict_types=1);

class Address implements AddressInterface {
  public function __construct(string $street, string $city, string $state, string $zip) {
    // ...
  }
  public function getZip() : string {
    return $this->zip;
  }


  // ...
}
        

Affects calls and returns from this file

Strict mode

setup.php


declare(strict_types=1);

$u_address = new Address('45 Hull St', 'Boston', 'MA', 02113);
$user = new User(123, $u_address);

$src = new Address('2211 N. Elston Ave', 'Chicago', 'IL', 60614);
compute_shipping($src, $user->getAddress());
        

Affects calls and returns from this file

Casting in strict mode

* int converts to float automatically

Using strict types

90% of the time

(Everywhere except Input)

Also, Documentation

No silver bullet

Types find bugs

Types find many bugs

Type checkers have false positives


class ThingOne {
  public function helpCat() { /* ... */ }
}

class ThingTwo {
  public function helpCat() { /* ... */ }
}

class CatInTheHat {

  public function welcome(ThingOne $thing) {
    $thing->helpCat();
  }
}
        

Type checking will reject valid programs!

So, testing?

Program testing can be used to show the presence of bugs, but never to show their absence.

— Edsger W. Dijkstra

Testing will accept invalid programs!

Testing: establishes upper bounds on correctness

Proof: establishes lower bounds on correctness

Chris Smith

Typing is a form of automated proof

Type first, then test

We've outsourced testing to language syntax!

Larry Garfield

Director of Runtimes and Integrations Platform.sh

Continuous Deployment Cloud Hosting

Stalk us at @PlatformSH

Further reading