$30 off During Our Annual Pro Sale. View Details »

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 )

    View Slide

  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.

    View Slide

  3. This talk
    is about
    iterators

    View Slide

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

    View Slide

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

    View Slide

  6. Q. Why is PHP
    so friggin'
    awesome?

    View Slide

  7. array()  

    View Slide

  8. []  
    ( Since PHP 5.4 )

    View Slide

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

    View Slide

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

    View Slide

  11. array_column(…);  
    array_diff(…);  
    array_filter(…);  
    array_keys(…);  
    array_map(…);  
    array_merge(…);  
    array_push(…);  
    array_slice(…);  
    array_unshift(…);  

    View Slide

  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',  
       ]  
    ];  

    View Slide

  13. foreach  ($data  as  $item)  {  
           echo  $item  .  "\n";  
    }  
    Iterating arrays

    View Slide

  14. foreach  ($data  as  $key  =>  $value)  {  
         echo  $key  .  ':'  .  $value  .  "\n";  
    }  
    Iterating arrays (w/ keys)

    View Slide

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

    View Slide

  16. View Slide

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

    View Slide

  18. View Slide

  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

    View Slide

  20. View Slide

  21. •  Iterator
    •  Iterate / Traverse / "foreach over"
    •  Iterable / Traversable
    •  Iteration
    •  Emit / Yield
    Quick Terms

    View Slide

  22. if  ($object  instanceof  Traversable)  {  
           echo  "It  can  be  used  in  a  foreach.";  
    }  
    Traversable objects

    View Slide

  23. TIP #1
    You can tell if a value is
    foreach-able by
    checking if it is an
    array or an instance of
    Traversable.

    View Slide

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

    View Slide

  25. class  Foo  implements  Traversable  {  
           …  
    }  
    >  PHP  Fatal  error:  Class  Foo  must    
    >  implement  interface  Traversable  as    
    >  part  of  either  Iterator  or  
    >  IteratorAggregate.  
    Implement Traversable?
    NOPE!

    View Slide

  26. View Slide

  27.  
    Traversable  
     
    Iterator  
     
    IteratorAggregate  

    View Slide

  28. interface  Iterator  {  
         function  current();  //  mixed|false  
         function  key();          //  scalar|null  
         function  next();        //  void  
         function  rewind();    //  void  
         function  valid();      //  bool  
    }  
    Iterator Interface

    View Slide

  29. foreach  ($iterator  as  $key  =>  $value)  {  
           //  Do  stuff!  
    }  

    View Slide

  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();  
    }  

    View Slide

  31. TIP #2
    foreach automatically
    performs a rewind on
    the iterator.
    Other loops do not.

    View Slide

  32. TIP #3
    Most iterators must be
    rewound before they
    work properly.

    View Slide

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

    View Slide

  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

    View Slide

  35. TIP #4
    To avoid bugs, you
    should make your
    Iterator's methods
    “idempotent” per
    iteration.

    View Slide

  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?

    View Slide

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

    View Slide

  38. COOL!

    View Slide

  39. WAIT!

    View Slide

  40. WHY?

    View Slide

  41. Because…
    OOP?

    View Slide

  42. No, give
    me real
    reasons.

    View Slide

  43. OK…
    Fine!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  47. View Slide

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

    View Slide

  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!

    View Slide

  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?

    View Slide

  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?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. Why Iterators?
    1.  Reduced memory ✓

    View Slide

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

    View Slide

  57. Encapsulation[0]
     
     
         =  $note-­‐>getMessage()  ?>  
     
     

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

  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.

    View Slide

  62. View Slide

  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.

    View Slide

  64. Encapsulation[4]
    class  NoteIterator  extends  IteratorIterator  {  
           public  function  current()  {  
                   return  new  Note(parent::current());  
           }  
    }  

    View Slide

  65. TIP #5
    Be lazy. Delay doing
    anything with data until
    you absolutely need to.
    Iterators help with this.

    View Slide

  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.  

    View Slide

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

    View Slide

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

    View Slide

  69. Composition
    When one object
    contains another object,
    combining their
    behaviors.

    View Slide

  70. Composition
    $notes  =  new  NoteIterator($stmt);  
    PDOStatement  
    (Traversable)  
    We "wrapped" one
    iterator with another,
    composing their
    behaviors together.

    View Slide

  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

    View Slide

  72. And now a
    poem about
    Decorators,
    by the
    @phpbard

    View Slide

  73. View Slide

  74.  
    Component  
    Component   ComponentDecorator  
     

    View Slide

  75. Foo  extends  Bar  

    View Slide

  76. $nums  =  new  RangeIterator(100);  
    foreach  ($nums  as  $num)  {  
           echo  $num  .  "\n";  
    }  
     
    >  1  
    >  2  
    >  …  
    >  99  
    >  100  
    Iterators

    View Slide

  77. interface  Iterator  {  
         function  current();  //  mixed|false  
         function  key();          //  scalar|null  
         function  next();        //  void  
         function  rewind();    //  void  
         function  valid();      //  bool  
    }  
    Iterators

    View Slide

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

    View Slide

  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  

    View Slide

  80. LimitIterator   CallbackFilterItr  
     
    Iterator  
    OurCoolIterator  

    View Slide

  81. LimitIterator   CallbackFilterItr  
     
    Iterator  
    OurCoolIterator  
    ?

    View Slide

  82. LimitIterator   CallbackFilterItr  
     
    Iterator  
    OurCoolIterator  
    x  

    View Slide

  83. Decorate!
    $nums  =  new  RangeIterator(100);  
     
    $nums  =  new  CallbackFilterIterator($nums,  
           function  ($n)  {return  $n  %  5  ===  0;}  
    );  
     
    $nums  =  new  LimitIterator($nums,  10,  5);  

    View Slide

  84. Decorate!
    foreach  ($nums  as  $num)  {  
           echo  $num  .  "\n";  
    }  
     
    >  10  
    >  15  
    >  20  
    >  25  
    >  30  

    View Slide

  85. LimitIterator  
    CallbackFilterIterator  
    RangeIterator  
     
    current()  
    current()  
    current()  

    View Slide

  86. Thank you!
    @phpbard
    phpbard.tumblr.com

    View Slide

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

    View Slide

  88. Generators
    $stmt-­‐>execute();  
     
    $getNotes  =  function  ($stmt)  {  
         foreach  ($stmt  as  $row)  {  
               yield  new  Note($row);  
         }  
    };  
     
    $notes  =  $getNotes($stmt);  

    View Slide

  89. TIP #7
    Read the Manual.

    View Slide

  90. It Feels Great
    to Iterate
    By Jeremy Lindblom • @jeremeamia
    ( see also: @seaphp • @awsforphp • @phpbard )

    View Slide

  91. Vocabulary
    •  Iterator
    •  Traversable
    •  Interface
    •  Encapsulation
    •  Composition
    •  Decorator
    •  Generator
    •  Adapter
    •  Idempotent
    •  SOLID
    •  Inheritance
    •  Proxy

    View Slide