I have an object that implements Iterator and holds 2 arrays: "entries" and "pages". Whenever I loop through this object, I want to modify the entries array but I get the error An iterator cannot be used with foreach by reference which I see started in PHP 5.2.
My question is, how can I use the Iterator class to change the value of the looped object while using foreach on it?
My code:
//$flavors = instance of this class:
class PaginatedResultSet implements \Iterator {
private $position = 0;
public $entries = array();
public $pages = array();
//...Iterator methods...
}
//looping
//throws error here
foreach ($flavors as &$flavor) {
$flavor = $flavor->stdClassForApi();
}
The reason for this is that sometimes $flavors will not be an instance of my class and instead will just be a simple array. I want to be able to modify this array easily regardless of the type it is.
I just tried creating an iterator which used:
public function ¤t() {
$element = &$this->array[$this->position];
return $element;
}
But that still did not work.
The best I can recommend is that you implement \ArrayAccess, which will allow you to do this:
foreach ($flavors as $key => $flavor) {
$flavors[$key] = $flavor->stdClassForApi();
}
Using generators:
Updating based on Marks comment on generators, the following will allow you to iterate over the results without needing to implement \Iterator or \ArrayAccess.
class PaginatedResultSet {
public $entries = array();
public function &iterate()
{
foreach ($this->entries as &$v) {
yield $v;
}
}
}
$flavors = new PaginatedResultSet(/* args */);
foreach ($flavors->iterate() as &$flavor) {
$flavor = $flavor->stdClassForApi();
}
This is a feature available in PHP 5.5.
Expanding upon Flosculus' solution, if you don't want to reference the key each time you use the iterated variable, you can assign a reference to it to a new variable in the first line of your foreach.
foreach ($flavors as $key => $f) {
$flavor = &$flavors[$key];
$flavor = $flavor->stdClassForApi();
}
This is functionally identical to using the key on the base object, but helps keep code tidy, and variable names short... If you're into that kind of thing.
If you implemented the iterator functions in your calss, I would suggest to add another method to the class "setCurrent()":
//$flavors = instance of this class:
class PaginatedResultSet implements \Iterator {
private $position = 0;
public $entries = array();
public $pages = array();
/* --- Iterator methods block --- */
private $current;
public function setCurrent($value){
$this->current = $value;
}
public function current(){
return $this->current;
}
//...Other Iterator methods...
}
Then you can just use this function inside the foreach loop:
foreach ($flavors as $flavor) {
$newFlavor = makeNewFlavorFromOldOne($flavor)
$flavors -> setCurrent($newFlavor);
}
If you need this function in other classes, you can also define a new iterator and extend the Iterator interface to contain setCurrent()
Related
I would like to get all the instances of an object of a certain class.
For example:
class Foo {
}
$a = new Foo();
$b = new Foo();
$instances = get_instances_of_class('Foo');
$instances should be either array($a, $b) or array($b, $a) (order does not matter).
A plus is if the function would return instances which have a superclass of the requested class, though this isn't necessary.
One method I can think of is using a static class member variable which holds an array of instances. In the class's constructor and destructor, I would add or remove $this from the array. This is rather troublesome and error-prone if I have to do it on many classes.
If you derive all your objects from a TrackableObject class, this class could be set up to handle such things (just be sure you call parent::__construct() and parent::__destruct() when overloading those in subclasses.
class TrackableObject
{
protected static $_instances = array();
public function __construct()
{
self::$_instances[] = $this;
}
public function __destruct()
{
unset(self::$_instances[array_search($this, self::$_instances, true)]);
}
/**
* #param $includeSubclasses Optionally include subclasses in returned set
* #returns array array of objects
*/
public static function getInstances($includeSubclasses = false)
{
$return = array();
foreach(self::$_instances as $instance) {
if ($instance instanceof get_class($this)) {
if ($includeSubclasses || (get_class($instance) === get_class($this)) {
$return[] = $instance;
}
}
}
return $return;
}
}
The major issue with this is that no object would be automatically picked up by garbage collection (as a reference to it still exists within TrackableObject::$_instances), so __destruct() would need to be called manually to destroy said object. (Circular Reference Garbage Collection was added in PHP 5.3 and may present additional garbage collection opportunities)
Here's a possible solution:
function get_instances_of_class($class) {
$instances = array();
foreach ($GLOBALS as $value) {
if (is_a($value, $class) || is_subclass_of($value, $class)) {
array_push($instances, $value);
}
}
return $instances;
}
Edit: Updated the code to check if the $class is a superclass.
Edit 2: Made a slightly messier recursive function that checks each object's variables instead of just the top-level objects:
function get_instances_of_class($class, $vars=null) {
if ($vars == null) {
$vars = $GLOBALS;
}
$instances = array();
foreach ($vars as $value) {
if (is_a($value, $class)) {
array_push($instances, $value);
}
$object_vars = get_object_vars($value);
if ($object_vars) {
$instances = array_merge($instances, get_instances_of_class($class, $object_vars));
}
}
return $instances;
}
I'm not sure if it can go into infinite recursion with certain objects, so beware...
I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound).
I would suggest having a separate object where you register objects with (An observer pattern). PHP has built-in support for this, through spl; See: SplObserver and SplSubject.
As far as I know, the PHP runtime does not expose the underlying object space, so it would not be possible to query it for instances of an object.
I have a long running PHP daemon with a collection class that extends ArrayIterator. This holds a set of custom Column objects, typically less than 1000. Running it through the xdebug profiler I found my find method consuming about 35% of the cycles.
How can I internally iterate over the items in an optimized way?
class ColumnCollection extends \ArrayIterator
{
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
$this->rewind();
while ($this->valid()) {
/** #var Column $column */
$column = $this->current();
if (strtolower($column->name) === $name) {
$return = $column;
break;
}
$this->next();
}
$this->rewind();
return $return;
}
}
Your find() method apparently just returns the first Column object with the queried $name. In that case, it might make sense to index the Array by name, e.g. store the object by it's name as the key. Then your lookup becomes a O(1) call.
ArrayIterator implements ArrayAccess. This means you can add new items to your Collection like this:
$collection = new ColumnCollection;
$collection[$someCollectionObject->name] = $someCollectionObject;
and also retrieve them via the square bracket notation:
$someCollectionObject = $collection["foo"];
If you don't want to change your client code, you can simply override offsetSet in your ColumnCollection:
public function offsetSet($index, $newValue)
{
if ($index === null && $newValue instanceof Column) {
return parent::offsetSet($newValue->name, $newValue);
}
return parent::offsetSet($index, $newValue);
}
This way, doing $collection[] = $column would automatically add the $column by name. See http://codepad.org/egAchYpk for a demo.
If you use the append() method to add new elements, you just change it to:
public function append($newValue)
{
parent::offsetSet($newValue->name, $newValue);
}
However, ArrayAccess is slower than native array access, so you might want to change your ColumnCollection to something like this:
class ColumnCollection implements IteratorAggregate
{
private $columns = []; // or SplObjectStorage
public function add(Column $column) {
$this->columns[$column->name] = $column;
}
public function find($name) {
return isset($this->data[$name]) ? $this->data[$name] : null;
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}
I replaced the iterator method calls with a loop on a copy of the array. I presume this gives direct access to the internal storage since PHP implements copy-on-write. The native foreach is much faster than calling rewind(), valid(), current(), and next(). Also pre-calculating the strtolower on the Column object helped. This got performance down from 35% of cycles to 0.14%.
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
/** #var Column $column */
foreach ($this->getArrayCopy() as $column) {
if ($column->nameLower === $name) {
$return = $column;
break;
}
}
return $return;
}
Also experimenting with #Gordon's suggestion of using an array keyed on name instead of using the internal storage. The above is working well for a simple drop-in replacement.
I'm having a hard time understanding PHP's iterator_to_array function.
I tried reading the manual but that didn't help.
What is it? How can I use it? What are the appropriate use cases?
In a nutshell, iterator_to_array() function takes an iterator of type Traversable and convert it to an associative/non-associative array, depending upon the argument provided. From the documentation,
array iterator_to_array ( Traversable $iterator [, bool $use_keys = true ] )
The function takes the following two arguments,
The first argument is of type Traversal, which is an interface. Both IteratorAggregate and Iterator class extends this interface. You can implement these two classes in your custom class, like this:
class myIterator implements IteratorAggregate {
private $array = array('key1'=>'value1', 'value2', 'value3', 'value4');
public function getIterator(){
return new ArrayIterator($this->array);
}
}
$obj = new myIterator;
$array = iterator_to_array($obj->getIterator(), true);
var_dump($array);
Or,
class myIterator implements Iterator {
private $key;
private $array = array('key1'=>'value1', 'value2', 'value3', 'value4');
public function __construct(){
$this->key = key($this->array);
}
public function rewind(){
reset($this->array);
$this->key = key($this->array);
}
public function current(){
return $this->array[$this->key];
}
public function key(){
return $this->key;
}
public function next(){
next($this->array);
$this->key = key($this->array);
}
public function valid(){
return isset($this->array[$this->key]);
}
}
$obj = new myIterator;
$array = iterator_to_array($obj, true);
var_dump($array);
The most important point to note here is that argument 1 passed to iterator_to_array() function must implement interface Traversable, so you cannot directly pass an array or object of any other type to this function. See the following example,
$array = array('key1'=>'value1', 'value2', 'value3', 'value4');
$array = iterator_to_array($array, true); // wrong
The second argument is a boolean value, to indicate whether to use the iterator element keys as index or not. See Example #1 here.
Iterators are useful because they allow you to use a custom defined order of data within a foreach loop. Let's take this (slightly pared down) example from the PHP manual:
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
function rewind() {
$this->position = 0;
}
function current() {
return $this->array[$this->position];
}
function key() {
return $this->position;
}
function next() {
++$this->position;
}
function valid() {
return isset($this->array[$this->position]);
}
}
$it = new myIterator;
To iterate over it, we can use a foreach loop just like an array:
foreach($it as $ele){
echo $ele;
}
Unlike an array, however, you cannot access a single element without iterating to it first. Attempting to do so will give you a fatal error:
/*
* Fatal error: Uncaught Error: Cannot use object of type myIterator as array
* in /home/hpierce/sandbox.php
*/
echo $it[1];
//Iterating to the second element.
$it->next();
// "secondelement"
echo $it->current();
To access a single element, you could use iterator_to_array() to cast the iterator as an array and then access the element without the extra step:
$array = iterator_to_array($it);
// "secondelement"
echo $array[1];
If you know ahead of time that you will need to access a single element within an iterator, you should consider using ArrayIterator instead.
I'm making a class which needs to provide me with all the data from the MySQL-table "content".
I want to have my data returned as an object.
So far I managed to get one object returned, but i'd like to get an Object Collection with all my rows from the database returned.
<?
class ContentItem {
public $id;
public $title;
public $subtitle;
public $conent;
public $intro_length;
public $active;
public $start_date;
public $end_date;
public $views;
static function getContentItems() {
$query = "SELECT * FROM content";
$result = mysql_query($query)or die(mysql_error());
$item = new ContentItem();
while ($data = mysql_fetch_object($result)) {
$item = $data;
}
return $item;
}
}
For collections you need to create an object which implements Iterator interface. You can fill it with arrays, objects or whatever you want. Iterator makes sure you can use this collection in foreach loops after.
Furthermore, there's a mistake in your code. You are overwriting $item over and over again. You should create an array (or object with implemented Iterator, as I mentioned) which will be filled each cycle of while (as tandu wrote already).
Your loop keeps overwriting data with item, and new ContentItem() is overwritten immediately. If by "object collection" you mean "array," it's quite simple:
$items = array();
while ($data = mysql_fetch_object($result)) {
$items[] = $data;
}
return $items;
If you want to return your own custom object collection, then define the collection class and add $data to its collection each time (probably stored in an array as well).
The simple solution would be an array. I also assume you want a ContentItem made from each set of $data
$items = array();
while ($data = mysql_fetch_object($result)) {
$items[] = new ContentItem($data);
}
return $items;
If you later want to work with the items, you can use foreach
foreach ($items as $item) {
// do something with $item
}
I would like to get all the instances of an object of a certain class.
For example:
class Foo {
}
$a = new Foo();
$b = new Foo();
$instances = get_instances_of_class('Foo');
$instances should be either array($a, $b) or array($b, $a) (order does not matter).
A plus is if the function would return instances which have a superclass of the requested class, though this isn't necessary.
One method I can think of is using a static class member variable which holds an array of instances. In the class's constructor and destructor, I would add or remove $this from the array. This is rather troublesome and error-prone if I have to do it on many classes.
If you derive all your objects from a TrackableObject class, this class could be set up to handle such things (just be sure you call parent::__construct() and parent::__destruct() when overloading those in subclasses.
class TrackableObject
{
protected static $_instances = array();
public function __construct()
{
self::$_instances[] = $this;
}
public function __destruct()
{
unset(self::$_instances[array_search($this, self::$_instances, true)]);
}
/**
* #param $includeSubclasses Optionally include subclasses in returned set
* #returns array array of objects
*/
public static function getInstances($includeSubclasses = false)
{
$return = array();
foreach(self::$_instances as $instance) {
if ($instance instanceof get_class($this)) {
if ($includeSubclasses || (get_class($instance) === get_class($this)) {
$return[] = $instance;
}
}
}
return $return;
}
}
The major issue with this is that no object would be automatically picked up by garbage collection (as a reference to it still exists within TrackableObject::$_instances), so __destruct() would need to be called manually to destroy said object. (Circular Reference Garbage Collection was added in PHP 5.3 and may present additional garbage collection opportunities)
Here's a possible solution:
function get_instances_of_class($class) {
$instances = array();
foreach ($GLOBALS as $value) {
if (is_a($value, $class) || is_subclass_of($value, $class)) {
array_push($instances, $value);
}
}
return $instances;
}
Edit: Updated the code to check if the $class is a superclass.
Edit 2: Made a slightly messier recursive function that checks each object's variables instead of just the top-level objects:
function get_instances_of_class($class, $vars=null) {
if ($vars == null) {
$vars = $GLOBALS;
}
$instances = array();
foreach ($vars as $value) {
if (is_a($value, $class)) {
array_push($instances, $value);
}
$object_vars = get_object_vars($value);
if ($object_vars) {
$instances = array_merge($instances, get_instances_of_class($class, $object_vars));
}
}
return $instances;
}
I'm not sure if it can go into infinite recursion with certain objects, so beware...
I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound).
I would suggest having a separate object where you register objects with (An observer pattern). PHP has built-in support for this, through spl; See: SplObserver and SplSubject.
As far as I know, the PHP runtime does not expose the underlying object space, so it would not be possible to query it for instances of an object.