Arrayaccess, Iterator and current() - php

I have class which implements Countable, ArrayAccess, Iterator and Serializable.
I have a public varibale $data, in array form. And my iteration implementions:
public function rewind() { return reset($this->data); }
public function current() { return current($this->data); }
public function key() { return key($this->data); }
public function next() { return next($this->data); }
public function valid() { return isset($this->data[$this->key()]); }
Well everything works with foreach loop, but if i manually call current($arrayObject), it returns whole $data array, not teh current in it. I can do current($arrayObject->data), but like to keep native array functionality as where i can.
This is php behavior right? (not mine code bug) And is there any workaround this, without custom function(fingers crossed)?
[EDIT] Simplified version of full class(working):
$arrayObject = mysqli_fetch_object($this->result_id, "simpleMysqliResult ", array(array(
"fields" => array( "field1", "field2", "field3" )
)));
class simpleMysqliResult implements Countable, ArrayAccess, Iterator, Serializable {
public $data = array();
public function __construct($input) {
extract($input);
foreach ($fields as $field) {
$this->data[$field] = $this->{$field};
unset($this->{$field});
}
}
public function &toArray() { return $this->data; }
public function offsetGet($index) { return $this->data[$index]; }
public function offsetSet($index, $value) { $this->data[$index] = $value; }
public function offsetUnset($index) { unset($this->data[$index]); }
public function offsetExists($index) { return $this->offsetGet($index) !== null; }
public function count() { return count($this->data); }
public function rewind() { return reset($this->data); }
public function current() { return current($this->data); }
public function key() { return key($this->data); }
public function next() { return next($this->data); }
public function valid() { return isset($this->data[$this->key()]); }
public function serialize() { return serialize($this->data); }
public function unserialize($str) { return $this->data = unserialize($str); }
public function __call($func, $argv) {
if (!is_callable($func) || substr($func, 0, 6) !== 'array_')
{
throw new BadMethodCallException(__CLASS__.'->'.$func);
}
return call_user_func_array($func, array_merge(array($this->data), $argv));
}
}

Try placing your data in private mode. I suspect you have an external procedure playing around with it and making changes to it. I copied your class and altered your constructor a bit since i didn't have the same input as you and i get no strange behavior. Ultimately, you might want to look into why you extract($input) also, just use $input['fields'] or $input->fields as you see fit, it'll still work.
Here are my corrections:
private $data = array();
public function __construct($input) {
foreach($input['fields'] as $field) {
$this->data[$field] = $input[$field];
}
}
Here are my tests
$data = array('fields' => array('id', 'name'), 'id' => 1, 'name' => 'Franco');
$smr = new simpleMysqliResult($data);
var_dump($smr->current());
var_dump($smr->current());
var_dump($smr->current());
var_dump($smr->current());
var_dump($smr->next());
var_dump($smr->current());
var_dump($smr->current());
And my output is ok
int(1) int(1) int(1) int(1) string(6) "Franco" string(6) "Franco" string(6) "Franco"
So like i said, i think your problem mainly lies in the fact that your $data is public and something else is playing with it.
OR
It lies in your constructor that i had to fix to make it work on my side.
Good luck

Related

Passing array to a function inside class PHP

I have a question on how to pass an array to a function in PHP. I have a class called "MyClass" and inside it has functions called rankVal($arr1, $arr2) and processResponse($data, $db, $id, $lat, $lng).
processResponse() will call rankVal() and here is my problem is.
class MyClass{
private function cmpVal($a, $b){
/*do sorting stuff*/
}
function rankVal($arr1, $arr2){
$arrIdx=[];
foreach ($arr1 as $key => $value) {
$n=array_search($value, $arr2);
$newPos = ($key+$n)/2;
$arrNewIdx [$n]=round($newPos,0, PHP_ROUND_HALF_DOWN);
}
}
function processResponse($data, $db, $id, $lat, $lng){
//Do some stuffs here...
$someArr1 = [];
foreach($results as $key => $value){
$newVal = new stdClass();
$newVal->key1 = $value->key1;
$newVal->key2 = $value->key2;
$newVal->key3 = $value->key3;
$newVal->key4 = $value->key4;
$newVal->key5 = $value->key5;
$someArr1 []= $newVal;
}
$someArr2 = $someArr1;
usort($someArr2, array($this, "cmpVal"));
$rankedVal = $this->rankVal($someArr1, $someArr2);
}
}
When I called the processResponse() function I got this error:
array_search() expects parameter 2 to be array, object given
So, I var_dump($arr2) in rankVal(), and the output clearly says that $arr2 is an array. Here's the sample output of the var_dump($arr2):
array(30) {
[0]=>
object(stdClass)#385 (7) {
["key1"]=>
string(24) "something"
["key2"]=>
string(20) "something"
["key3"]=>
string(41) "something"
["key4"]=>
float(1.23455)
["key5"]=>
float(1.19128371983198)
}
What did I do wrong? I tried to pass the array by reference by adding "&" in rankVal(&$arr1, &$arr2), but the error is still there.
To add to the other answer here (which now seems to have gone), if you want your class to behave as an array where appropriate, you need to make it iterable.
All you need to do is implement Iterable. This means that you need to create the necessary methods, but this is all you need in order to have your class behave that way.
Its useful for classes which are designed to hold an array of data, but you want to encapsulate additional tools along with that data.
Heres an example:
class Row implements \Iterator {
protected $data;
protected $position = 0;
public function __construct( array $data = [ ]) {
$this->data = $data;
}
public function addData( $value ) {
$this->data[] = $value;
}
public function replaceData( $index, $value ) {
$this->data[ $index ] = $value;
}
public function getData() {
return $this->data;
}
public function setData( array $data ) {
$this->data = $data;
return $this;
}
/** Required by Iterator */
public function current() {
return $this->data[ $this->position ];
}
/** Required by Iterator */
public function next() {
++$this->position;
}
public function __toArray() {
return $this->data;
}
/** Required by Iterator */
public function key() {
return $this->position;
}
/** Required by Iterator */
public function valid( $index = null ) {
return isset( $this->data[ $index ? $index : $this->position ] );
}
/** Required by Iterator */
public function rewind() {
$this->position = 0;
}
public function count() {
return count( $this->data );
}
}
Once you have this, it can be used anywhere you can use an array.
So after checking my code again, I finally found the problem that causing this weird bug which was not supposed to be there.
The culprit is in the rankVal() function where I called usort() which used rankVal() as the callback function for the sorting process. Then, I changed this callback function to the right one, and voila problem's solved.
Thanks for everyone who had answered and given me some suggestions on how to fix it.

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 array_key_exists() and SPL ArrayAccess interface: not compatible?

I wrote a simple collection class so that I can store my arrays in objects:
class App_Collection implements ArrayAccess, IteratorAggregate, Countable
{
public $data = array();
public function count()
{
return count($this->data);
}
public function offsetExists($offset)
{
return (isset($this->data[$offset]));
}
public function offsetGet($offset)
{
if ($this->offsetExists($offset))
{
return $this->data[$offset];
}
return false;
}
public function offsetSet($offset, $value)
{
if ($offset)
{
$this->data[$offset] = $value;
}
else
{
$this->data[] = $value;
}
}
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}
Problem: when calling array_key_exists() on this object, it always returns "false" as it seems this function is not being handled by the SPL. Is there any way around this?
Proof of concept:
$collection = new App_Collection();
$collection['foo'] = 'bar';
// EXPECTED return value: bool(true)
// REAL return value: bool(false)
var_dump(array_key_exists('foo', $collection));
This is a known issue which might be addressed in PHP6. Until then, use isset() or ArrayAccess::offsetExists().

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

PHP - Hashed array, insert at Index?

i wrote an array wrapper class PersonArray which can contain objects of a certain type (Person). Every person has a unique getHash() function which returns the ID + Name as a unique identifier. This allows for speedy retrieval of the Person from the PersonArray. The PersonArray actually holds two internal Arrays. One for the storage of Person objects ($items), and one for the storage of the Hash values ($itemsHash).
I want to create a insertAt(index, Person) function which puts the Person object at the [index] position in the $items array. Is there a way to insertAt a certain position in an array? If so how can I also update the $itemsHash of the PersonArray?
class Person {
function getHash() {
return $this->id . $this->name;
}
}
class PersonArray implements Iterator {
public $items = array();
public $itemsHash = array();
public function Find($pKey) {
if($this->ContainsKey($pKey)) {
return $this->Item($this->internalRegisteredHashList[$pKey]);
}
}
public function Add($object) {
if($object->getHash()) {
$this->internalRegisteredHashList[$object->getHash()] = $this->Count();
array_push($this->items, $object);
}
}
public function getItems() {
return $this->items;
}
function ContainsKey($pKey) {}
function Count() {}
function Item($pKey) {}
//Iteration implementation
public function rewind() {}
public function current() {}
public function key() {}
public function next() {}
public function valid() {}
}
You may find it is faster and easier to use PHP's associative arrays rather than re-implementing them.
As an aside you can also implement the simpler IteratorAggregate if you are actually just iterating over an array.
e.g.
class PersonArray implements IteratorAggregate {
public $items = array();
public function getItems() {
return $this->items;
}
public function Add($object) {
if($object->getHash()) {
$this->items[$object->getHash()] = $object;
}
}
public function Find($pKey) {
if(isset($this->items[$pKey])) {
return $this->items[$pKey];
}
}
public function insertAt($index, $person) {
$tmp = array_slice($this->items, 0, $index);
$tmp[$person->getHash()] = $person;
$tmp = array_merge($tmp, array_slice($this->items, $index));
$this->items = $tmp;
}
//IteratorAggregate implementation
public function getIterator() {
return new ArrayIterator($this->items);
}
}

Categories