Slide 1

Slide 1 text

Fantastic Bugs and How to Avoid Them PHPUGMRN | JUN 24, 2021 @afilina

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Anna Filina ‣ Coding since 1997. ‣ PHP since 2003. ‣ Legacy archaeologist. ‣ Test automation. ‣ Talks and workshops. ‣ YouTube videos.

Slide 4

Slide 4 text

What is this about? ‣Real-world "silly" bugs. ‣How they came to be. ‣How you can avoid making mistakes. ‣Also, I need to rant about bad code.

Slide 5

Slide 5 text

$paths = $this->getPaths(); $urls = array_map(function ($path) { return self::URL_ROOT . $path; }, $paths); array_map(): Expected parameter 2 to be an array, string given

Slide 6

Slide 6 text

private function getPaths() { return '[ {"list_products":"/products"}, {"view_cart":"/cart"}, ]'; }

Slide 7

Slide 7 text

private function getPaths(): array { return [ $this->path1, $this->path2, ]; }

Slide 8

Slide 8 text

$paths = $this->getPaths(); $urls = array_map(function ($path) { return self::URL_ROOT . $path; }, $paths); Array to string conversion

Slide 9

Slide 9 text

private $path1 = ['list_products' => '/products']; private $path2 = ['view_cart' => '/cart'];

Slide 10

Slide 10 text

private $path1 = '/products'; private $path2 = '/cart';

Slide 11

Slide 11 text

$urls = array_map(function (string $path) { return self::URL_ROOT . $path; }, $paths);

Slide 12

Slide 12 text

/** * @return array */ private function getPaths(): array

Slide 13

Slide 13 text

composer require --dev vimeo/psalm vendor/bin/psalm --init Detected level 7 as a suitable initial default vendor/bin/psalm src

Slide 14

Slide 14 text

/** * @return array */ private function getPaths(): array { return [ $this->path1, $this->path2, ]; } ERROR: MixedReturnTypeCoercion - src/TypeMismatch.php:65:16 - The type 'array{0: mixed, 1: mixed}' is more general than the declared return type 'array' ...

Slide 15

Slide 15 text

/** * @var string */ private string $path;

Slide 16

Slide 16 text

/** * @return array */ private function getPaths(): array { return [ $this->path1, $this->path2, ]; }

Slide 17

Slide 17 text

if ($path === '') { throw new InvalidArgumentException('Is blank'); }

Slide 18

Slide 18 text

$urls = array_map(function (string $path) { return self::URL_ROOT . $path; }, $paths);

Slide 19

Slide 19 text

$urls = array_map(function (Path $path) { return self::URL_ROOT . $path; }, $paths);

Slide 20

Slide 20 text

composer require beberlei/assert

Slide 21

Slide 21 text

final class Path { private string $path; public function __construct(string $path) { Assert::that($path) ->notBlank(); $this->path = $path; } public function __toString(): string { return $this->path; } }

Slide 22

Slide 22 text

/** * @return array */ private function getPaths(): array { return [ new Path('/products'), new Path('/cart'), ]; }

Slide 23

Slide 23 text

array_map(function (Path $path) { return self::URL_ROOT . $path; }, $paths);

Slide 24

Slide 24 text

public function setPrice(int $price) { $this->price = $price; } $this->setPrice(1.15);

Slide 25

Slide 25 text

Slide 26

Slide 26 text

Strict types. Strict types. Strict types.

Slide 27

Slide 27 text

private string $stringPath; private Path $voPath;

Slide 28

Slide 28 text

NPEs galore.

Slide 29

Slide 29 text

class Product { public $name; } //... $this->findByName($this->product->name); TypeError : Argument 1 passed to MyClass::findByName() must be of the type string, null given

Slide 30

Slide 30 text

class Product { public string $name; }

Slide 31

Slide 31 text

class ProductEntity { public $name; public $price; } final class Product { private string $name; private int $price; //... }

Slide 32

Slide 32 text

return new Product( $product->name, $product->price );

Slide 33

Slide 33 text

if ($product->getLastPrice() !== null) { return number_format($product->getLastPrice()); } TypeError : number_format() expects parameter 1 to be float, null given

Slide 34

Slide 34 text

public function getLastPrice() { return array_pop($this->prices); }

Slide 35

Slide 35 text

$lastPrice = $product->getLastPrice(); if ($lastPrice !== null) { return number_format($lastPrice); }

Slide 36

Slide 36 text

@$array[$foo->a()]; public function a() { trigger_error('my error', E_USER_ERROR); } $array[$foo->a()] ?? 'something else';

Slide 37

Slide 37 text

interface ApiAware { public function setApi(Api $api); } if ($class instanceof ApiAware) { $class->setApi($api); }

Slide 38

Slide 38 text

final class MyClass implements ApiAware { private $api; public function setApi(Api $api): void { $this->api = $api; } public function sendApiRequest() { $product = new Product(); $this->api->sendRequest($product); } }

Slide 39

Slide 39 text

Error : Call to a member function sendRequest() on null

Slide 40

Slide 40 text

final class MyClass { private Api $api; public function __construct(Api $api) { $this->api = $api; } public function sendApiRequest() { $product = new Product(); $this->api->sendRequest($product); } }

Slide 41

Slide 41 text

Dependency injection is your friend.

Slide 42

Slide 42 text

if (!empty($array)) { return $array[0]; } Trying to access array offset on value of type bool

Slide 43

Slide 43 text

empty(""); empty(0); empty(0.0); empty("0"); empty(null); empty(false); empty(array());

Slide 44

Slide 44 text

!== "" !== 0 !== 0.0 !== null === true !== []

Slide 45

Slide 45 text

0.99 + 0.01 === 1

Slide 46

Slide 46 text

IEEE 754 floating point arithmetic.

Slide 47

Slide 47 text

$amountInCents + 1

Slide 48

Slide 48 text

/** @var PaymentGatewayInterface */ $gateway = $this->getSelectedGateway(); $gateway->preauthorizePayment();

Slide 49

Slide 49 text

@afilina