Are Multiple Iterators possible in php? - php

PLEASE CHECK ANSWERS by VolkerK too, he provided another solution, but I can't mark two posts as answer. :(
Good day!
I know that C# allows multiple iterators using yield, like described here:
Is Multiple Iterators is possible in c#?
In PHP there is and Iterator interface. Is it possible to implement more than one iteration scenario for a class?
More details (EDIT):
For example I have class TreeNode implementing single tree node. The whole tree can be expressed using only one this class. I want to provide iterators for iterating all direct and indirect children of current node, for example using BreadthFirst or DepthFirst order.
I can implement this Iterators as separate classes but doing so I need that tree node should expose it's children collection as public.
C# pseudocode:
public class TreeNode<T>
{
...
public IEnumerable<T> DepthFirstEnumerator
{
get
{
// Some tree traversal using 'yield return'
}
}
public IEnumerable<T> BreadthFirstEnumerator
{
get
{
// Some tree traversal using 'yield return'
}
}
}

Yes, you can.
foreach(new IteratorOne($obj) as $foo) ....
foreach(new IteratorTwo($obj) as $bar) .....
Actually, as long as you class implements Iterator, you can apply any arbitrary IteratorIterator to it. This is a Good Thing, because applied meta iterators are not required to know anything about the class in question.
Consider, for example, an iterable class like this
class JustList implements Iterator
{
function __construct() { $this->items = func_get_args(); }
function rewind() { return reset($this->items); }
function current() { return current($this->items); }
function key() { return key($this->items); }
function next() { return next($this->items); }
function valid() { return key($this->items) !== null; }
}
Let's define some meta iterators
class OddIterator extends FilterIterator {
function accept() { return parent::current() % 2; }
}
class EvenIterator extends FilterIterator {
function accept() { return parent::current() % 2 == 0; }
}
Now apply meta iterators to the base class:
$list = new JustList(1, 2, 3, 4, 5, 6, 7, 8, 9);
foreach(new OddIterator($list) as $p) echo $p; // prints 13579
foreach(new EvenIterator($list) as $p) echo $p; // prints 2468
UPDATE: php has no inner classes, so you're kinda out of luck here, without resorting to eval, at least. Your iterators need to be separate classes, which are aware of the baseclass structure. You can make it less harmful by providing methods in the base class that instantiate iterators behind the scenes:
class TreeDepthFirstIterator implements Iterator
{
function __construct($someTree).....
}
class Tree
{
function depthFirst() { return new TreeDepthFirstIterator($this); }
....
}
foreach($myTree->depthFirst() as $node).....
Another option is to use lambdas instead of foreach. This is nicer and more flexible, requires php5.3 though:
class Tree
{
function depthFirst($func) {
while($node = .....)
$func($node);
.....
$myTree->depthFirst(function($node) {
echo $node->name;
});

For your purpose it might be sufficient to have a "mode" flag in your class, so the user can choose whether to have a bread-first or a depth-first iterator.
class Tree {
const TREE_DEPTH_FIRST = 0;
const TREE_BREADTH_FIRST = 0;
protected $mode;
protected $current;
public function __construct($mode=Tree::TREE_DEPTH_FIRST) {
$this->mode = $mode;
}
public function setMode($mode) {
...
}
public function next() {
$this->current = advance($this->current, $this->mode);
}
....
}
(and the short answer to your initial question: no php doesn't have the syntactic sugar of yield return and it doesn't have inner private classes, i.e. whatever you would need the iterator you're returning to do with the "original" object has to be exposed to the outside world. So you'd probably end up "preparing" all elements for an iterator object like ArrayIterator, the very thing you avoid by using yield)

This code shows you how to add multiple iterators in a class.
class TreeNode {
public function getOddIterator () {
return new OddIterator($this->nodes);
}
public function getEvenIterator () {
return new EvenIterator($this->nodes);
}
}

You can have multiple Iterators. The key idea in the Iterator is to take the responsibility for access and traversal out of the list object and put it into an iterator object. So if you want to have multiple Iterators with the same list or different lists; there's no problem.
You can find four different PHP examples here:
http://www.php5dp.com/category/design-patterns/iterator/
You can also use them with Linked Lists.
Cheers,
Bill

Related

Symfony2 Doctrine iterate over with while next()

I'm looking for a working solution, to iterate over a mongodb PersistentCollection in symfony2. Unfortunately this seems not to work? Symfony ignores the next() function!
while (($animal = $zooAnimals->next()) !== false) {
$color = $animal->getColor();
print_r($color); die; // Test and die
}
print_r('Where are the animals?'); die; // << Current result
Reference: Doctrine\ODM\MongoDB\PersistentCollection
This is not Symfony's "fault". This is a misunderstanding of how to iterate over an object. There are several ways to handle this for your use case. Here are some
Use a foreach!
Your PersistentCollection implements Collection which implements IteratorAggregate which implements Traversable (long way heh?).
An object which implements interface Traversable can be used in a foreach statement.
IteratorAggregate forces you to implement one method getIterator which must return an Iterator. This last also implements Traversable interface.
Usage of an iterator
Iterator interface forces your object to declare 5 methods in order to be used by a foreach
class MyCollection implements Iterator
{
protected $parameters = array();
protected $pointer = 0;
public function add($parameter)
{
$this->parameters[] = $parameter;
}
/**
* These methods are needed by Iterator
*/
public function current()
{
return $this->parameters[$this->pointer];
}
public function key()
{
return $this->pointer;
}
public function next()
{
$this->pointer++;
}
public function rewind()
{
$this->pointer = 0;
}
public function valid()
{
return array_key_exists($this->pointer, $this->parameters);
}
}
You can use any class which implements Iterator it like this - Demo file
$coll = new MyCollection;
$coll->add('foo');
$coll->add('bar');
foreach ($coll as $key => $parameter) {
echo $key, ' => ', $parameter, PHP_EOL;
}
Use iterator with a while
In order to use this class like a foreach. Methods should be called this way - Demo file
$coll->rewind();
while ($coll->valid()) {
echo $coll->key(), ' => ', $coll->current(), PHP_EOL;
$coll->next();
}
Simple solution:
1 Convert your PersistentCollection to an array first
$zooAnimalsArray = $zooAnimals->toArray();
2 Handle the array classically like you would with any PHP arrays.
Note This has the advantage of creating code that doesn't depend too much on your database (in case you wish one day to switch to a relational database), you wouldn't have to rewrite everything.
Works to me!
$collection = new ArrayCollection();
$collection->add('Laranja');
$collection->add('Uva');
$collection->add('Morango');
do {
print_r($collection->current());
} while ($collection->next());
This is my solution,
$zooAnimals->getIterator();
while ($animal = $zooAnimals->current()) {
echo $animal->getColor();
$zooAnimals->next();
}

Is it possible to class cast?

I'm am a newb with the whole class inheritance in general but I am a bit more confused with php.
I would like to have the following:
class Base
{
//some fields and shared methods...
}
class Node
{
private $children = array();
public function getChildren()
{
...
}
public function addChild($item)
{
$children[] = $item;
}
public function sum()
{
}
}
I want $item to be either another Node or a Leaf:
class Leaf extends Base
{
private $value;
public getValue()
{
}
public setValue($someFloatNumber)
{
$this->value = $someFloatNumber;
}
}
For public sum(), I want something like:
$sum = 0;
foreach ($children as $child)
{
switch(gettype($child))
{
case "Node":
$sum+= ((Node) $child)->sum();
break;
case "Leaf":
$sum+= ((Leaf) $child)->getValue();
break;
}
}
return $sum;
Not sure how to do the cast. Also would the array store the type of the added $item?
This is not proper OOP. Try this instead:
Add method sum to Base (abstract if you don't want to implement). Implement this same method sum for Leaf, which would simply return it's getValue. Then you can simply call sum on both types, thus no need for case, or to know it's type and so on:
foreach ($children as $child) {
$sum += $child->sum();
}
This is called polymorphism and it's one of the basic concepts of object oriented programming.
To also answer your question, you can hint type locally in Netbeans and Zend Studio (and probably other editors) with:
/* #var $varName Type_Name */
You're asking about hinting but then, in your code, you actually try to do a cast. Which is not necessary and not possible in that way.
Hinting example:
private function __construct(Base $node) {}
Which ensures that you can only pass an instance of Base or inheriting classes to the function.
Or if it's important for working in your IDE, you can do:
$something = $child->someMethod(); /* #var $child Base */
Which will make sure, your IDE (and other software) know that $child is of the type Base.
Instead of casting you could just use is_a like that:
if (is_a($child, 'Node') {}
else (is_a($child, 'Leaf') {}
But to be honest, it rather seems like you should refactor your code. I don't think it a good idea that a leaf is any different from a node. A leaf is just a node that doesn't have any children, which you can test anytime with $node->hasChildren() and even set and unset a leaf flag, if you need to.

Is changing an objects state/contents appropriate Iterator usage (php)

Consider an object used to store a collection of items, but that collection may vary depending on predefined contexts.
Class Container implements IteratorAggregate (
protected $contexts; // list of associated contexts, example: array(0=>1,1=>3)
protected $contents; // array
public loadContents( $contextId ) { /* populates $this->contents*/ }
public getContexts() { /* populates $this->contexts */ }
...
public function getIterator() { return new ArrayIterator($this->contents); }
public getContextIterator() { return new contextIterator($this); }
}
The iterator looks like:
Class contextIterator {
protected $container;
protected $contexts;
protected $currentContext;
public function __construct($container) {
$this->container = $container;
$this->contexts = $container->getContexts();
$this->currentContext = 0;
}
public current() {
$this->container->loadContents( $this->key() );
return $this->contexts[ $this->key() ];
}
public function key() { return $this->currentContext; }
public function next() { $this->currentContext++; }
public function rewind() { $this->currentContext = 0; }
public function valid() { return isset( $this->contexts[ $this->key() ] ); }
}
For the few cases where each context needs to be examined iteratively, I do the following:
$myContainer = new Container();
foreach( $myContainer->getContextIterator() as $key => $value ) {
$myContainer->someMethod();
}
The above is nice and compact, but it feels dirty to me since I'm never actually using $key or $value. Is using the iterator overkill? Further, should an iterator ever change the state/contents of the object it is iterating?
The above is nice and compact, but it feels dirty to me since I'm never actually using $key or $value.
You have not shown the inners of getContextIterator() so it's hard to make concrete suggestions. Generally it's possible to create iterate-able objects in PHP by implementing the OuterIterator interace or by just implementing the Iterator interface. Both interfaces are predefined and you then can use your object with next(), foreach etc.
I assume you've implemented something like OuterIterator. If you implement OuterIterator instead, you will get some speed benefit AFAIK.
Is using the iterator overkill?
No, won't say so. Iterators are very good for collections as you said you have one. I just would change it into a SPL iterator though.
Further, should an iterator ever change the state/contents of the object it is iterating?
Well actually each iterator does, at least for the internal pointer of the iteration. But I think that was not your concern, but might already lighten up.
So even for "more" changes inside the object you're iterating over, it's perfectly okay that it changes as long as it's clear what it does. Counter-Example: if you iterate over an array and it would shuffle elements each time the iteration goes one step ahead would not be useful.
But there are other cases where this is totally valid and useful. So decide on what's done, not with a general rule.

PHP5 : Applying a method from an extended class on an object from the original (parent) class

I'm trying to extend two native PHP5 classes (DOMDocument and DOMNode) to implement 2 methods (selectNodes and selectSingleNode) in order to make XPath queries easier. I thought it would be rather straighforward, but I'm stuck in a problem which I think is an OOP beginner's issue.
class nDOMDocument extends DOMDocument {
public function selectNodes($xpath){
$oxpath = new DOMXPath($this);
return $oxpath->query($xpath);
}
public function selectSingleNode($xpath){
return $this->selectNodes($xpath)->item(0);
}
}
Then I tried to do extend DOMNode to implement the same methods so I can perform an XPath query directly on a node:
class nDOMNode extends DOMNode {
public function selectNodes($xpath){
$oxpath = new DOMXPath($this->ownerDocument,$this);
return $oxpath->query($xpath);
}
public function selectSingleNode($xpath){
return $this->selectNodes($xpath)->item(0);
}
}
Now if I execute the following code (on an arbitrary XMLDocument):
$xmlDoc = new nDOMDocument;
$xmlDoc->loadXML(...some XML...);
$node1 = $xmlDoc->selectSingleNode("//item[#id=2]");
$node2 = $node1->selectSingleNode("firstname");
The third line works and returns a DOMNode object $node1. However, the fourth line doesn't work because the selectSingleNode method belongs to the nDOMNode class, not DOMNode.
So my question: is there a way at some point to "transform" the returned DOMNode object into a nDOMNode object? I feel I'm missing some essential point here and I'd greatly appreciate your help.
(Sorry, this is a restatement of my question Extending DOMDocument and DOMNode: problem with return object)
You can "tell" a DOMDocument via DOMDocument::registerNodeClass() which class it shall use instead of the default classes when instantiating an object for a certain node type.
E.g.
$doc->registerNodeClass('DOMElement', 'Foo');
each time the dom would "normally" instantiate a DOMElement it will now create an instance of Foo.
$doc = new nDOMDocument;
$doc->loadxml('<a><b><c>foo</c></b></a>');
$a = $doc->selectSingleNode('//a');
$c = $a->selectSingleNode('//c');
echo 'content: ', $c->textContent;
class nDOMElement extends DOMElement {
public function selectNodes($xpath){
$oxpath = new DOMXPath($this->ownerDocument);
return $oxpath->query($xpath);
}
public function selectSingleNode($xpath){
return $this->selectNodes($xpath)->item(0);
}
}
class nDOMDocument extends DOMDocument {
public function __construct($version=NULL, $encoding=NULL) {
parent::__construct($version, $encoding);
$this->registerNodeClass('DOMElement', 'nDOMElement');
}
public function selectNodes($xpath){
$oxpath = new DOMXPath($this);
return $oxpath->query($xpath);
}
public function selectSingleNode($xpath){
return $this->selectNodes($xpath)->item(0);
}
}
prints content: foo.
But you can't simply set registerNodeClass('DOMNode', 'MyDOMNode') and expect a DOMElement from now on to inherit from MyDOMNode instead of DOMNode. I.e. you have to register all node types you want to overwrite.
You'd need to code nDOMDocument::selectSingleNode() to return a nDOMNode object. There's no magical transform that can happen.
I say you should continue with your experiment as you will learn some good OOP lessons along the way (albeit, the hard way). Nothing wrong with that.
One of the consequences of the object-oriented approach is that inheritance is a one-way street. That is, there is no way to make the parent aware of the methods of the children. Thus while you may be able to iterate through a collection of objects, you can only reliably call the methods of the parent class. PHP does have ways to test if a method exists at runtime, and this can be useful but it can also get ugly quickly.
That means that when you extend a built-in class, you need to completely extend it so that your application is never working with instances of the built-in class--only your version. In your case, you need to expand your extended classes to override all of the parent's methods that return the parent's class type:
public myClass extends foo {
// override all methods that would normally return foo objects so that they
// return myClass objects.
}
I think you'll be better off here decorating the existing library with wrapper objects, rather than directly subclassing them. As you can see already, you can't easily append helper methods to DOMDocument and have it return your subclassed objects.
Specifically, the problem here is DOMXPath::item() does not know to return a nDOMNode.
If you tried to do something like the following, it would fail (many properties appear to be read only):
class nDOMDocument extends DOMDocument {
...
public function selectSingleNode($xPath) {
$node = $this->selectNodes($xPath)->item(0);
$myNode = new nDOMNode;
$myNode->set_name($node->get_name()); // fail?
$myNode->set_content($node->get_content());
// set namespace
// can you set the owner document? etc
return $myNode;
}
}
If you wrap the object instead you could quickly expose the existing DOMNode interface using the __get() and __call() magic methods, and include your additional functionality and elect to return your own wrapped/custom classes to achieve your original goal.
Example:
class nDOMNode {
protected $_node;
public function __construct(DOMNode $node) {
$this->_node = $node;
}
// your methods
}
class nDOMDocument {
protected $_doc;
public function __construct(DOMDocument $doc) {
$this->_doc = $doc;
}
...
public function selectNodes($xPath){
$oxPath = new DOMXPath($this->_doc->ownerDocument, $this->_doc);
return $oxPath->query($xPath);
}
public function selectSingleNode($xPath) {
return new nDOMNode($this->selectNodes($xPath)->item(0));
}
}
$doc = new nDOMDocument(new DOMDocument);
$doc->loadXML('<xml>');
$node = $doc->selectSingleNode("//item[#id=2]"); // returns nDOMNode
Hope this helps.
I think you may have to take the long way here and install pointing properties that point down, up, left and right, somewhat like the DOM does for JavaScript. These will necessarily be assigned when creating the structure.

Combining recursive iterator results: children with parents

I'm trying to iterate over a directory which contains loads of PHP files, and detect what classes are defined in each file.
Consider the following:
$php_files_and_content = new PhpFileAndContentIterator($dir);
foreach($php_files_and_content as $filepath => $sourceCode) {
// echo $filepath, $sourceCode
}
The above $php_files_and_content variable represents an iterator where the key is the filepath, and the content is the source code of the file (as if that wasn't obvious from the example).
This is then supplied into another iterator which will match all the defined classes in the source code, ala:
class DefinedClassDetector extends FilterIterator implements RecursiveIterator {
public function accept() {
return $this->hasChildren();
}
public function hasChildren() {
$classes = getDefinedClasses($this->current());
return !empty($classes);
}
public function getChildren() {
return new RecursiveArrayIterator(getDefinedClasses($this->current()));
}
}
$defined_classes = new RecursiveIteratorIterator(new DefinedClassDetector($php_files_and_content));
foreach($defined_classes as $index => $class) {
// print "$index => $class"; outputs:
// 0 => Class A
// 1 => Class B
// 0 => Class C
}
The reason the $index isn't sequential numerically is because 'Class C' was defined in the second source code file, and thus the array returned starts from index 0 again. This is preserved in the RecursiveIteratorIterator because each set of results represents a separate Iterator (and thus key/value pairs).
Anyway, what I am trying to do now is find the best way to combine these, such that when I iterate over the new iterator, I can get the key is the class name (from the $defined_classes iterator) and the value is the original file path, ala:
foreach($classes_and_paths as $filepath => $class) {
// print "$class => $filepath"; outputs
// Class A => file1.php
// Class B => file1.php
// Class C => file2.php
}
And that's where I'm stuck thus far.
At the moment, the only solution that is coming to mind is to create a new RecursiveIterator, that overrides the current() method to return the outer iterator key() (which would be the original filepath), and key() method to return the current iterator() value. But I'm not favouring this solution because:
It sounds complex (which means the code will look hideous and it won't be intuitive
The business rules are hard-coded inside the class, whereas I would like to define some generic Iterators and be able to combine them in such a way to produce the required result.
Any ideas or suggestions gratefully recieved.
I also realise there are far faster, more efficient ways of doing this, but this is also an exercise in using Iterators for myselfm and also an exercise in promoting code reuse, so any new Iterators that have to be written should be as minimal as possible and try to leverage existing functionality.
Thanks
OK, I think I finally got my head around this. Here's roughly what I did in pseudo-code:
Step 1
We need to list the directory contents, thus we can perform the following:
// Reads through the $dir directory
// traversing children, and returns all contents
$dirIterator = new RecursiveDirectoryIterator($dir);
// Flattens the recursive iterator into a single
// dimension, so it doesn't need recursive loops
$dirContents = new RecursiveIteratorIterator($dirIterator);
Step 2
We need to consider only the PHP files
class PhpFileIteratorFilter {
public function accept() {
$current = $this->current();
return $current instanceof SplFileInfo
&& $current->isFile()
&& end(explode('.', $current->getBasename())) == 'php';
}
}
// Extends FilterIterator, and accepts only .php files
$php_files = new PhpFileIteratorFilter($dirContents);
The PhpFileIteratorFilter isn't a great use of re-usable code. A better method would have been to be able to supply a file extension as part of the construction and get the filter to match on that. Although that said, I am trying to move away from construction arguments where they are not required and rely more on composition, because that makes better use of the Strategy pattern. The PhpFileIteratorFilter could simply have used the generic FileExtensionIteratorFilter and set itself up interally.
Step 3
We must now read in the file contents
class SplFileInfoReader extends FilterIterator {
public function accept() {
// make sure we use parent, this one returns the contents
$current = parent::current();
return $current instanceof SplFileInfo
&& $current->isFile()
&& $current->isReadable();
}
public function key() {
return parent::current()->getRealpath();
}
public function current() {
return file_get_contents($this->key());
}
}
// Reads the file contents of the .php files
// the key is the file path, the value is the file contents
$files_and_content = new SplFileInfoReader($php_files);
Step 4
Now we want to apply our callback to each item (the file contents) and somehow retain the results. Again, trying to make use of the strategy pattern, I've done away unneccessary contructor arguments, e.g. $preserveKeys or similar
/**
* Applies $callback to each element, and only accepts values that have children
*/
class ArrayCallbackFilterIterator extends FilterIterator implements RecursiveIterator {
public function __construct(Iterator $it, $callback) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('$callback is not callable');
}
$this->callback = $callback;
parent::__construct($it);
}
public function accept() {
return $this->hasChildren();
}
public function hasChildren() {
$this->results = call_user_func($this->callback, $this->current());
return is_array($this->results) && !empty($this->results);
}
public function getChildren() {
return new RecursiveArrayIterator($this->results);
}
}
/**
* Overrides ArrayCallbackFilterIterator to allow a fixed $key to be returned
*/
class FixedKeyArrayCallbackFilterIterator extends ArrayCallbackFilterIterator {
public function getChildren() {
return new RecursiveFixedKeyArrayIterator($this->key(), $this->results);
}
}
/**
* Extends RecursiveArrayIterator to allow a fixed $key to be set
*/
class RecursiveFixedKeyArrayIterator extends RecursiveArrayIterator {
public function __construct($key, $array) {
$this->key = $key;
parent::__construct($array);
}
public function key() {
return $this->key;
}
}
So, here I have my basic iterator which will return the results of the $callback I supplied through, but I've also extended it to create a version that will preserve the keys too, rather than using a constructor argument for it.
And thus we have this:
// Returns a RecursiveIterator
// key: file path
// value: class name
$class_filter = new FixedKeyArrayCallbackFilterIterator($files_and_content, 'getDefinedClasses');
Step 5
Now we need to format it into a suitable manner. I desire the file paths to be the value, and the keys to be the class name (i.e. to provide a direct mapping for a class to the file in which it can be found for the auto loader)
// Reduce the multi-dimensional iterator into a single dimension
$files_and_classes = new RecursiveIteratorIterator($class_filter);
// Flip it around, so the class names are keys
$classes_and_files = new FlipIterator($files_and_classes);
And voila, I can now iterate over $classes_and_files and get a list of all defined classes under $dir, along with the file they're defined in. And pretty much all of the code used to do this is re-usable in other contexts as well. I haven't hard-coded anything in the defined Iterator to achieve this task, nor have I done any extra processing outside the iterators
I think what you want to do, is more or less to reverse the keys and values returned from PhpFileAndContent. Said class returns a list of filepath => source, and you want to first reverse the mapping so it is source => filepath and then expand source for each class defined in source, so it will be class1 => filepath, class2 => filepath.
It should be easy as in your getChildren() you can simply access $this->key() to get the current file path for the source you are running getDefinedClasses() on. You can write getDefinedClasses as getDefinedClasses($path, $source) and instead of returning an indexed array of all the classes, it will return a dictionary where each value from the current indexed array is a key in the dictionary and the value is the filepath where that class was defined.
Then it will come out just as you want.
The other option is to drop your use of RecursiveArrayIterator and instead write your own iterator that is initialized (in getChildren) as
return new FilePathMapperIterator($this->key,getDefinedClasses($this->current()));
and then FilePathMapperIterator will convert the class array from getDefinedClasses to the class => filepath mapping I described by simply iterating over the array and returning the current class in key() and always returning the specified filepath in current().
I think the latter is more cool, but definitely more code so its unlikely that I would have gone that way if I can adapt getDefinedClasses() for my needs.

Categories