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!

Ca57a7cfac69ba3abf517470f3770aae?s=128

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. This talk is about <?php iterators

  4. Actually, we'll be covering a lot of OOP

  5. ———————— ★ Iterators ★ ———————— ★ Decorators ★ ———————— ★

    Generators ★ ————————
  6. Q. Why is PHP so friggin' awesome?

  7. array()  

  8. []   ( Since PHP 5.4 )

  9. Is it a(n)… a.  List b.  Map c.  Vector d. 

    Queue e.  Stack []  
  10. Is it a(n)… a.  List b.  Map c.  Vector d. 

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

    array_merge(…);   array_push(…);   array_slice(…);   array_unshift(…);  
  12. $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',      ]   ];  
  13. foreach  ($data  as  $item)  {          echo

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

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

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

             echo  $file-­‐>getFilename()  .  "\n";   }   Iterating objects?
  18. None
  19. 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
  20. None
  21. •  Iterator •  Iterate / Traverse / "foreach over" • 

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

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

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

      }   Implement Traversable?
  25. class  Foo  implements  Traversable  {          …

      }   >  PHP  Fatal  error:  Class  Foo  must     >  implement  interface  Traversable  as     >  part  of  either  Iterator  or   >  IteratorAggregate.   Implement Traversable? NOPE!
  26. None
  27. <interface>   Traversable   <interface>   Iterator   <interface>  

    IteratorAggregate  
  28. interface  Iterator  {        function  current();  //  mixed|false

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

       //  Do  stuff!   }  
  30. 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();   }  
  31. TIP #2 foreach automatically performs a rewind on the iterator.

    Other loops do not.
  32. TIP #3 Most iterators must be rewound before they work

    properly.
  33. $numbers  =  new  HundoIterator();   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Example Implementation
  34. 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
  35. TIP #4 To avoid bugs, you should make your Iterator's

    methods “idempotent” per iteration.
  36. 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?
  37. $numbers  =  new  HundoIterator();   foreach  ($numbers  as  $number)  {

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  99   >  100   Example Implementation
  38. COOL!

  39. WAIT!

  40. WHY?

  41. Because… OOP?

  42. No, give me real reasons.

  43. OK… Fine!

  44. $numbers  =  range(1,  100);   foreach  ($numbers  as  $number)  {

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

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

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

             echo  $number  .  "\n";   }   When arrays don't work
  49. $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!
  50. $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?
  51. 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?
  52. $numbers  =  new  RangeIterator(1000000000);   foreach  ($numbers  as  $number)  {

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

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

             echo  $number  .  "\n";   }     >  1   >  2   >  …   >  999999999   >  1000000000   Will iterators work? Yep. Told ya.
  55. Why Iterators? 1.  Reduced memory ✓

  56. Why Iterators? 1.  Reduced memory ✓ 2.  Encapsulation 3.  Lazy

    evaluation 4.  Composition
  57. Encapsulation[0] <ul>   <?php  foreach  ($notes  as  $note):  ?>  

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

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

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

     $pdo-­‐>prepare($sql);   $stmt-­‐>execute();     $notes  =  [];   while  ($row  =  $stmt-­‐>fetch())  {    $notes[]  =  new  Note($row);   }  
  61. 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.
  62. None
  63. 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.
  64. Encapsulation[4] class  NoteIterator  extends  IteratorIterator  {        

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

    you absolutely need to. Iterators help with this.
  66. 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.  
  67. Why Iterators? 1.  Reduced memory ✓ 2.  Encapsulation ✓ 3. 

    Lazy evaluation ✓ 4.  Composition
  68. Why Iterators? 1.  Reduced memory ✓ 2.  Encapsulation ✓ 3. 

    Lazy evaluation ✓ 4.  Composition ✓
  69. Composition When one object contains another object, combining their behaviors.

  70. Composition $notes  =  new  NoteIterator($stmt);   PDOStatement   (Traversable)  

    We "wrapped" one iterator with another, composing their behaviors together.
  71. 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
  72. And now a poem about Decorators, by the @phpbard

  73. None
  74. <interface>   Component   Component   ComponentDecorator   <client>  

  75. Foo  extends  Bar  

  76. $nums  =  new  RangeIterator(100);   foreach  ($nums  as  $num)  {

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

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

     LimitIterator($nums,  10,  5);   foreach  ($nums  as  $num)  {          echo  $num  .  "\n";   }     >  10   >  11   >  12   >  13   >  14  
  79. 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  
  80. LimitIterator   CallbackFilterItr   <interface>   Iterator   OurCoolIterator  

  81. LimitIterator   CallbackFilterItr   <interface>   Iterator   OurCoolIterator  

    ?
  82. LimitIterator   CallbackFilterItr   <interface>   Iterator   OurCoolIterator  

    x  
  83. Decorate! $nums  =  new  RangeIterator(100);     $nums  =  new

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

     echo  $num  .  "\n";   }     >  10   >  15   >  20   >  25   >  30  
  85. LimitIterator   CallbackFilterIterator   RangeIterator   <Client>   current()  

    current()   current()  
  86. Thank you! @phpbard phpbard.tumblr.com

  87. TIP #6 Use composition of iterators instead of implementing new

    classes when possible.
  88. Generators $stmt-­‐>execute();     $getNotes  =  function  ($stmt)  {  

         foreach  ($stmt  as  $row)  {              yield  new  Note($row);        }   };     $notes  =  $getNotes($stmt);  
  89. TIP #7 Read the Manual.

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

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

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