Platform.sh
Presented by Larry Garfield (@Crell)
 
      implements Huggable 
        (Credit: https://seld.be/notes/php-versions-stats-2018-1-edition)
 
        (Credit: https://seld.be/notes/php-versions-stats-2018-1-edition)
Do you trust your docblock?
class User {
  protected $id;
  protected $address;
  public function getAddress() {
    return $this->address;
  }
}
function compute_shipping($src, User $user) {
  $dest = $user->getAddress();
  $distance = distance_between($src, $dest);
  return $distance * .5; // 50 cents/mile
}
        
class User {
  protected $id;
  protected $address;
  /**
   * @return Address
   */
  public function getAddress() {
    return $this->address;
  }
}
function compute_shipping($src, User $user) {
  $dest = $user->getAddress();
  $distance = distance_between($src, $dest);
  return $distance * .5; // 50 cents/mile
}
        
class User {
  protected $id;
  protected $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/mile
}
        
$r = new UserRepository();
print $r->findById(123)->getAddress()->getStreet() . PHP_EOL;
        
          Method called on non-object
        No NULL means:
class UserRepository {
  // ...
  public function findById($id) : User {
    if (/* can't find the user */) {
     throw new InvalidArgumentException('No such User: ' . $id);
    }
    return $user;
  }
}
        
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 UserRepository {
  // ...
  public function findById($id) : ?User {
    if (/* can't find the user */) {
     return null;
    }
    return $user;
  }
}
        (Please don't)
void returns
function return_to_sender() : void {
  // Do stuff
  // ...
  return;      // Legal
  return null; // Not OK
  return 1;    // Not even a little OK
}
        
function compute_shipping($src, AddressInterface $dest) {
  $distance = distance_between($src, $dest);
  return $distance * .5; // 50 cents/mile
}
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/mile
}
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());
        Everyone here owes
Andrea Faulds and Anthony Ferrara a drink
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', 02215);
$user = new User(123, $u_address);
$src = new Address('1060 W Addison St', 'Chicago', 'IL', 60613);
compute_shipping($src, $user->getAddress());
        Weak by default
* string -> int may E_NOTICE
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
setup.php
declare(strict_types=1);
$u_address = new Address('4 Yawkey Way', 'Boston', 'MA', 02215);
$user = new User(123, $u_address);
$src = new Address('1060 W Addison St', 'Chicago', 'IL', 60613);
compute_shipping($src, $user->getAddress());
        Affects calls and returns from this file
* int converts to float automatically
90% of the time
(Everywhere except Input)
Also, Documentation
 
        New reserved words!
usort($array, function ($a, $b) {
  if ($a < $b) {
    return -1;
  }
  elseif ($a > $b) {
    return 1;
  }
  else {
    return 0;
  }
});
        
usort ($array, function($a, $b) {
  return $a <=> $b;
});
        
usort($people, function (Person $a, Person $b) {
  if ($a->lastName() < $b->lastName()) {
    return -1;
  }
  elseif ($a->lastName() > $b->lastName()) {
    return 1;
  }
  else {
    if ($a->firstName() < $b->firstName()) {
      return -1;
    }
    elseif ($a->firstName() > $b->firstName()) {
      return 1;
    }
    else {
      return 0;
    }
  }
});
        
usort($people, function (Person $a, Person $b) {
  return $a->lastName() < $b->lastName() ? -1
    : ($a->lastName() > $b->lastName() ? 1
      : $a->firstName() < $b->firstName() ? -1
        : ($a->firstName() > $b->firstName() ? 1 : 0));
});
        You're Fired!
usort($people, function (Person $a, Person $b) {
 return [$a->lastName(), $a->firstName()] <=> [$b->lastName(), $b->firstName()];
});
        
$username = $username ? $username : 'Anonymous';
        
$username = $username ?: 'Anonymous';
        But what if $username doesn't exist?
Notice: Undefined variable: username on line 3
        
$username = isset($username) && !is_null($username) ? $username : 'Anonymous';
        NULL coalesce!
$username = $username ?? 'Anonymous';
        
$username = $submitted['username'] ?? $user->username() ?? 'Anonymous';
        ?? checks setness, not truthiness
Cryptography is hard
rand() isn't really randommcrypt() isn't really random (also unsupported)fread(fopen('/dev/urandom'), 16) isn't portable 
      What we want is an easy 
Cryptographically Secure Pseudo-Random Number Generator
CSPRNG!
Ask and ye shall receiveā¦
$junk = random_bytes(16);
$val = random_int(1, 100);
       If you're not using these functions for security...
You're Doing It Wrong!
Reserved function names
random_bytes()random_int()
$mock_repository = $this
  ->getMockBuilder(ThingieRepository::class)
  ->disableOriginalConstructor()
  ->getMock();
$obj1 = new Thingie();
$obj1->setVal('abc');
$obj2 = $this->getMockBuilder(Thingie::class)
  ->disableOriginalConstructor()
  ->getMock();
$obj1 ->method('getVal')->willReturn('def');
$map = [ [1, $obj1], [2, $obj2] ];
$mock_repository
  ->method('load')->will($this->returnValueMap($map));
$subject = new ClassUnderTest($mock_repository);
$this->assertTrue($subject->findThings());
         
$fake_repository = new class extends ThingieRepository {
  public function __construct() {}
  public function load($id) {
    switch ($id) {
      case 1:
        $obj1 = new Thingie();
        $obj1->setVal('abc');
        return $obj1;
      case 2:
        return new class extends Thingie {
          public function getVal() {
            return 'def';
          }
        };
    }
  }
};
$subject = new ClassUnderTest($fake_repository);
$this->assertTrue($subject->findThings());
         
new Service(new class implements LoggerInterface {
  use LoggerTrait;
  public function log($message) {
    print $message . PHP_EOL;
  }
});
         
new class($logger) implements ServiceInterface {
  public function __construct(LoggerInterface $logger) {
    $this->logger = $logger;
  }
  public function doServiceStuff() { }
};
         Fatal errors suck
Recoverable errors⦠aren't
function doStuff(Request $r) {
  // ...
}
$u = new User();
doStuff($u);
        Catchable fatal error: Argument 1 passed to doStuff() must be an instance of Request,
instance of User given
function doStuff(Request $r) {
  // ...
}
$u = new User();
try {
  doStuff($u);
}
catch (\TypeError $e) {
  print "Wrong variable type, dummy." . PHP_EOL;
}
        
try {
  include 'buggy_file.php';
}
catch (\ParseError $e) {
  $logger->error("That file is buggy!");
}
        
try {
  nonexistant_function();
}
catch (\Error $e) {
  print "The error was " . $e->getMessage();
}
        E_FATAL, E_RECOVERABLE_ERROR, E_PARSE, E_COMPILE_ERROR
Exceptions: The user screwed up
Errors: The coder screwed up
 
        New reserved class names
Global exception handler
set_exception_handler(function(\Exception $e) {
  // ...
});
        
set_exception_handler(function(\Throwable $e) {
  // ...
});
          
set_exception_handler(function($e) {
  // ...
});
          Assertions suck
function doStuff(array $def) {
  assert(isset($def['key']) && isset($def['value']), 'Invalid def');
  // ...
}
        
function doStuff(array $def) {
  assert('isset($def[\'key\']) && isset($def[\'value\'])', 'Invalid def');
  // ...
}
          
ini_set('assert.exception', 1);
ini_set('zend.assertions', 1);
class InvalidDef extends AssertionError {}
function doStuff(array $def) {
  assert(isset($def['key']) && isset($def['value']), new InvalidDef('Invalid def'));
  // ...
}
        Assertions are actually not-broken!
Iterate this!
function hug_friends(array $friends) {
  foreach ($friends as $friend) {
    hug($friend);
  }
}
        
function hug_friends(\Traversable $friends) {
  foreach ($friends as $friend) {
    hug($friend);
  }
}
        There can be only one!
/**
 * @param array|\Traversable $friends
 */
function hug_friends($friends) {
  foreach ($friends as $friend) {
    hug($friend);
  }
}
        
function hug_friends(iterable $friends) : iterable {
  foreach ($friends as $friend) {
    hug($friend);
  }
  return $friends;
}
        
is_iterable([1, 2, 3]); // true
is_iterable(new ArrayIterator([1, 2, 3])); // true
is_iterable((function () { yield 1; })()); // true
is_iterable(1); // false
is_iterable(new stdClass()); // false
        iterable => foreach()-able
$$foo['bar']['baz']
        
$foo->$bar['baz']
        
$foo->$bar['baz']()
        
Foo::$bar['baz']()
        
Foo::$bar[1][2][3]()
        Always Left-To-Right
$foo()['bar']()
[$obj1, $obj2][0]->prop
getStr(){0}
$foo['bar']::$baz
$foo->bar()::baz()
$foo()()
        
(...)['foo']
(...)->foo
(...)->foo()
(...)()
(function() { ... })()
($obj->closure)()
         
        Complex cases change meaning
When in doubt, use () or {}
 
      (Credit: https://www.flickr.com/photos/martin_heigan/4544138976/)
 
        Credit: Rasmus Lerdorf
 
        Credit: Rasmus Lerdorf
 
        Credit: Rasmus Lerdorf
 
        Credit: Rasmus Lerdorf
 
        Credit: Rasmus Lerdorf
 
        Credit: Rasmus Lerdorf
 
        Credit: Rasmus Lerdorf
But I have legacy code!
You have tests, right...?
https://github.com/squizlabs/PHP_CodeSnifferhttps://github.com/wimg/PHPCompatibilityvendor/bin/phpcs --standard=PHPCompatibility .$beverageoldAndBusted.php
function random_int()
{
    return rand();
}
class Error {}
$a = [1, 2, 3];
$b = ['a', 'b', 'c'];
print $$b[1];
          composer.json
{
    "require-dev": {
        "squizlabs/php_codesniffer": "^2.2 || ^3.0.2",
        "wimg/php-compatibility": "*",
        "DealerDirect/phpcodesniffer-composer-installer": "^0.4.3"
    },
    "prefer-stable" : true
}
          Profit
$ vendor/bin/phpcs --standard=PHPCompatibility oldAndBusted.php
FILE: /home/crell/temp/phptest/oldAndBusted.php
---------------------------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
---------------------------------------------------------------------------------------
 11 | ERROR | Indirect access to variables, properties and methods will be evaluated
    |       | strictly in left-to-right order since PHP 7.0. Use curly braces to
    |       | remove ambiguity.
---------------------------------------------------------------------------------------
Time: 60ms; Memory: 6Mb
          Tools can do 80% of the work
PHP 7.2-ready hosts: 22 and counting
(Yes, Platform.sh is one of them!)





 
        
      Director of Developer Experience, Platform.sh
Continuous Deployment Cloud Hosting
Stalk us at @PlatformSH
Thanks to Palantir.net for helping develop these slides