PHP array_key_exists() and SPL ArrayAccess interface: not compatible? - php

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().

Related

Creating a priority queue class in PHP

I am attempting to create a priority queue class with array object in PHP. I know there is the SplPriorityQueue in PHP, but I am trying to practice object oriented programming here. Priority Queues have data and priority level, so I have a rough MyQueue class that implements these attributes. I am not sure if I am going in the right direction here. I have not worked with arrayObject's in PHP before.
public class MyQueue{
private string data;
private int priority;
myQueue = arrayObject(array(data => priority));
}
Priority queue class might look like this:
class MyQueue implements Iterator, Countable {
private $data;
public function __construct() {
$this->data = array();
}
function compare($priority1, $priority2) {}
function count() {
return count($this->data);
}
function extract() {
$result = $this->current();
$this->next();
return $result;
}
function current() {
return current($this->data). ' - ' .$this->key();
}
function key() {
return key($this->data);
}
function next() {
return next($this->data);
}
function insert($name, $priority) {
$this->data[$name] = $priority;
asort($this->data);
return $this;
}
function isEmpty() {
return empty($this->data);
}
function recoverFromCorruption() {}
function rewind() {}
function valid() {
return (null === key($this->data)) ? false : true;
}
}
Usage:
$items = new MyQueue();
$items ->insert('Charles', 8)
->insert('James', 1)
->insert('Michael', 4)
->insert('John', 2)
->insert('David', 6)
->insert('William', 5)
->insert('Robert', 3)
->insert('Richard', 7);
foreach($items as $item) {
echo $item,'<br>';
}

PHP __set variable while __get passes by reference

I think its quite a simple question but not sure.
I have a class:
<?PHP
class PropertyTest {
private $data = array();
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function __isset($name) {
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
public function __unset($name) {
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
public function getHidden() {
return $this->hidden;
}
}
?>
Not sure why but the 'code' block is annoying as hell, anyway.
Just the basic magic get set really. But when I change the __get to pass by reference I cant do this anymore:
$object->$variableName = $variableValue;
I'm not sure why although I assume because it checks if it exists but since it has to return something by reference it will fail to do so if it doesn't exists to begin with. The set function wont be called probably and even if I return a fake value it would never call the set function cause it 'already exists/has a value'.
Am I understanding this correctly? If so, Is there a work around? If not how does it work and is there a workaround?
Unless I'm missing something it's working fine for me
<?php
class PropertyTest
{
private $data = array();
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function &__get($name)
{
if(array_key_exists($name, $this->data))
return $this->data[$name];
return null;
}
public function __isset($name)
{
return isset($this->data[$name]);
}
public function __unset($name)
{
unset($this->data[$name]);
}
public function getHidden()
{
return $this->hidden;
}
}
$oPropTest = new PropertyTest();
$sField = 'my-field';
$oPropTest->$sField = 5;
var_dump($oPropTest);
Outputs:
bash-3.2$ php test-get-set.php
object(PropertyTest)#1 (1) {
["data":"PropertyTest":private]=>
array(1) {
["my-field"]=>
int(5)
}
}
One tweak I'd suggest for your __get implementation is to leverage the __isset method rather than re-implement the check (doing it 2 different ways as you are)
public function __get($name)
{
if($this->__isset($name))
return $this->data[$name];
return null;
}
Regarding the idea of return-by-reference from __get; it will work, but be useless on anything but public attributes, since private and protected attributes won't be settable directly through a reference outside the class scope.
$oPropTest = new PropertyTest();
$sField = 'my-field';
$oPropTest->$sField = 5; // sets $oPropTest->my-field to 5 (effectively)
$iRef = $oPropTest->$sField; // $iRef is a reference to $oPropTest->my-field
$iRef = 6; // this will not set $oPropTest->my-field since it's private

Arrayaccess, Iterator and current()

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

Why does a class that implements ArrayAccess, Iterator, and Countable not work with array_filter()?

I have the following class:
<?php
/*
* Abstract class that, when subclassed, allows an instance to be used as an array.
* Interfaces `Countable` and `Iterator` are necessary for functionality such as `foreach`
*/
abstract class AArray implements ArrayAccess, Iterator, Countable
{
private $container = array();
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset)
{
return isset($this->container[$offset]);
}
public function offsetUnset($offset)
{
unset($this->container[$offset]);
}
public function offsetGet($offset)
{
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
public function rewind() {
reset($this->container);
}
public function current() {
return current($this->container);
}
public function key() {
return key($this->container);
}
public function next() {
return next($this->container);
}
public function valid() {
return $this->current() !== false;
}
public function count() {
return count($this->container);
}
}
?>
Then, I have another class that sub-classes AArray:
<?php
require_once 'AArray.inc';
class GalleryCollection extends AArray { }
?>
When I fill a GalleryCollection instance with data and then try to use it in array_filter(), in the first argument, I get the following error:
Warning: array_filter() [function.array-filter]: The first argument should be an array in
Because array_filter only works with arrays.
Look at other options, like FilterIterator, or create an array from your object first.

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