Why are objects considered not iterable although they actually are? - php

PHP 7.1 added the new is_iterable function to check whether a variable can be iterated. But although you may iterate over an objects properties is_iterable returns false for them.
This seems to be intentional, as it is documented and I couldn't find any PHP bugs regarding the issue but I wonder why this was done?
<?php
$a = new stdClass();
$a->foo = 'bar';
var_dump(is_iterable($a));
foreach ($a as $key => $value) {
var_dump($key, $value);
}
Outputs
boolean false
string 'foo' (length=3)
string 'bar' (length=3)
But I would expect it to output
boolean true
string 'foo' (length=3)
string 'bar' (length=3)

Its a question of definition, and "why is it done" is a tricky question, probably better for the PHP devs.
But some information: You can foreach over an object, and it will show the visible properies according to the manual
PHP 5 provides a way for objects to be defined so it is possible to iterate through a list of items, with, for example a foreach statement. By default, all visible properties will be used for the iteration.
The is_iterable is checking for specific things, see the manual
Verify that the contents of a variable is accepted by the iterable
pseudo-type, i.e. that it is an array or an object implementing Traversable
An object does not implement Traversable if you don't do so yourself (and is not one of the 'other' things, like an array) so it is not "iterable" in that sence.
Long story short: foreach can iterate over anything that is iterable, but not everything that foreach can iterate over is called iterable. Its a chair-has-4-legs vs everything-with-4-legs-is-a-chair kinda issue.
I would see the fact that you can iterate over an object without explicitly implementing iterable is a sort of exception, and leave it at that. The only other anwer would be "they did it because".

Related

Strange PHP loop scope what am i missing? [duplicate]

I'm seeing some confusing behavior related to reference/assignment in PHP...
private function doSomething($things)
{
foreach ($things as $thing) {
echo $thing->property; // 'foobar'
$copyThing = $thing;
unset($copyThing->property);
echo $thing->property; // undefined
I expect this behavior when passing variables by reference (&$thing) but I'm not trying to do that here and it seems to be happening anyway. What am I missing?
Just explaining my comment:
objects in foreach loops are always passed by reference
When you use a foreach loop for an array of objects the variable that you are using inside the loop is a pointer to that object so it works as a reference, any change on the object inside the loop is a change on the object outside.
This is because:
objects are always passed by reference (#user3137702 quote)
Detailed and official explanation here.
When you copy and unset your variable:
$copyThing = $thing;
unset($copyThing->property);
you are creating another pointer and unseting it, so the original value is a gone. As a matter of fact, since the foreach loop also uses a pointer the $things array is also affected.
check this ideone (notice the vardump [where the 'a' property is gone], as the output is the same as you got)
I do not know in which version it changed, if ever, as it seems like default object/pointer behavior
As a workaround (some ideas):
Copy your initial array
Use clone: $x = clone($obj); (As long as the default copy constructor works for your objects)

Why does "scalar" appear like this when I cast json_decode() (in non-associative mode) to an object?

While doing some code refactoring I momentarily ended up in a situation where I was basically doing the (somewhat abstracted-out) equivalent of
$data = (object)json_decode('"test"');
Of course I understand json_decode() generates objects on its own unless assoc is false. (Incidentally I got into this situation because I was in the middle of moving some format processing code around, and I hadn't yet realized one of my (object) casts was now redundant.)
But... when this happened, PHP decided that $data contained:
stdClass Object
(
[scalar] => test
)
Wat.
scalar?!
Last I learned, "test" is a string, so it seems more than one pile of things has fallen over internally here. Or is this unintuitive yet intended design?!
I have of course removed the (object) and things work exactly how I intended now. So there's no bug here per se. I just wanted to understand what just happened.
Here you go, in case you want to join in the headscratching:
php -r 'print_r((object)json_decode("\"test\""));'
I'm using 7.0.25.
This is exactly what the manual specifies will happen when casting a scalar type (i.e. int, string, float, boolean) to an object.
If an object is converted to an object, it is not modified. If a value of any other type is converted to an object, a new instance of the stdClass built-in class is created. If the value was NULL, the new instance will be empty. An array converts to an object with properties named by keys and corresponding values. Note that in this case before PHP 7.2.0 numeric keys have been inaccessible unless iterated.
For any other value, a member variable named scalar will contain the value.
$obj = (object) 'ciao';
echo $obj->scalar; // outputs 'ciao'

PHP objects with Int variables

I've noticed something quite strange with PHP Objects and can't find a documented cause of it.
The following code demonstrates the behaviour
<?php
$a = (object) array( 0 => 1 );
foreach($a as $b => $c) {
$a->$b = ++$c; //I'm expecting the key to be overwritten here
}
var_dump($a);
$var = 0;
var_dump($a->$var);
$var = "0";
var_dump($a->$var);
and the output
object(stdClass)#1 (2) {
[0]=>
int(1)
["0"]=>
int(2)
}
int(2)
int(2)
Is the numeric part of the class inaccessible using -> syntax?
When you perform an (object) cast on an array you promote that array as the internal property list of an anonymous object (i.e. stdClass).
The way properties are indexed in an object is slightly different than that of an array; specifically, object property names are always treated as strings whereas array indices are looked up based on the intended type (e.g. numeric strings are seen as integers).
The above behaviour doesn't affect foreach loops because there's no hashing involved there; as far as PHP is concerned, a regular array is being iterated.
To answer your question, yes, the numeric keys from your original array can't be accessed using the -> operator. To avoid this you should remove the numeric indices from your array before the cast is performed.
It's hard to find this behaviour in the documentation, but a hint of it can be found here:
If an object is converted to an array, the result is an array whose elements are the object's properties. The keys are the member variable names, with a few notable exceptions: integer properties are unaccessible ...
FYI
In this particular case you can circumvent the issue by using references; this is not recommended, please follow the earlier advise of not using numeric property names:
foreach ($a as &$c) {
++$c;
}
unset($c);
Update
2014-11-26: I've updated the documentation; the live pages will be updated this Friday - commit.
stdClass handles data terribly loosely, since it's an object representation of an internal array (thus the ability of of casting without problems).
$stdClassObject->property = "value";
The property is handled as a string, but upon casting, the property type doesn't change (which is somehow understandable, as if you cast to an object and then to an array again, you'd have lost all the integer indexes).
I don't think they could do better than that, but you can create your own alternative to stdClass :-)

try to understand The Iterator interface in php

I am reading http://php.net/manual/en/class.iterator.php, but had a hard time to understand the Example #1 Basic usage.
Questions:
var_dump(__METHOD__);
I know you can use variable here, eg: var_dump($count), but METHOD is not variable, or it is global variable/constant?
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
if I change it to:
foreach($it as $key => $value) {
}
if I run the script, it can still show the outcome, why?
var_dump($key, $value);
the outcome is
int 0 string 'firstelement' (length=12)
int 1 string 'secondelement' (length=13)
...
why it is this result? foreach($it as $key => $value), $it is object, it is not $array, so how could this happen?
The Iterator interface allows the class to behave like it was an array in the foreach statement.
Because it's not an array, the class must know, how to behave in this situation. This is done by calling (by the foreach, let's say for simplicity) some methods that are implemented from the Iterator interface. As it's the interface requirements, all of the methods should be implemented, even if you're not going to use some of them, like retrieving the key.
In the methods you can type whatever you like, even something that does not makes sense in the foreach loop (say you do not increase the counter $position).
In the manual the var_dump()s are used to show you which methods are called. The __METHOD__ pseudo-constant is a string that returns the current method's name. You should remove these lines, as they are given for the example purposes only.
Each of the methods from the Iterator interface are public, so you can call them from any place in the code, but there is no need to call them in your program. In the foreach loop they are called automatically so that's why your empty loop works.

Difference between ArrayIterator, ArrayObject and Array in PHP

Can somebody explain clearly the fundamental differences between ArrayIterator, ArrayObject and Array in PHP in terms of functionality and operation? Thanks!
Array is a native php type. You can create one using the php language construct array(), or as of php 5.4 onwards []
ArrayObject is an object that work exactly like arrays. These can be created using new keyword
ArrayIterator is like ArrayObject but it can iterate on itself. Also created using new
Comparing Array vs (ArrayObject/ArrayIterator)
They both can be used using the php's array syntax, for eg.
$array[] = 'foo';
$object[] = 'foo';
// adds new element with the expected numeric key
$array['bar'] = 'foo';
$object['bar'] = 'foo';
// adds new element with the key "bar"
foreach($array as $value);
foreach($object as $value);
// iterating over the elements
However, they are still objects vs arrays, so you would notice the differences in
is_array($array); // true
is_array($object); // false
is_object($array); // false
is_object($object); // true
Most of the php array functions expect arrays, so using objects there would throw errors. There are many such functions. For eg.
sort($array); // works as expected
sort($object); // Warning: sort() expects parameter 1 to be array, object given in ......
Finally, objects can do what you would expect from a stdClass object, i.e. accessing public properties using the object syntax
$object->foo = 'bar'; // works
$array->foo = 'bar'; // Warning: Attempt to assign property of non-object in ....
Arrays (being the native type) are much faster than objects. On the other side, the ArrayObject & ArrayIterator classes have certain methods defined that you can use, while there is no such thing for arrays
Comparing ArrayObject vs ArrayIterator
The main difference between these 2 is in the methods the classes have.
The ArrayIterator implements Iterator interface which gives it methods related to iteration/looping over the elements. ArrayObject has a method called exchangeArray that swaps it's internal array with another one. Implementing a similar thing in ArrayIterator would mean either creating a new object or looping through the keys & unseting all of them one by one & then setting the elements from new array one-by-one.
Next, since the ArrayObject cannot be iterated, when you use it in foreach it creates an ArrayIterator object internally(same as arrays). This means php creates a copy of the original data & there are now 2 objects with same contents. This will prove to be inefficient for large arrays. However, you can specify which class to use for iterator, so you can have custom iterators in your code.
ArrayObject and array are somewhat alike. Merely a collection of objects (or native types). They have some different methods that you can call, but it mostly boils down to the same thing.
However, an Iterator is something else completely. The iterator design pattern is a way to secure your array (making it only readable). Lets take the next example:
You have a class that has an array. You can add items to that array by using addSomethingToMyArray. Note however, that we do something to item before we actually add it to the array. This could be anything, but lets for a moment act like it is very important that this method is fired for EVERY item that we want to add to the array.
class A
{
private $myArray;
public function returnMyArray()
{
return $this->myArray;
}
public function addSomethingToMyArray( $item )
{
$this->doSomethingToItem( $item );
array_push( $item );
}
}
The problem with this, is that you pass the array by reference here. That means that classes that actually use returnMyArray get the real myArray object. That means that classes other than A can add things to that array, and therefor also change the array inside A without having to use the addSOmethingToMyArray. But we needed to doSOmethingToItem, remember? This is an example of a class not in control of its own inner status.
The solution to this is an iterator. Instead of passing the array, we pass the array to a new object, that can only READ things from the array. The most simple Iterator ever is something like this:
<?php
class MyIterator{
private $array;
private $index;
public function __construct( $array )
{
$this->array = $array;
}
public function hasNext()
{
return count( $this->array ) > $this->index;
}
public function next()
{
$item = $this->array[ $this->index ];
this->$index++;
return $item;
}
}
?>
As you can see, i have no way of adding new items to the given array, but i do have posibilities to read the array like this:
while( $iterator->hasNext() )
$item = $iterator->next();
Now there is again, only one way to add items to myArray in A, namely via the addSomethingToArray method. So that is what an Iterator is, it is somewhat of a shell around arrays, to provide something called encapsulation.
Array is array type, ArrayObject and ArrayIterator are built-in classes, with their instances being object type, that partially behave like arrays on syntax and usage level.
For the classes I think the quick idea can be gained from the interfaces they implement:
class ArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable {
class ArrayIterator implements SeekableIterator, ArrayAccess, Serializable, Countable {
Both classes implement:
ArrayAccess, so they support array syntax to access values
Countable, so they support count of values, like arrays do
Iterator (though in different ways), so they fit iterable type and can be looped over with foreach and such
One way to look at it, is that classes go through a lot of trouble to work like an array. The point is that being classes, they can be extended and customized.
They would also be inherently interoperable with any code that expects Iterator in general, so the basic use case is just that - wrapping to feed array data into iterator code.
So in a nutshell ArrayObject and ArrayIterator are Do It Yourself arrays (with some implementation differences between the two). Their instances (partially) behave like array type, but as classes they are extensible and as Iterator implementers they are interoperable with code that wants that.
Unless you need a deeply custom behavior and/or Iterator interoperability, sticking with array type is probably the way to go between these.
Notably there are also implementations of collections around, that aim to deliver the class benefits with more friendly abstractions.
array is one the eight primitive types in PHP. Allthough it comes with a lot of built-in utility functions, but they are all procedural.
Both the ArrayObject and the ArrayIterator allow us to make arrays first class citizens in an object oriented program (OOP).
Difference between ArrayObject and the ArrayIterator is that, since ArrayIterator implements SeekableIterator interface, you can do $myArray->seek(10); with ArrayIterator.
An Iterator is an object that enables a programmer to traverse a container, particularly lists. Various types of iterators are often provided via a container's interface.
There is no much difference between ArrayObject and Array as they represent the same things albeit using different object types.
ArrayIterator is an Iterator that iterates over Array-like objects, this includes all objects that implement ArrayAcess and the native Array type.
In fact, when you foreach over an array, PHP internally creates ArrayIterator to do the traversing and transform your code to look as if typed this,
for( $arrayIterator->rewind(); $arrayIterator->valid(); $arrayIterator-
>next())
{ $key = $arrayIteartor->key();
$value = $arrayIterator->current();
}
So you can see, every collection object has an Iterator except for your defined collections which you need to define your own Iterators for.
EDIT:forgot to address the normal array in my answer..
Before you read all this, if your new to php and not trying to do anything tricky just storing values to be used later, then just use the array primitive type.
$data = [1,2,3,4,5];
//or
$data = array(1,2,3,4,5);
Array is the entry point and you should get used to using first them before considering using the others. If you do not understand arrays then you will probably will not understand the benefits of using the others.
( See https://www.w3schools.com/php/php_arrays.asp for a tutorial on arrays. )
Otherwise continue reading...
I have the same question and after coming here decided to just spend some time testing them out and here is my findings and conclusions...
First lets clear up some things that were said by others that I immediately tested.
ArrayObject and ArrayItterator do not protect the data. Both can still be passed by reference in a for-each loop (see down he bottom for example).
Both return true for is_object(), both return false for is_array() and both allow direct access to the values as an array without providing protection for adding values etc. and both can be passed by reference allowing the original data to be manipulated during a foreach loop.
$array = new ArrayIterator();
var_dump(is_array($array)); // false
var_dump(is_object($array)); // true
$array[] = 'value one';
var_dump($array[0]);//string(9) "value one"
$array = new ArrayObject();
var_dump(is_array($array)); // false
var_dump(is_object($array)); // true
$array[] = 'value one';
var_dump($array[0]);//string(9) "value one"
The big difference can be seen in the functions that are available for either of them.
ArrayIteroator has all the functions required to traverse the values such as a foreach loop. ( A foreach loop will call rewind(),valid(),current(),key() methods )
see: https://www.php.net/manual/en/class.iterator.php for an excellent example of the concept (lower level class documentation).
While an ArrayObject can still be iterated over and access values the same way, it does not offer public access to pointer functions.
Object is kind of like adding a wrapper arount the ArrayItterator object and has public getIterator ( void ) : ArrayIterator that will faciliate traversing of the values inside.
You can always get the ArrayItterator From ArrayObject if your really need the added functions.
ArrayItterator is best choice if you have your own pseudo foreach loop for traversing in weird ways and want better control instead of just start to end traversal.
ArrayItterator would also be a good choice for overriding the default array behavior when iterated over by a foreach loop. eg..
//I originally made to this to solve some problem where generic conversion to XML via a foreach loop was used,
//and I had a special case where a particular API wanted each value to have the same name in the XML.
class SameKey extends ArrayIterator{
public function key()
{
return "AlwaysTheSameKey";
}
}
$extendedArrayIterator = new SameKey(['value one','value two','value three']);
$extendedArrayIterator[] = 'another item added after construct';
//according to foreach there all the keys are the same
foreach ($extendedArrayIterator as $key => $value){
echo "$key: ";//key is always the same
var_dump($value);
}
//can still be access as array with index's if you need to differentiate the values
for ($i = 0; $i < count($extendedArrayIterator); $i++){
echo "Index [$i]: ";
var_dump($extendedArrayIterator[$i]);
}
The ArrayObject might be a good choice if you have more high level complexities with itterators going on for example...
//lets pretend I have many custom classes extending ArrayIterator each with a different behavior..
$O = new ArrayObject(['val1','val2','val3']);
//and I want to change the behavior on the fly dynamically by changing the iterator class
if ($some_condition = true) $O->setIteratorClass(SameKey::class);
foreach ($O as $key => $value){
echo "$key: ";//AlwaysTheSameKey:
var_dump($value);
}
One example might be changing the output of the same data set such as having a bunch of custom iterators that will return the values in a different format when traversing the same data set. eg...
class AustralianDates extends ArrayIterator{
public function current()
{
return Date('d/m/Y',parent::current());
}
}
$O = new ArrayObject([time(),time()+37474,time()+37845678]);
//and I want to change the behaviour on the fly dynamically by changing the iterator class
if ($some_condition = true) $O->setIteratorClass(AustralianDates::class);
foreach ($O as $key => $value){
echo "$key: ";//AlwaysTheSameKey:
var_dump($value);
}
Obviously there are probably better ways to do this kind of thing.
In short these are the major major advantages differences I can see.
ArrayItorator
- lower level ability to control or extend & override traversal behaviors.
VS
ArrayObject
- one Container for data but able to change ArrayIterator class.
There are also other differences you might care to inspect but I'd imagine you wont fully grasp them all until you use them extensively.
It appears both objects can be used by reference in a foreach but NOT when using a custom itterator class via an ArrayObject..
//reference test
$I = new ArrayIterator(['mouse','tree','bike']);
foreach ($I as $key => &$value){
$value = 'dog';
}
var_dump($I);//all values in the original are now 'dog'
$O = new ArrayObject(['mouse','tree','bike']);
foreach ($O as $key => &$value){
$value = 'dog';
}
var_dump($O);//all values in the original are now 'dog'
$O->setIteratorClass(SameKey::class);
foreach ($O as $key => &$value){//PHP Fatal error: An iterator cannot be used with foreach by reference
$value = 'dog';
}
var_dump($O);
Recommendation/Conclusion
Use arrays.
If you want to do something tricky then,
I'd recommend always using ArrayIterator to start off with and only move on to ArrayObject if you want to something specific that only ArrayObject can do.
Considering the ArrayObject can make use of any custom ArrayIterators you have created along the way, id say this is the logical path to take.
Hope this helps you as much as it helped me looking into it.
Arrays
An array in PHP is actually an ordered map. A map is a type that
associates values to keys. This type is optimized for several
different uses; it can be treated as an array, list (vector), hash
table (an implementation of a map), dictionary, collection, stack,
queue, and probably more. As array values can be other arrays, trees
and multidimensional arrays are also possible.
The ArrayObject class
This class allows objects to work as arrays.
The ArrayIterator class
This iterator allows to unset and modify values and keys while
iterating over Arrays and Objects.
When you want to iterate over the same array multiple times you need
to instantiate ArrayObject and let it create ArrayIterator instances
that refer to it either by using foreach or by calling its
getIterator() method manually.

Categories