Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Use Generics in PHP. Today.

Use Generics in PHP. Today.

Developers in other languages like Java, C#, or TypeScript, have been enjoying the benefits of generics for a long time. They allow us to describe precise types of values included in a collection object or to work around type system limitations without the need for unsafe type casting.

PHP does not support generics natively but with the help of PHPDocs, we're able to simulate them. Static analysers like PHPStan or Psalm are able to interpret generics and take advantage of them to find more bugs thanks to strongly-typed code.

In this talk I'll introduce the concept of generics, show several useful use cases to the audience, and even dive into how they're implemented in PHPStan and what had to be considered during the development.

Ondřej Mirtes

June 08, 2021
Tweet

More Decks by Ondřej Mirtes

Other Decks in Programming

Transcript

  1. Use Generics in PHP. Today. Ondřej Mirtes PHPFest, October 23rd

    2020
  2. Ondřej Mirtes Breaking the builds at Slevomat by day, 


    creating PHPStan by night. @OndrejMirtes
  3. $ composer require --dev phpstan/phpstan What is PHPStan? $ vendor/bin/phpstan

    analyse src/ tests/
  4. What is PHPStan?

  5. if ($foo instanceof Foo) catch (FooException $e) Foo::class function foo(Bar

    $bar)
  6. /** 
 * @param int $param 
 * @return int

    
 */ 
 function foo($param) 
 { 
 } 
 What are generics for?
  7. /** 
 * @param string $param 
 * @return string

    
 */ 
 function foo($param) 
 { 
 } 
 What are generics for?
  8. /** 
 * @param float $param 
 * @return float

    
 */ 
 function foo($param) 
 { 
 } 
 What are generics for?
  9. /** 
 * @param mixed $param 
 * @return mixed

    
 */ 
 function foo($param) 
 { 
 } 
 What are generics for?
  10. None
  11. class Entry<KeyType, ValueType> { 
 ... 
 } 
 $entry

    = new Entry<int,string>(1, 'test'); This is how real 
 generics would look like
  12. Type variables /** 
 * @template T 
 * @param

    T $param 
 * @return T 
 */ 
 function foo($param) 
 { 
 } 

  13. Type variable bound /** 
 * @template T of \Exception

    
 * @param T $param 
 * @return T 
 */ 
 function foo($param) 
 { 
 } 

  14. Class name /** 
 * @template T 
 * @param

    class-string<T> $param 
 * @param int $id 
 * @return T 
 */ 
 function find( 
 string $className, int $id 
 ) 
 
 $a = find(Article::class, 1) 
 // $a is Article
  15. Arrays with generic types /** 
 * @template T 


    * @param class-string<T> $param 
 * @return T[] 
 */ 
 function findAll(string $className) 
 { 
 } 

  16. /** 
 * @template K 
 * @template V 


    * @template V2 
 * @param callable(V): V2 $callback 
 * @param array<K, V> $array 
 * @return array<K, V2> 
 */ 
 function ???($callback, $array) {} What function is this?
  17. Type variable above class /** 
 * @template T 


    */ class Collection 
 { 
 }
  18. Type variable above class /** 
 * @param T $item

    
 */ 
 public function add($item): void
  19. Generic objects /** 
 * @param Collection<Dog> $dogs 
 */

    
 function x(Collection $dogs) 
 { 
 $dogs->add(new Cat()); 
 }
  20. Covariance & contravariance Animal Dog Covariance Contravariance

  21. Parameter must be contravariant interface DogFeeder { 
 function feed(Dog

    $dog); 
 } 
 
 class BulldogFeeder 
 implements DogFeeder { 
 function feed(Bulldog $dog) {} 
 }
  22. function feedChihuahua( 
 DogFeeder $feeder 
 ) { 
 $feeder->feed(new

    Chihuahua()); 
 } 
 
 feedChihuahua(new BulldogFeeder()); Parameter must be contravariant
  23. interface DogFeeder { 
 function feed(Dog $dog); 
 } 


    
 class AnimalFeeder 
 implements DogFeeder { 
 function feed(Animal $m) {} 
 } Parameter must be contravariant
  24. Return type must be covariant interface DogShelter { 
 function

    get(): Dog; 
 } 
 
 class AnimalShelter 
 implements DogShelter { 
 function get(): Animal {} 
 }
  25. function getDogAndBark( 
 DogShelter $shelter 
 ) { 
 $shelter->get()->bark();

    
 } 
 
 getDogAndBark(new AnimalShelter()); Return type must be covariant
  26. interface DogShelter { 
 function get(): Dog; 
 } 


    
 class ChihuahuaShelter 
 implements DogShelter { 
 function get(): Chihuahua {} 
 } Return type must be covariant
  27. None
  28. interface Consumer { 
 function consume(Message $msg); 
 } class

    SendMailMessageConsumer { 
 implements Consumer { 
 function consume( 
 SendMailMessage $msg 
 ) {} 
 } These rules can be troublesome
  29. /** 
 * @template T of Message 
 */ 


    interface Consumer { 
 /** 
 * @param T $msg 
 */ 
 function consume(Message $msg); 
 } Generics will save us!
  30. /** 
 * @implements Consumer<SendMailMessage> 
 */ 
 class SendMailMessageConsumer

    
 implements Consumer {...} 
 Specifying parent's type variables
  31. /** 
 * @extends Collection<Dog> 
 */ 
 class DogCollection

    
 extends Collection {...} 
 Specifying parent's type variables
  32. /** 
 * @template T 
 * @extends Collection<T> 


    */ 
 class BetterCollection 
 extends Collection { 
 ... 
 } 
 Preserving genericness when inheriting
  33. IDE compatibility /** 
 * @phpstan-template T of \Exception 


    * @param \Exception $param 
 * @phpstan-param T $param 
 * @return \Exception 
 * @phpstan-return T 
 */ 
 function foo($param) { }
  34. Generics – more examples

  35. Built-in generic PHP classes

  36. None
  37. None
  38. None
  39. @OndrejMirtes