Happy SPLing Marcus Brger phpworks Marcus Brger Happy
Happy SPLing Marcus Börger php|works Marcus Börger Happy SPLing
Happy SPLing þ Discuss overloadable engine features þ Learn about SPL aka Standard PHP Library Marcus Börger Happy SPLing 2
From engine overloading. . . þ Zend engine 2. 0+ allows to overload the following þ by implementing interfaces þ Foreach by implementing Iterator, Iterator. Aggregate þ Array access by implementing Array. Access þ Serializing by implementing Serializable (PHP 5. 1) þ by providing magic functions þ Function invocation by method __call() þ Property access by methods __get() and __set() þ Automatic loading of classes by function __autoload() Marcus Börger Happy SPLing 3
. . . to SPL It is easy in a complex way - Lukas Smith php conference 2004 þ A collection of standard interfaces and classes Most of which based around engine overloading þ A few helper functions Marcus Börger Happy SPLing 4
What is SPL about & what for þ Captures some common patterns þ More to follow þ Advanced Iterators þ Functional programming þ Exception hierarchy with documented semantics þ Makes __autoload() useable Marcus Börger Happy SPLing 5
What are Iterators þ Iterators are a concept to iterate anything that contains other things. þ Iterators allow to encapsulate algorithms Marcus Börger Happy SPLing 6
What are Iterators þ Iterators are a concept to iterate anything that contains other things. Examples: þ þ þ þ Values and Keys in arrays Array. Object, Array. Iterator Text lines in a file Spl. File. Object Files in a directory [Recursive]Directory. Iterator XML Elements or Attributes ext: Simple. XML, DOM Database query results ext: PDO, SQLite, My. SQLi Dates in a calendar range PECL/date Bits in an image ? Iterators allow to encapsulate algorithms Marcus Börger Happy SPLing 7
What are Iterators þ Iterators are a concept to iterate anything that contains other things. Examples: þ þ þ þ Values and Keys in an array. Array. Object, Array. Iterator Text lines in a file Spl. File. Object Files in a directory Directory. Iterator XML Elements or Attributes ext: Simple. XML, DOM Database query results ext: PDO, SQLite, My. SQLi Dates in a calendar range PECL/date Bits in an image ? Iterators allow to encapsulate algorithms þ Classes and Interfaces provided by SPL: Append. Iterator, Caching. Iterator, Limit. Iterator, Filter. Iterator, Empty. Iterator, Infinite. Iterator, No. Rewind. Iterator, Outer. Iterator, Parent. Iterator, Recursive. Iterator, Seekable. Iterator, . . . Marcus Börger Happy SPLing 8
The basic concepts þ Iterators can be internal or external also referred to as active or passive þ An internal iterator modifies the object itself þ An external iterator points to another object without modifying it þ PHP always uses external iterators at engine-level þ Iterators may iterate over other iterators Marcus Börger Happy SPLing 9
The big difference þ Arrays þ require memory for all elements þ allow to access any element directly þ Iterators þ þ þ only know one element at a time only require memory for the current element forward access only Access done by method calls Containers þ require memory for all elements þ allow to access any element directly þ can create external Iterators or are internal Iterators Marcus Börger Happy SPLing 10
PHP Iterators þ þ þ Anything that can be iterated implements Traversable Objects implementing Traversable can be used in foreach User classes cannot implement Traversable Iterator. Aggregate is for objects that use external iterators Iterator is for internal traversal or external iterators Marcus Börger Happy SPLing 11
Implementing Iterators Marcus Börger Happy SPLing 12
How Iterators work þ Iterators can be used manually <? php $o = new Array. Iterator(array(1, 2, 3)); $o->rewind(); while ($o->valid()) { $key = $o->key(); $val = $o->current(); // some code $o->next(); } ? > þ Iterators can be used implicitly with foreach <? php $o = new Array. Iterator(array(1, 2, 3)); foreach($o as $key => $val) { // some code } ? > Marcus Börger Happy SPLing 13
Overloading Array access þ PHP provides interface Array. Access þ Objects that implement it behave like normal arrays (only in terms of syntax though) þ Array. Access does not allow references (the following is an error) interface function } Marcus Börger Array. Access { &offset. Get($offset); offset. Set($offset, &$value); offset. Exists($offset); offset. Unset($offset); Happy SPLing 14
Array and property traversal þ Array. Objectallows external traversal of arrays þ Array. Objectcreates Array. Iteratorinstances þ Multiple Array. Iteratorinstances can reference the same target with different states þ Both implement Seekable. Iteratorwhich allows to 'jump' to any position in the Array directly. Marcus Börger Happy SPLing 15
Array and property traversal Marcus Börger Happy SPLing 16
Functional programming? þ þ Abstract from the actual data (types) Implement algorithms without knowing the data Example: Sorting requires a container for elements Sorting requires element comparison Containers provide access to elements Sorting and Containers must not know data Marcus Börger Happy SPLing 17
An example þ þ Reading a menu definition from an array Writing it to the output Problem Handling of hierarchy Detecting recursion Formatting the output Marcus Börger Happy SPLing 18
Recursion with arrays þ A typical solution is to directly call array functions No code reuse possible <? php function recurse_array($ar) { // do something before recursion reset($ar); while (!is_null(key($ar))) { // probably do something with the current element if (is_array(current($ar))) { recurse_array(current($ar)); } // probably do something with the current element // probably only if not recursive next($ar); } // do something after recursion } ? > Marcus Börger Happy SPLing 19
Detecting Recursion þ An array is recursive If the current element itself is an Array In other words current() has children This is detectable by is_array() Recursing requires creating a new wrapper instance for the child array þ Recursive. Iterator is the interface to unify Recursion þ Recursive. Iterator handles the recursion þ þ class Recursive. Array. Iterator extends Array. Iterator implements Recursive. Iterator { function has. Children() { return is_array($this->current()); } function get. Children() { return new Recursive. Array. Iterator($this->current()); } } Marcus Börger Happy SPLing 20
Debug Session <? php $a = array('1', '2', array('31', '32'), '4'); $o = new Recursive. Array. Iterator($a); $i = new Recursive. Iterator($o); foreach($i as $key => $val) { echo "$key => $valn"; } ? > 0 1 3 => => => 1 2 31 32 4 <? php class Recursive. Array. Iterator implements Recursive. Iterator { protected $ar; function __construct(Array $ar) { $this->ar = $ar; } function rewind() { reset($this->ar); } function valid() { return !is_null(key($this->ar)); } function key() { return key($this->ar); } function current() { return current($this->ar); } function next() { next($this->ar); } function has. Children() { return is_array(current($this->ar)); } function get. Children() { return new Recursive. Array. Iterator($this->current()); } } ? > Marcus Börger Happy SPLing 21
Making Array. Object recursive þ Change class type of Array. Objects Iterator We simply need to change get. Iterator() <? php class Recursive. Array. Object extends Array. Object { function get. Iterator() { return new Recursive. Array. Iterator($this); } } ? > Marcus Börger Happy SPLing 22
How does our Menu look? þ þ þ The basic interface is Menu. Item A Menu. Entry is the basic element of class Menu A Menu stores one or more Menu. Item objects A Sub. Menu is a Menu and a Menu. Item A Menu. Iterator shall iterate Menu and Sub. Menu è è è Menu can store Menu. Entry and Sub. Menu can store in a Menu. Entry or Sub. Menu. Item should know whether it has children Menu is a Iterator. Aggregate Menu. Iterator is a Recursive. Iterator Marcus Börger Happy SPLing 23
How does our Menu look? þ The general interface for menu entries þ Only talking to entries through this interface ensures the code works no matter what we later add or change interface Menu. Item { /** @return string representation of item (e. g. name/link)*/ function __to. String(); /** @return whether item has children */ function get. Children(); /** @return children of the item if any available */ function has. Children(); /** @return whether item is active or grayed */ function is. Active(); /** @return whether item is visible or should be hidden */ function is. Visible(); /** @return the name of the entry if any */ function get. Name(); } Marcus Börger Happy SPLing 24
How does our Menu look? þ We need a storage for the items þ Either extend Recursive. Array. Iterator þ Or use an array an implement Iterator. Aggregate class Menu implements Iterator. Aggregate { public $_ar = array(); // PHP does not support friend function add. Item(Menu. Item $item) { $this->_ar[$item->get. Name()] = $item; return $item; } function get. Iterator() { return new Menu. Iterator($this); } } Marcus Börger Happy SPLing 25
How does our Menu look? þ Extend Recursive. Array. Iterator but be typesafe þ Ensure get. Children() returns the correct type þ Elements are non arrays þ Recursion works slightly different þ Override has. Children() to not use is_array() þ Keep existing get. Children() and other iterator methods class Recursive. Array. Iterator Menu. Iterator extends Recursive. Array. Iterator { extends Array. Iterator implements Recursive. Iterator { function __construct(Menu $menu) { function parent: : __construct($menu->_ar); has. Children() { } return is_array($this->current()); function has. Children() { } function returnget. Children() $this->current()->has. Children(); { } if return (empty($ref)) new Recursive. Array. Iterator($this->current()); $this->ref = new Reflection. Class($this); } } return $ref->new. Instance($this->current()); } } protected $ref; } Marcus Börger Happy SPLing 26
How does our Menu look? class Menu. Entry implements Menu. Item { protected $name, $link, $active, $visible; function __construct($name, $link = NULL) { $this->name = $name; $this->link = is_numeric($link) ? NULL : $link; $this->active = true; $this->visible = true; } function __to. String() { if (strlen($this->link)) { return '<a href="'. $this->link. '">'. $this>name. '</a>'; } else { return $this->name; } } function has. Children() { return false; } function get. Children() { return NULL; } function is. Active() { return $this->active; } function is. Visible() { return $this->visible; } function get. Name() { return $this->name; } Marcus Börger Happy SPLing 30
How does our Menu look? class Sub. Menu extends Menu implements Menu. Item { protected $name, $link, $active, $visible; function __construct($name = NULL, $link = NULL) { $this->name = $name; $this->link = is_numeric($link) ? NULL : $link; $this->active = true; $this->visible = true; } function __to. String() { if (strlen($this->link)) { return '<a href="'. $this->link. '">'. $this>name. '</a>'; } else if (strlen($this->name)) { return $this->name; } else return ''; } function has. Children() { return true; } function get. Children() { return new Menu. Iterator($this); } function is. Active() { return $this->active; } function is. Visible() { return $this->visible; } function get. Name() { return $this->name; } Marcus Börger Happy SPLing 31
How to create a menu þ To create a Menu we manually call add. Item() þ We need to keep track of the level in local temp vars <? php $menu = new Menu(); $menu->add. Item(new Menu. Entry('Home')); $sub = new Sub. Menu('Downloads'); $sub->add. Item(new Menu. Entry('')); $menu->add. Item($sub); ? > Marcus Börger Happy SPLing 32
Reading a menu from an array þ þ We'd need to foreach the array and do recursion Recursive. Iterator helps with events class Recursive. Iterator { /** @return $this->get. Inner. Iterator()->has. Children()*/ function call. Has. Children() /** @return $this->get. Inner. Iterator()->get. Children()*/ function call. Get. Children() /** Called if recursing into children */ function begin. Children() /** called after last children */ function end. Children() /** called if a new element is available */ function next. Element() //. . . } Marcus Börger Happy SPLing 33
Reading a menu from array class Menu. Load. Array extends Recursive. Iterator { protected $sub = array(); function __construct(Menu $menu, Array $def) { $this->sub[0] = $menu; parent: : __construct( new Recursive. Array. Iterator($def, self: : LEAVES_ONLY)); } function call. Get. Children() { $child = parent: : call. Get. Children(); $this->sub[] = end($this->sub)->add. Item(new Sub. Menu()); return $child; Provide some storage for the } menu and ist sub menus and function end. Children() { their sub menus. array_pop($this->sub); } function next. Element() { end($this->sub)->add. Item( new Menu. Entry($this->current(), $this->key())); } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); Marcus Börger Happy SPLing 34
Reading a menu from array class Menu. Load. Array extends Recursive. Iterator { protected $sub = array(); function __construct(Menu $menu, Array $def) { $this->sub[0] = $menu; parent: : __construct( new Recursive. Array. Iterator($def, self: : LEAVES_ONLY)); } function call. Get. Children() { $child = parent: : call. Get. Children(); $this->sub[] = end($this->sub)->add. Item(new Sub. Menu()); return $child; Menu. Load. Array controls the } recursice iteration. . . function end. Children() { array_pop($this->sub); …a recursive structure. } function next. Element() { end($this->sub)->add. Item( new Menu. Entry($this->current(), $this->key())); } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); Marcus Börger Happy SPLing 35
Reading a menu from array class Menu. Load. Array extends Recursive. Iterator { protected $sub = array(); function __construct(Menu $menu, Array $def) { $this->sub[0] = $menu; parent: : __construct( new Recursive. Array. Iterator($def, self: : LEAVES_ONLY)); } function call. Get. Children() { $child = parent: : call. Get. Children(); $this->sub[] = end($this->sub)->add. Item(new Sub. Menu()); return $child; When recursing we create a new } unnamed Sub. Menu and make it function end. Children() { the new top level element of our array_pop($this->sub); 'level' storage. } function next. Element() { end($this->sub)->add. Item( new Menu. Entry($this->current(), $this->key())); } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); Marcus Börger Happy SPLing 36
Reading a menu from array class Menu. Load. Array extends Recursive. Iterator { protected $sub = array(); function __construct(Menu $menu, Array $def) { $this->sub[0] = $menu; parent: : __construct( new Recursive. Array. Iterator($def, self: : LEAVES_ONLY)); } function call. Get. Children() { $child = parent: : call. Get. Children(); $this->sub[] = end($this->sub)->add. Item(new Sub. Menu()); return $child; At the end of a sub array in our } case representing a sub menu function end. Children() { when pop that sub menu thus array_pop($this->sub); going to it's parent menu. } function next. Element() { end($this->sub)->add. Item( new Menu. Entry($this->current(), $this->key())); } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); Marcus Börger Happy SPLing 37
Reading a menu from array class Menu. Load. Array extends Recursive. Iterator { protected $sub = array(); function __construct(Menu $menu, Array $def) { $this->sub[0] = $menu; parent: : __construct( new Recursive. Array. Iterator($def, self: : LEAVES_ONLY)); } function call. Get. Children() { $child = parent: : call. Get. Children(); $this->sub[] = end($this->sub)->add. Item(new Sub. Menu()); return $child; All elements in our definition that } are not sub arrays are meant to function end. Children() { end up as entries so we only array_pop($this->sub); want leaves as elements. } function next. Element() { end($this->sub)->add. Item( new Menu. Entry($this->current(), $this->key())); } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); Marcus Börger Happy SPLing 38
Reading a menu from array class Menu. Load. Array extends Recursive. Iterator { protected $sub = array(); function __construct(Menu $menu, Array $def) { $this->sub[0] = $menu; parent: : __construct( new Recursive. Array. Iterator($def, self: : LEAVES_ONLY)); } function call. Get. Children() { $child = parent: : call. Get. Children(); $this->sub[] = end($this->sub)->add. Item(new Sub. Menu()); return $child; Now lets use thing to fill in the } menu from the definition in the function end. Children() { array_pop($this->sub); } function next. Element() { end($this->sub)->add. Item( new Menu. Entry($this->current(), $this->key())); } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); Marcus Börger Happy SPLing 39
Output HTML þ Problem how to format the output using </ul> Detecting recursion begin/end class Menu. Output extends Recursive. Iterator { function __construct(Menu $menu) { parent: : __construct($menu); } function begin. Children() { // called after childs rewind() is called echo str_repeat(' ', $this>get. Depth()). "<ul>n"; } function end. Children() { // right before child gets destructed echo str_repeat(' ', $this>get. Depth()). "</ul>n"; } } Marcus Börger Happy SPLing 40
Output HTML þ þ Problem how to write the output Echo the output within foreach The following works for our Array def <ul> <li>1</li> <li>2</li> <ul> <li>31</li> <li>32</li> </ul> <li>4</li> </ul> class Menu. Output extends Recursive. Iterator { function __construct(Recursive. Iterator $ar) { parent: : __construct($ar); } function begin. Children() { echo str_repeat(' ', $this->get. Depth()). "<ul>n"; } function end. Children() { echo str_repeat(' ', $this->get. Depth()). "</ul>n"; } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Recursive. Array. Iterator($def); $it = new Menu. Output($menu); echo "<ul>n"; // for the intro foreach($it as $m) { echo str_repeat(' ', $it->get. Depth()+1)'<li>', $m, "</li>n"; } echo "</ul>n"; // for the outro Marcus Börger Happy SPLing 41
Output HTML þ þ Problem how to write the output Echo the output within foreach The following works for our Menu <ul> <li>1</li> <li>2</li> <ul> <li>31</li> <li>32</li> </ul> <li>4</li> </ul> class Menu. Output extends Recursive. Iterator { function __construct(Menu $ar) { parent: : __construct($ar); } function begin. Children() { echo str_repeat(' ', $this->get. Depth()). "<ul>n"; } function end. Children() { echo str_repeat(' ', $this->get. Depth()). "</ul>n"; } } $def = array('1', '2', array('31', '32'), '4'); $menu = new Menu(); foreach(new Menu. Load. Array($menu, $def) as $v); $it = new Menu. Output($menu); echo "<ul>n"; // for the intro foreach($it as $m) { echo str_repeat(' ', $it->get. Depth()+1)'<li>', $m, "</li>n"; } echo "</ul>n"; // for the outro Marcus Börger Happy SPLing 42
Wow - but why? þ Why did we used SPL here? þ More reliability þ Fix one time – no problem in finnding all incarnations þ Easier to change soemthing without touching other stuff þ Functional separation þ Code ruse þ Responsability control Marcus Börger Happy SPLing 43
Filtering Problem Only recurse into active Menu. Item elements Only show visible Menu. Item elements Changes prevent recurse_array from <? php reuse class Menu. Item Menu { function is. Active() // return true if active function is. Visible() // return true if visible } function recurse_array($ar) { // do something before recursion while (!is_null(key($ar))) { if (is_array(current($ar))) (is_array(current($ar)) && { current($ar)->is. Active()) { recurse_array(current($ar)); } if (current($ar)->current()->is. Active()) { // do something } next($ar); } // do something after recursion } ? > Marcus Börger Happy SPLing 44
Filtering Solution to filter the incoming data Unaccepted data simply needs to be skipped Do not accept inactive menu elements Using a Filter. Iterator interface Menu. Item { //. . . function is. Active() // return true if active function is. Visible() // return true if visible } Marcus Börger Happy SPLing 45
Filter. Iterator þ Filter. Iterator is an abstract Outer. Iterator þ Constructor takes an Iterator (called inner iterator) þ Any iterator operation is executed on the inner iterator þ For every element accept() is called Inside the call current()/key() are valid è All you have to do is implement accept() þ Recursive. Filter. Iterator is also available Marcus Börger Happy SPLing 46
Debug Session <? php $a = array(1, 2, 5, 8); $i = new Even. Filter(new My. Iterator($a)); foreach($i as $key => $val) { echo "$key => $valn"; } ? > 1 => 2 3 => 8 <? php class Even. Filter extends Filter. Iterator { function __construct(Iterator $it) { parent: : __construct($it); } function accept() { return $this->current() % 2 == 0; } } class My. Iterator implements Iterator { function __construct($ar) { $this->ar = $ar; } function rewind() { reset($this->ar); } function valid() { return !is_null(key($this->ar)); } function current() { return current($this->ar); } function key() { return key($this->ar); } function next() { next($this->ar); } } ? > Marcus Börger Happy SPLing 47
Filtering þ Using a Filter. Iterator <? php class Menu. Filter extends Recursive. Filter. Iterator { function __construct(Menu $m) { parent: : __construct($m); } function accept() { return $this->current()->is. Visible(); } function has. Children() { return $this->current()->has. Children() && $this->current()->is. Active(); } function get. Children() { return new Menu. Filter( $this->current()->get. Children()); } } ? > Marcus Börger Happy SPLing 48
Putting it together þ Make Menu. Output operate on Menu. Filter Pass a Menu to the constructor (guarded by type hint) Create a Menu. Filter from the Menu. Filter implements Recursive. Iterator We could also use a special Menu. Filter/Menu proxy We could also have Menu as an interface of Menu. Filter class Menu. Output extends Recursive. Iterator { function __construct(Menu $m) { parent: : __construct(new Menu. Filter($m)); } function begin. Children() { echo "<ul>n"; } function end. Children() { echo "</ul>n"; } } Marcus Börger Happy SPLing 49
What now þ þ If your menu structure comes from a database If your menu structure comes from XML You have to change Menu or provide an alternative to Menu. Load. Array Detection of recursion works differently No single change in Menu. Output needed No single change in Menu. Filter needed Marcus Börger Happy SPLing 50
Using XML þ Change Menu to inherit from Simple. XMLIterator þ Which is already a Recursive. Iterator þ We need to make it create Menu instances for children class Menu extends Simple. XMLIterator { static function factory($xml) { return simplexml_load_string($xml, 'Menu'); } function is. Active() { return $this['active']; // access attribute } function is. Visible() { return $this['visible']; // access attribute } // get. Children already returns Menu instances } Marcus Börger Happy SPLing 51
Using PDO þ Change Menu to read from database PDO supports Iterator based access PDO can create and read into objects PDO will be integrated into PHP 5. 1 <? php $db = new PDO("mysql: //. . . "); $stmt= $db->prepare("SELECT. . . FROM Menu. . . ", "Menu"); foreach($stmt->execute() as $m) { // fetch now returns Menu instances echo $m; // call $m->__to. String() } ? > Marcus Börger Happy SPLing 52
Conclusion so far þ Iterators require a new way of programming þ Iterators allow to implement algorithms abstracted from data þ Iterators promote code reuse þ Some things are already in SPL þ Filtering þ Handling recursion þ Limiting Marcus Börger Happy SPLing 57
Other magic Marcus Börger Happy SPLing 60
Dynamic class loading þ __autoload() is good when you're alone þ Requires a single file for each class þ Only load class files when necessary þ No need to parse/compile unneeded classes þ No need to check which class files to load ý Additional user space code Only one single loader model is possible Marcus Börger Happy SPLing 61
__autoload & require_once þ Store the class loader in an include file þ In each script: require_once('<path>/autoload. inc') þ Use INI option: auto_prepend_file=<path>/autoload. inc <? php function __autoload($class_name) { require_once( dirname(__FILE__). '/'. $class_name. '. p 5 c'); } ? > Marcus Börger Happy SPLing 62
SPL's class loading þ Supports fast default implementation þ Look into path's specified by INI option include_path þ Look for specified file extensions (. inc, . inc. php) þ Ability to register multiple user defined loaders þ Overwrites ZEND engine's __autoload() cache þ You need to register __autoload if using spl's autoload <? php spl_autoload_register('spl_autoload'); if (function_exists('__autoload')) { spl_autoload_register('__autoload'); } ? > Marcus Börger Happy SPLing 63
SPL's class loading þ þ þ spl_autoload($class_name) Load a class though registered class loaders Fast c cod eimplementation spl_autoload_extensions([$extensions]) Get or set filename extensions spl_autoload_register($loader_function) Registers a single loader function spl_autoload_unregister($loader_function) Unregister a single loader function spl_autoload_functions() List all registered loader functions spl_autoload_call($class_name) Load a class though registered class loaders Uses spl_autoload() as fallback Marcus Börger Happy SPLing 64
THANK YOU þ This Presentation http: //somabo. de/talks/ þ SPL Documentation http: //php. net/~helly Marcus Börger Happy SPLing 65
- Slides: 56