Platform.sh
Presented by Larry Garfield (@Crell)
implements Huggable
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
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', 02113);
$user = new User(123, $u_address);
$src = new Address('2211 N Elston Ave', 'Chicago', 'IL', 60614);
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('45 Hull St', 'Boston', 'MA', 02113);
$user = new User(123, $u_address);
$src = new Address('2211 N Elston Ave', 'Chicago', 'IL', 606014);
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 portableWhat 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!
class RemoteSource implements Iterator {
protected $key;
protected $current;
protected $client;
public function __construct(Client $client) {
$this->client = $client;
$this->key = 0;
}
public function current() {
return $this->current;
}
public function next() {
$this->current = $this->client->get('http://api.com/entry/' . $this->key);
$this->key++;
}
public function key() {
return $this->key;
}
public function valid() {
return (bool)$this->current;
}
public function rewind() {
throw new Exception();
}
}
$result = $db->query(...);
$iterator = new AppendIterator();
$iterator->append(new RemoteSource($client));
$iterator->append($result);
foreach ($iterator as $item) {
// Do something.
}
function getRemoteValues($client) {
$key = 0;
while ($val = $client->get('http://api.com/entry/' . $key++)) {
yield $val;
}
}
function getLocalValues() {
foreach (db()->query(...) as $record) {
yield $record;
}
}
function getValues($client) {
// This is the new part.
yield from getRemoteValues($client);
yield from getLocalValues();
return 'done';
}
$values = getValues($client);
foreach ($values as $item) {
// Do stuff
}
print $val->getReturn() . PHP_EOL;
$$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: Rasmus Lerdorf
Credit: Rasmus Lerdorf
Credit: Rasmus Lerdorf
Credit: Rasmus Lerdorf
But I'm scared of .0 releases!
http://phpversions.info/php-7/
PHP 7-ready hosts: 34 and counting
(Yes, Platform.sh is one of them!)
Director of Runtimes and Integrations Platform.sh
Continuous Deployment Cloud Hosting
Stalk us at @PlatformSH
Thanks to Palantir.net for helping develop these slides