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. Ondřej Mirtes Breaking the builds at Slevomat by day, 


    creating PHPStan by night. @OndrejMirtes
  2. /** 
 * @param int $param 
 * @return int

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

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

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

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

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

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

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

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

  9. 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
  10. Arrays with generic types /** 
 * @template T 


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

  11. /** 
 * @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?
  12. Type variable above class /** 
 * @template T 


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

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

    
 function x(Collection $dogs) 
 { 
 $dogs->add(new Cat()); 
 }
  15. Parameter must be contravariant interface DogFeeder { 
 function feed(Dog

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

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


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

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

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


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

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


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

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

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


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


    * @param \Exception $param 
 * @phpstan-param T $param 
 * @return \Exception 
 * @phpstan-return T 
 */ 
 function foo($param) { }