Type hinting content of an array - php

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 :)

Related

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?

Config Class using ArrayObject

I've been looking a way to implement ArrayObject Class to store application configs and i found this implementation in the php manual ( one of the comments )
<?php
use \ArrayObject;
/**
* Singleton With Configuration Info
*/
class Config extends ArrayObject
{
/**
*
* Overwrites the ArrayObject Constructor for
* Iteration throught the "Array". When the item
* is an array, it creates another static() instead of an array
*/
public function __construct(Array $array)
{
$this->setFlags(ArrayObject::ARRAY_AS_PROPS);
foreach($array as $key => $value)
{
if(is_array($value))
{
$value = new static($value);
}
$this->offsetSet($key, $value);
}
}
public function __get($key)
{
return $this->offsetGet($key);
}
public function __set($key, $value)
{
$this->offsetSet($key, $value);
}
/**
* Returns Array when printed (like "echo array();")
* Instead of an Error
*/
public function __ToString()
{
return 'Array';
}
}
Usage:
$config = new Config\Config($settings);
$config->uri = 'localhost'; // works
$config->url->uri = 'localhost'; // doesn't work
print_r($config);
I've tried adding to this class the __get and __set it works fine for a simple array but when it comes to multidimensional arrays well ... things are different. I'm getting an error saying that the index is not defined.
Can someone help me out please i tried everything i knew googled a lot about it and i didn't find the solution.
I have solved the problem with this class. Later on i will post here a fully working example maybe someone will need it. Thank you everyone for taking time and reading this thread
Update:
So what do you think guys? What improvements... changes should i make ?
public function __construct(Array $properties)
{
$this->populateArray($properties);
}
private function populateArray(Array $array)
{
if(is_array($array))
{
foreach($array as $key => $value)
{
$this->createProperty($key, $value);
}
}
unset($this->properties);
}
private function createProperty($key, $value)
{
is_array($value) ?
$this->offsetSet($key, $this->createComplexProperty($value))
: $this->offsetSet($key, $value);
}
private function createComplexProperty(Array $array)
{
return new Config($array);
}
private function createPropertyIfNone($key)
{
if($this->offsetExists($key))
return;
$this->createProperty($name, array());
}
public function __get($key)
{
$this->createPropertyIfNone($key);
return $this->offsetGet($key);
}
public function __set($key, $value)
{
$this->createProperty($key, $value);
}
public function __ToString()
{
return (string) $value;
}
}
If you want to assume a non-existing key is an array, then this should work.
public function __get($key)
{
if(!$this->offsetExists($key))
{
$this->offsetSet($key,new Array());
}
return &$this->offsetGet($key);
}
Usage:
$config = new Config\Config($settings);
$config->url['uri'] = 'localhost';
print_r($config);
EDIT:
Not sure if you have to return a reference or not for this to work.
return &$this->offsetGet($key);
or this
return $this->offsetGet($key);

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