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

It Feels Great to Iterate

It Feels Great to Iterate

Iterators are an awesome and important feature of PHP, and PHP comes with a lot of them built in too. Let’s talk about what they are, how they’re used, and how to make your own. Then we'll talk about generators, which is really cool PHP 5.5+ feature that takes iterators to the next level. And… we can’t talk about iterators without also discussing the Decorator design pattern. After all, most of the SPL iterator classes are decorators too. It’s never felt so great to iterate! May the foreach be with you!

Jeremy Lindblom

April 07, 2015
Tweet

More Decks by Jeremy Lindblom

Other Decks in Programming

Transcript

  1. It Feels Great to Iterate By Jeremy Lindblom • @jeremeamia

    ( see also: @seaphp • @awsforphp • @phpbard )
  2. WARNING This is not a talk about agile development, but,

    yes, “iterate” is a somewhat overloaded term, so I can see how you might have been confused.
  3. Is it a(n)… a.  List b.  Map c.  Vector d. 

    Queue e.  Stack f.  All of the above []  
  4. array_column(…);   array_diff(…);   array_filter(…);   array_keys(…);   array_map(…);  

    array_merge(…);   array_push(…);   array_slice(…);   array_unshift(…);  
  5. $speaker  =  [      'name'        

     =>  'Jeremy  Lindblom',      'twitter'    =>  '@jeremeamia',      'employer'  =>  'AWS  (Amazon)',      'bio'            =>  [          'Works  on  AWS  SDK  for  PHP',          'Is  president  of  @SeaPHP',          'Is  PHP-­‐FIG  rep  for  Guzzle',          'Writes  PHPoetry  as  @phpbard',      ]   ];  
  6. foreach  ($data  as  $item)  {          echo

     $item  .  "\n";   }   Iterating arrays
  7. foreach  ($data  as  $key  =>  $value)  {      

     echo  $key  .  ':'  .  $value  .  "\n";   }   Iterating arrays (w/ keys)
  8. $data  =  new  DirectoryIterator(__DIR__);   foreach  ($data  as  $file)  {

             echo  $file-­‐>getFilename()  .  "\n";   }   Iterating objects?
  9. $data  =  new  DirectoryIterator(__DIR__);   foreach  ($data  as  $file)  {

             echo  $file-­‐>getFilename()  .  "\n";   }   Iterating objects?
  10. AppendIterator ArrayIterator CachingIterator CallbackFilterIterator DirectoryIterator EmptyIterator FilesystemIterator FilterIterator GlobIterator InfiniteIterator

    Iterator IteratorAggregate IteratorIterator LimitIterator MultipleIterator NoRewindIterator OuterIterator ParentIterator RecursiveArrayIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIteratorIterator RecursiveTreeIterator RegexIterator SimpleXMLIterator SplFileObject SplQueue SplStack
  11. •  Iterator •  Iterate / Traverse / "foreach over" • 

    Iterable / Traversable •  Iteration •  Emit / Yield Quick Terms
  12. if  ($object  instanceof  Traversable)  {          echo

     "It  can  be  used  in  a  foreach.";   }   Traversable objects
  13. TIP #1 You can tell if a value is foreach-able

    by checking if it is an array or an instance of Traversable.
  14. class  Foo  implements  Traversable  {          …

      }   >  PHP  Fatal  error:  Class  Foo  must     >  implement  interface  Traversable  as     >  part  of  either  Iterator  or   >  IteratorAggregate.   Implement Traversable? NOPE!
  15. interface  Iterator  {        function  current();  //  mixed|false

           function  key();          //  scalar|null        function  next();        //  void        function  rewind();    //  void        function  valid();      //  bool   }   Iterator Interface
  16. foreach  ($iterator  as  $key  =>  $value)  {      

       //  Do  stuff!   }     //  THIS  IS  THE  SAME  AS:     $iterator-­‐>rewind();   while  ($iterator-­‐>valid())  {          $key  =  $iterator-­‐>key();          $value  =  $iterator-­‐>current();          //  Do  stuff!          $iterator-­‐>next();   }  
  17. $numbers  =  new  HundoIterator();   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Example Implementation
  18. class  HundoIterator  implements  Iterator  {      private  $num  =

     1;      function  current()  {            return  $this-­‐>valid()  ?  $this-­‐>num  :  false;      }      function  key()  {          return  $this-­‐>valid()  ?  $this-­‐>num  –  1  :  null;      }      function  next()  {$this-­‐>num++;}      function  rewind()  {$this-­‐>num  =  1;}      function  valid()  {return  $this-­‐>num  <=  100;}   }   Example Implementation
  19. TIP #4 To avoid bugs, you should make your Iterator's

    methods “idempotent” per iteration.
  20. class  HundoIterator  implements  Iterator  {      private  $num  =

     1;      function  current()  {            return  $this-­‐>valid()  ?  $this-­‐>num  :  false;      }      function  key()  {          return  $this-­‐>valid()  ?  $this-­‐>num  –  1  :  null;      }      function  next()  {$this-­‐>num++;}      function  rewind()  {$this-­‐>num  =  1;}      function  valid()  {return  $this-­‐>num  <=  100;}   }   Idempotency?
  21. $numbers  =  new  HundoIterator();   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Example Implementation
  22. $numbers  =  range(1,  100);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Array Implementation
  23. $numbers  =  range(1,  100);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Array Implementation Yeah! See…?
  24. $numbers  =  range(1,  100);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Array Implementation Wait for it…
  25. $numbers  =  range(1,  1000000000);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }   When arrays don't work
  26. $numbers  =  range(1,  1000000000);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  PHP  Fatal  error:    Allowed  memory   >  size  of  536870912  bytes  exhausted   When arrays don't work Oh noes!
  27. $numbers  =  range(1,  1000000000);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  PHP  Fatal  error:    Allowed  memory   >  size  of  536870912  bytes  exhausted   When arrays don't work Inorite?
  28. class  RangeIterator  implements  Iterator  {      private  $value  =

     1;      private  $limit;      function  __construct($limit)  {          $this-­‐>limit  =  $limit;      }      //  ...      function  valid()  {          return  ($this-­‐>value  <=  $this-­‐>limit);      }   }   Will iterators work?
  29. $numbers  =  new  RangeIterator(1000000000);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  999999999   >  1000000000   Will iterators work?
  30. $numbers  =  new  RangeIterator(1000000000);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  999999999   >  1000000000   Will iterators work? Ah, I see.
  31. $numbers  =  new  RangeIterator(1000000000);   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  999999999   >  1000000000   Will iterators work? Yep. Told ya.
  32. Encapsulation[0] <ul>   <?php  foreach  ($notes  as  $note):  ?>  

         <li><?=  $note-­‐>getMessage()  ?></li>   <?php  endforeach;  ?>   </ul>  
  33. Encapsulation[1] $sql  =  'SELECT  *  FROM  notes';   $stmt  =

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  $stmt-­‐>fetchAll();  
  34. Encapsulation[1] $sql  =  'SELECT  *  FROM  notes';   $stmt  =

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  $stmt-­‐>fetchAll();   •  Loads all results into memory. •  Hard to transform data.
  35. Encapsulation[2] $sql  =  'SELECT  *  FROM  notes';   $stmt  =

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  [];   while  ($row  =  $stmt-­‐>fetch())  {    $notes[]  =  new  Note($row);   }  
  36. Encapsulation[2] $sql  =  'SELECT  *  FROM  notes';   $stmt  =

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  [];   while  ($row  =  $stmt-­‐>fetch())  {    $notes[]  =  new  Note($row);   }   •  Loads all results into memory. •  Easy to transform data.
  37. Encapsulation[3] $sql  =  'SELECT  *  FROM  notes';   $stmt  =

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  [];   foreach  ($stmt  as  $row)  {    $notes[]  =  new  Note($row);   }   •  Loads all results into memory. •  Easy to transform data.
  38. Encapsulation[4] class  NoteIterator  extends  IteratorIterator  {        

     public  function  current()  {                  return  new  Note(parent::current());          }   }  
  39. TIP #5 Be lazy. Delay doing anything with data until

    you absolutely need to. Iterators help with this.
  40. Encapsulation[5] $sql  =  'SELECT  *  FROM  notes';   $stmt  =

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  new  NoteIterator($stmt);   •  Uses memory wisely. •  Easy to transform data. •  Encapsulates logic. •  Notes created lazily.  
  41. Composition $notes  =  new  NoteIterator($stmt);   PDOStatement   (Traversable)  

    We "wrapped" one iterator with another, composing their behaviors together.
  42. AppendIterator ArrayIterator CachingIterator CallbackFilterIterator DirectoryIterator EmptyIterator FilesystemIterator FilterIterator GlobIterator InfiniteIterator

    Iterator IteratorAggregate IteratorIterator LimitIterator MultipleIterator NoRewindIterator OuterIterator ParentIterator RecursiveArrayIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIteratorIterator RecursiveTreeIterator RegexIterator SimpleXMLIterator SplFileObject SplQueue SplStack
  43. $nums  =  new  RangeIterator(100);   foreach  ($nums  as  $num)  {

             echo  $num  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Iterators
  44. interface  Iterator  {        function  current();  //  mixed|false

           function  key();          //  scalar|null        function  next();        //  void        function  rewind();    //  void        function  valid();      //  bool   }   Iterators
  45. LimitIterator   $nums  =  new  RangeIterator(100);   $nums  =  new

     LimitIterator($nums,  10,  5);   foreach  ($nums  as  $num)  {          echo  $num  .  "\n";   }     >  10   >  11   >  12   >  13   >  14  
  46. CallbackFilterIterator   $nums  =  new  RangeIterator(100);   $nums  =  new

     CallbackFilterIterator($nums,          function  ($n)  {return  $n  %  5  ===  0;}   );   foreach  ($nums  as  $num)  {          echo  $num  .  "\n";   }   >  5   >  10   >  …   >  95   >  100  
  47. Decorate! $nums  =  new  RangeIterator(100);     $nums  =  new

     CallbackFilterIterator($nums,          function  ($n)  {return  $n  %  5  ===  0;}   );     $nums  =  new  LimitIterator($nums,  10,  5);  
  48. Decorate! foreach  ($nums  as  $num)  {        

     echo  $num  .  "\n";   }     >  10   >  15   >  20   >  25   >  30  
  49. Generators $stmt-­‐>execute();     $getNotes  =  function  ($stmt)  {  

         foreach  ($stmt  as  $row)  {              yield  new  Note($row);        }   };     $notes  =  $getNotes($stmt);  
  50. It Feels Great to Iterate By Jeremy Lindblom • @jeremeamia

    ( see also: @seaphp • @awsforphp • @phpbard )
  51. Vocabulary •  Iterator •  Traversable •  Interface •  Encapsulation • 

    Composition •  Decorator •  Generator •  Adapter •  Idempotent •  SOLID •  Inheritance •  Proxy