PHP Iterator like mysql iterator? - php

Already I extended and implemented from SPL iterator.
But if I want to use it, I should use it on a foreach.
I tried to use it in a while like this:
$news = new testClass();
while( $row = $news )
echo $row["name"];
It will create an infinite loop !
But with foreach, it works fine!
Here is top of my class:
class testClass implements \Iterator
Where is the mistake ?

Fist, bravo on using the SPL classes for this type of 'standard' problem. Too often have I seen inexperienced/sloppy developers (or even good ones that simply don't think ahead) reinvent the wheel in these types of situations.
You're missing some very important details about the implementation of the iterator interface.
see PHP:Iterator - Manual for more information, and the reference implementation from below.
First, you need to implement the, rewind, current, key, next, and valid functions. the reference implementation looks like this:
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}
function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}
function key() {
var_dump(__METHOD__);
return $this->position;
}
function next() {
var_dump(__METHOD__);
++$this->position;
}
function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
}
)
And the code for traversing that implementation looks like this:
$it = new myIterator;
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}

foreach is language construct that iterates through all elements. while executes block of code until given condition is true. To make it work you have to use your own function that checks for valid key and returns current element.

Finally I created a simple example of this:
<?php
/**
* #author Soroush Khosravi
* #copyright 2013
*/
class _Iterator
{
private $array;
public function setArray(array $data)
{
$this->array = $data;
}
public function reader()
{
if (is_null($this->array))
return false;
$elem = array_shift($this->array);
if (count ($this->array) > 0)
return $elem;
return false;
}
}
Class child extends _Iterator
{
function execute()
{
$this->setArray(array(1,2,3,4,5,6));
return $this;
}
}
$obj = new child;
$obj = $obj->execute();
while($row = $obj->reader())
echo $row;
?>

Related

PHP OOP : Fluent interface and tree graphs

I'm trying to create a fluent interface for tree objects.
Here's a simplified example of what I currently do :
<?php
class node {
private $childs = array();
private $parent;
public function __construct($parent = null) {
$this->parent = $parent;
}
public function addChild($child) {
$this->childs[] = $child;
return $this;
}
public function createChild() {
return $this->addChild(new node($this));
}
public function setFoo() {
/* do something */
return $this;
}
}
$root = new node();
$root ->addChild((new node($root))
->setFoo()
)->addChild((new node($root))
->setFoo()
);
?>
I would like to reduce the part where I create the tree.
What I want to do is something like this :
$root->createChild()->setFoo();
$root->createChild()->setFoo();
in one line. And without having to explicitly create new nodes instances (like I did in the first code with new operators).
My goal is to be able to create any tree of any order, and its nodes of any degree without having to put a semi-colon in the code.
Rather than adding a createChild function I think you should change your constructor and addChild functions to consistently establish the parent / child relationship in the data. Once you've done that the addChild function and the constructor can be used to do what you described without a createChild function. Right now your constructor allow cross-linking between different trees and branches in the trees so it's something that will probably need to change anyway.
class node {
private $childs = array();
private $parent;
public function __construct(node $parent = null) {
if(!is_null($parent)) {
$parent->addChild($this);
}
}
public function addChild(node $child) {
$this->childs[] = $child;
$child->parent = $this;
return $this;
}
public function setFoo() {
/* do something */
return $this;
}
}
With this you can chain new objects into a tree:
$tree = (new node())->addChild(new node())
->addChild((new node())->setFoo())
->addChild((new node())->addChild(new node())
->addChild(new node())
->setFoo()
);
Trying to use a createChild function is a catch-22 situation where sometimes you need the parent and sometimes you need the child. You can solve it using a return object that contains both but I think it's a situation that is better avoided. If you don't like the "(new node())" syntax, a static function might be the way to go:
public static function create(node $parent = null) {
return new node($parent);
}
Which might be a little prettier depending on your tastes:
$tree = node::create()->addChild(node::create())
->addChild(node::create()->setFoo())
->addChild(node::create()->addChild(new node())
->addChild(new node())
->setFoo()
);
You can add this methods to create as child as you can.
public function createManyChild($nbrOfChild) {
for($i = 0; $i < $nbrOfChild; $i++){
$this->addChild(new node($this));
}
return $this;
}
And use the code like this.
$root = new node();
$root->createManyChild(3)->setFoo();

Type hinting content of an array

I've got a method that accepts an array of rules as an argument.
public function setRule($name, Array $rules) { ... }
The passed in array should only contain objects that implement the IRule interface, but since I can't type hint the content of an array I would like to know if there's maybe another way of doing it?
I would highly appreciate examples with your answers.
It is not possible in the function header, but you can do instanceof checks later on.
Example:
foreach ($rules as $r) {
if ($r instanceof IRule) {
do_something();
} else {
raise_error();
}
}
Most people now will suggest to check the Array right when you are inside the method, but better try this way;
Implement an Iterator (this is a class that can be used like an array, with foreach for example), and pass this iterator to your class:
class IRuleIterator implements Iterator {
private $var = array();
public function __construct($array) {
if (is_array($array)) {
$this->var = $array;
}
}
public function add($element) {
$this->var[] = $element;
return $this;
}
public function rewind() {
reset($this->var);
return $this;
}
public function current() {
return current($this->var);
}
public function key() {
return key($this->var);
}
public function next() {
return next($this->var);
}
public function valid() {
return ($this->current() instanceof IRule);
}
}
Then your function:
public function setRule($name, IRuleIterator $rules) { /* ... */ }
You can find a full list of those "special PHP objects" which can be implemented here: http://php.net/manual/en/book.spl.php
The ArrayIterator would be even better for your purpose. There are lots of nice things in the SPL, have a look at it :)

PHP Recursive Iterator leaves

I'm trying to get better understanding of iterators in PHP. For this test I wanted to make a tree of items, and list them with different RecursiveIteratorIterator modes. ::SELF_FIRST and ::CHILD_FIRST modes are working as I expect them to. However, it doesn't when I want to list leaves. There must be something I'm missing in implementation which doesn't allow that mode to work properly as it prints out nothing. Is there something wrong with my Obj::hasChildren() method?
Here is the test class:
class Obj implements \RecursiveIterator {
public $children = array();
private $position;
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function valid()
{
return isset($this->children[$this->position]);
}
public function next()
{
$this->position++;
}
public function current()
{
return $this->children[$this->position];
}
public function rewind()
{
$this->position = 0;
}
public function key()
{
return $this->position;
}
public function hasChildren()
{
return !empty($this->children[$this->position]);
}
public function getChildren()
{
return $this->children[$this->position];
}
public function __toString()
{
return $this->name;
}
}
And here is the test:
use RecursiveIteratorIterator as RII;
$o1 = new Obj('Root');
$i1 = new Obj('Item 1');
$i12 = new Obj('Subitem 2');
$i1->children[] = new Obj('Subitem 1');
$i1->children[] = $i12;
$i12->children[] = new Obj('Subsubitem 1');
$i12->children[] = new Obj('Enough....');
$o1->children[] = $i1;
$o1->children[] = new Obj('Item 2');
$o1->children[] = new Obj('Item 3');
foreach (new RII($o1, RII::LEAVES_ONLY) as $o) {
echo "<br>" . $o;
}
What you assume is pointing in the right direction, there is a problem with the hasChildren() method you have. Compare it with the valid() and with the current() method, you then probably already see that it will always return true.
Because as long as there is a current(), hasChildren() returns true:
public function current()
{
return $this->children[$this->position];
}
and:
public function hasChildren()
{
return !empty($this->children[$this->position]);
}
Instead you want to test if the current element has children or not:
public function hasChildren()
{
return !empty($this->current()->children);
}
The slight difference that will give you your output:
Subitem 1
Subsubitem 1
Enough....
Item 2
Item 3
By always returning TRUE for hasChildren(), the RecursiveIteratorIterator is unable to detect any leaves. By concept of a tree, this is not possible but in a traversal process - as you demonstrated with your "bug" - clearly possible :)
See as well (if I may):
How does RecursiveIteratorIterator work in PHP?

ArrayAccess multidimensional (un)set?

I have a class implementing ArrayAccess and I'm trying to get it to work with a multidimensional array. exists and get work. set and unset are giving me a problem though.
class ArrayTest implements ArrayAccess {
private $_arr = array(
'test' => array(
'bar' => 1,
'baz' => 2
)
);
public function offsetExists($name) {
return isset($this->_arr[$name]);
}
public function offsetSet($name, $value) {
$this->_arr[$name] = $value;
}
public function offsetGet($name) {
return $this->_arr[$name];
}
public function offsetUnset($name) {
unset($this->_arr[$name]);
}
}
$arrTest = new ArrayTest();
isset($arrTest['test']['bar']); // Returns TRUE
echo $arrTest['test']['baz']; // Echo's 2
unset($arrTest['test']['bar']); // Error
$arrTest['test']['bar'] = 5; // Error
I know $_arr could just be made public so you could access it directly, but for my implementation it's not desired and is private.
The last 2 lines throw an error: Notice: Indirect modification of overloaded element.
I know ArrayAccess just generally doesn't work with multidimensional arrays, but is there anyway around this or any somewhat clean implementation that will allow the desired functionality?
The best idea I could come up with is using a character as a separator and testing for it in set and unset and acting accordingly. Though this gets really ugly really fast if you're dealing with a variable depth.
Does anyone know why exists and get work so as to maybe copy over the functionality?
Thanks for any help anyone can offer.
The problem could be resolved by changing public function offsetGet($name) to public function &offsetGet($name) (by adding return by reference), but it will cause Fatal Error ("Declaration of ArrayTest::offsetGet() must be compatible with that of ArrayAccess::offsetGet()").
PHP authors screwed up with this class some time ago and now they won't change it in sake of backwards compatibility:
We found out that this is not solvable
without blowing up the interface and
creating a BC or providing an
additional interface to support
references and thereby creating an
internal nightmare - actually i don't
see a way we can make that work ever.
Thus we decided to enforce the
original design and disallow
references completley.
Edit: If you still need that functionality, I'd suggest using magic method instead (__get(), __set(), etc.), because __get() returns value by reference. This will change syntax to something like this:
$arrTest->test['bar'] = 5;
Not an ideal solution of course, but I can't think of a better one.
Update: This problem was fixed in PHP 5.3.4 and ArrayAccess now works as expected:
Starting with PHP 5.3.4, the prototype checks were relaxed and it's possible for implementations of this method to return by reference. This makes indirect modifications to the overloaded array dimensions of ArrayAccess objects possible.
This issue is actually solvable, entirely functional how it should be.
From a comment on the ArrayAccess documentation here:
<?php
// sanity and error checking omitted for brevity
// note: it's a good idea to implement arrayaccess + countable + an
// iterator interface (like iteratoraggregate) as a triplet
class RecursiveArrayAccess implements ArrayAccess {
private $data = array();
// necessary for deep copies
public function __clone() {
foreach ($this->data as $key => $value) if ($value instanceof self) $this[$key] = clone $value;
}
public function __construct(array $data = array()) {
foreach ($data as $key => $value) $this[$key] = $value;
}
public function offsetSet($offset, $data) {
if (is_array($data)) $data = new self($data);
if ($offset === null) { // don't forget this!
$this->data[] = $data;
} else {
$this->data[$offset] = $data;
}
}
public function toArray() {
$data = $this->data;
foreach ($data as $key => $value) if ($value instanceof self) $data[$key] = $value->toArray();
return $data;
}
// as normal
public function offsetGet($offset) { return $this->data[$offset]; }
public function offsetExists($offset) { return isset($this->data[$offset]); }
public function offsetUnset($offset) { unset($this->data); }
}
$a = new RecursiveArrayAccess();
$a[0] = array(1=>"foo", 2=>array(3=>"bar", 4=>array(5=>"bz")));
// oops. typo
$a[0][2][4][5] = "baz";
//var_dump($a);
//var_dump($a->toArray());
// isset and unset work too
//var_dump(isset($a[0][2][4][5])); // equivalent to $a[0][2][4]->offsetExists(5)
//unset($a[0][2][4][5]); // equivalent to $a[0][2][4]->offsetUnset(5);
// if __clone wasn't implemented then cloning would produce a shallow copy, and
$b = clone $a;
$b[0][2][4][5] = "xyzzy";
// would affect $a's data too
//echo $a[0][2][4][5]; // still "baz"
?>
You can then extend that class, like so:
<?php
class Example extends RecursiveArrayAccess {
function __construct($data = array()) {
parent::__construct($data);
}
}
$ex = new Example(array('foo' => array('bar' => 'baz')));
print_r($ex);
$ex['foo']['bar'] = 'pong';
print_r($ex);
?>
This will give you an object that can be treated like an array (mostly, see note in code), which supports multi-dimensional array set/get/unset.
EDIT: See the response of Alexander Konstantinov. I was thinking of the __get magic method, which is analogous, but was actually implemented correctly. So you cannot do that without an internal implementation of your class.
EDIT2: Internal implementation:
NOTE: You might argue this is purely masturbatory, but anyway here it goes:
static zend_object_handlers object_handlers;
static zend_object_value ce_create_object(zend_class_entry *class_type TSRMLS_DC)
{
zend_object_value zov;
zend_object *zobj;
zobj = emalloc(sizeof *zobj);
zend_object_std_init(zobj, class_type TSRMLS_CC);
zend_hash_copy(zobj->properties, &(class_type->default_properties),
(copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));
zov.handle = zend_objects_store_put(zobj,
(zend_objects_store_dtor_t) zend_objects_destroy_object,
(zend_objects_free_object_storage_t) zend_objects_free_object_storage,
NULL TSRMLS_CC);
zov.handlers = &object_handlers;
return zov;
}
/* modification of zend_std_read_dimension */
zval *read_dimension(zval *object, zval *offset, int type TSRMLS_DC) /* {{{ */
{
zend_class_entry *ce = Z_OBJCE_P(object);
zval *retval;
void *dummy;
if (zend_hash_find(&ce->function_table, "offsetgetref",
sizeof("offsetgetref"), &dummy) == SUCCESS) {
if(offset == NULL) {
/* [] construct */
ALLOC_INIT_ZVAL(offset);
} else {
SEPARATE_ARG_IF_REF(offset);
}
zend_call_method_with_1_params(&object, ce, NULL, "offsetgetref",
&retval, offset);
zval_ptr_dtor(&offset);
if (!retval) {
if (!EG(exception)) {
/* ought to use php_error_docref* instead */
zend_error(E_ERROR,
"Undefined offset for object of type %s used as array",
ce->name);
}
return 0;
}
/* Undo PZVAL_LOCK() */
Z_DELREF_P(retval);
return retval;
} else {
zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name);
return 0;
}
}
ZEND_MODULE_STARTUP_D(testext)
{
zend_class_entry ce;
zend_class_entry *ce_ptr;
memcpy(&object_handlers, zend_get_std_object_handlers(),
sizeof object_handlers);
object_handlers.read_dimension = read_dimension;
INIT_CLASS_ENTRY(ce, "TestClass", NULL);
ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
ce_ptr->create_object = ce_create_object;
return SUCCESS;
}
now this script:
<?php
class ArrayTest extends TestClass implements ArrayAccess {
private $_arr = array(
'test' => array(
'bar' => 1,
'baz' => 2
)
);
public function offsetExists($name) {
return isset($this->_arr[$name]);
}
public function offsetSet($name, $value) {
$this->_arr[$name] = $value;
}
public function offsetGet($name) {
throw new RuntimeException("This method should never be called");
}
public function &offsetGetRef($name) {
return $this->_arr[$name];
}
public function offsetUnset($name) {
unset($this->_arr[$name]);
}
}
$arrTest = new ArrayTest();
echo (isset($arrTest['test']['bar'])?"test/bar is set":"error") . "\n";
echo $arrTest['test']['baz']; // Echoes 2
echo "\n";
unset($arrTest['test']['baz']);
echo (isset($arrTest['test']['baz'])?"error":"test/baz is not set") . "\n";
$arrTest['test']['baz'] = 5;
echo $arrTest['test']['baz']; // Echoes 5
gives:
test/bar is set
2
test/baz is not set
5
ORIGINAL follows -- this is incorrect:
Your offsetGet implementation must return a reference for it to work.
public function &offsetGet($name) {
return $this->_arr[$name];
}
For the internal equivalent, see here.
Since there's no analogous to get_property_ptr_ptr, you ought to return a reference (in the sense of Z_ISREF) or a proxy object (see the get handler) in write-like contexts (types BP_VAR_W, BP_VAR_RW and BP_VAR_UNSET), though it's not mandatory. If read_dimension is being called in a write-like context such as in $val =& $obj['prop'], and you return neither a reference nor an object, the engine emit a notice. Obviously, returning a reference is not enough for those operations to work correctly, it is necessary that modifying the returned zval actually has some effect. Note that assignments such as $obj['key'] = &$a are still not possible – for that one would need the dimensions to actually be storable as zvals (which may or may not be the case) and two levels of indirection.
In sum, operations that involve writing or unseting a sub-dimension of sub-property call offsetGet, not offsetSet, offsetExists or offsetUnset.
Solution:
<?php
/**
* Cube PHP Framework
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* #author Dillen / Steffen
*/
namespace Library;
/**
* The application
*
* #package Library
*/
class ArrayObject implements \ArrayAccess
{
protected $_storage = array();
// necessary for deep copies
public function __clone()
{
foreach ($this->_storage as $key => $value)
{
if ($value instanceof self)
{
$this->_storage[$key] = clone $value;
}
}
}
public function __construct(array $_storage = array())
{
foreach ($_storage as $key => $value)
{
$this->_storage[$key] = $value;
}
}
public function offsetSet($offset, $_storage)
{
if (is_array($_storage))
{
$_storage = new self($_storage);
}
if ($offset === null)
{
$this->_storage[] = $_storage;
}
else
{
$this->_storage[$offset] = $_storage;
}
}
public function toArray()
{
$_storage = $this -> _storage;
foreach ($_storage as $key => $value)
{
if ($value instanceof self)
{
$_storage[$key] = $value -> toArray();
}
}
return $_storage;
}
// as normal
public function offsetGet($offset)
{
if (isset($this->_storage[$offset]))
{
return $this->_storage[$offset];
}
if (!isset($this->_storage[$offset]))
{
$this->_storage[$offset] = new self;
}
return $this->_storage[$offset];
}
public function offsetExists($offset)
{
return isset($this->_storage[$offset]);
}
public function offsetUnset($offset)
{
unset($this->_storage);
}
}
I solved it using this:
class Colunas implements ArrayAccess {
public $cols = array();
public function offsetSet($offset, $value) {
$coluna = new Coluna($value);
if (!is_array($offset)) {
$this->cols[$offset] = $coluna;
} else {
if (!isset($this->cols[$offset[0]])) $this->cols[$offset[0]] = array();
$col = &$this->cols[$offset[0]];
for ($i = 1; $i < sizeof($offset); $i++) {
if (!isset($col[$offset[$i]])) $col[$offset[$i]] = array();
$col = &$col[$offset[$i]];
}
$col = $coluna;
}
}
public function offsetExists($offset) {
if (!is_array($offset)) {
return isset($this->cols[$offset]);
} else {
$key = array_shift($offset);
if (!isset($this->cols[$key])) return FALSE;
$col = &$this->cols[$key];
while ($key = array_shift($offset)) {
if (!isset($col[$key])) return FALSE;
$col = &$col[$key];
}
return TRUE;
}
}
public function offsetUnset($offset) {
if (!is_array($offset)) {
unset($this->cols[$offset]);
} else {
$col = &$this->cols[array_shift($offset)];
while (sizeof($offset) > 1) $col = &$col[array_shift($offset)];
unset($col[array_shift($offset)]);
}
}
public function offsetGet($offset) {
if (!is_array($offset)) {
return $this->cols[$offset];
} else {
$col = &$this->cols[array_shift($offset)];
while (sizeof($offset) > 0) $col = &$col[array_shift($offset)];
return $col;
}
}
}
So you can use it with:
$colunas = new Colunas();
$colunas['foo'] = 'Foo';
$colunas[array('bar', 'a')] = 'Bar A';
$colunas[array('bar', 'b')] = 'Bar B';
echo $colunas[array('bar', 'a')];
unset($colunas[array('bar', 'a')]);
isset($colunas[array('bar', 'a')]);
unset($colunas['bar']);
Please note that I don't check if offset is null, and if it's an array, it must be of size > 1.
Mainly according to Dakota's solution* I want to share my simplification of it.
*) Dakota's was the most understandable one to me and the outcome is quite great (- the others seem quite similar great).
So, for the ones like me, who have their difficulties in understanding what's going on here:
class DimensionalArrayAccess implements ArrayAccess {
private $_arr;
public function __construct(array $arr = array()) {
foreach ($arr as $key => $value)
{
$this[$key] = $value;
}
}
public function offsetSet($offset, $val) {
if (is_array($val)) $val = new self($val);
if ($offset === null) {
$this->_arr[] = $val;
} else {
$this->_arr[$offset] = $val;
}
}
// as normal
public function offsetGet($offset) {
return $this->_arr[$offset];
}
public function offsetExists($offset) {
return isset($this->_arr[$offset]);
}
public function offsetUnset($offset) {
unset($this->_arr);
}
}
class Example extends DimensionalArrayAccess {
function __construct() {
parent::__construct([[["foo"]]]);
}
}
$ex = new Example();
echo $ex[0][0][0];
$ex[0][0][0] = 'bar';
echo $ex[0][0][0];
I did some changes:
deleted the toArray-function, as it has no immediate purpose as long as you don't want to convert your object into an real (in Dakota's case associative) array.
deleted the clone-thing, as it has no immediate purpose as long as you don't want to clone your object.
renamed the extended class and same vars: seems more understandable to me. especially I want to emphasize, that the DimensionalArrayAccess-class gives array-like access to your object even for 3- or more-dimensional (and of course also non-associative) 'arrays' - at least as long as you instanciate it with an array counting the number of dimensions you need.
last it seems important to me to emphasize, that as you can see the Example-class itself is not dependent on a constructor variable, whereas the DimensionalArrayAccess-class is (as it calls itself in the offsetSet-function recursively.
As I introduced, this post is rather for the not so advanced ones like me.
EDIT: this only works for cells which are set during instantiation, whereas it is not possible to add new cells afterwards.
class Test implements \ArrayAccess {
private
$input = [];
public function __construct () {
$this->input = ['foo' => ['bar' => 'qux']];
}
public function offsetExists ($offset) {}
public function offsetGet ($offset) {}
public function offsetSet ($offset, $value) {}
public function offsetUnset ($offset) {}
}
runkit_method_redefine ('Test', 'offsetGet', '&$offset', 'return $this->input[$offset];');
$ui = new Test;
var_dump($ui['foo']['bar']); // string(3) "qux"

Custom foreach results for dynamic proxy class - magic methods?

I need to serialize a proxy class. The class uses __set and __get to store values in an array. I want the serialization to look like it is just a flat object. In other words, my class looks like:
class Proxy
{
public $data = array();
public function __get($name)
{
return $data[$name]
}
}
and I want a foreach loop to return all the keys and values in $data, when I say:
foreach($myProxy as $key)
Is this possible?
class Proxy implements IteratorAggregate
{
public $data = array();
public function __get($name)
{
return $data[$name];
}
public function getIterator()
{
$o = new ArrayObject($this->data);
return $o->getIterator();
}
}
$p = new Proxy();
$p->data = array(2, 4, 6);
foreach ($p as $v)
{
echo $v;
}
Output is: 246.
See Object Iteration in the PHP docs for more details.
You want to implement the SPL iterator interface
Something like this:
class Proxy implements Iterator
{
public $data = array();
public function __get($name)
{
return $data[$name]
}
function rewind()
{
reset($this->data);
$this->valid = true;
}
function current()
{
return current($this->data)
}
function key()
{
return key($this->data)
}
function next() {
next($this->data);
}
function valid()
{
return key($this->data) !== null;
}
}

Categories