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 :-)
Related
I came across (array) in WordPress in the following code but looked in the PHP manual search for (array) but could not find anything (https://www.php.net/manual-lookup.php?pattern=%28array%29&scope=quickref)
foreach ( (array) $cron as $timestamp => $hooks) {
foreach ( (array) $hooks as $hook => $args ) {
$key = md5(serialize($args['args']));
$new_cron[$timestamp][$hook][$key] = $args;
}
}
Could someone please explain what this (array) does?
This is called casting a variable (AKA casting or type-juggling). You are saying you want $cronhooks to be converted to and evaluated as an array. Look at this example:
$a = (int) 5.3;
print($a);
5
The (int) indicates that I want an integer from 5.3. so the PHP convert it.
It's casting the variable to an array. Perhaps $cronhooks was an object rather than an array, and could not be iterated as a key => value array.
Here's the manual page for Type Jugling in PHP
https://www.php.net/manual/en/language.types.type-juggling.php
Array members can be accessed using index or key as follow:
$cronhooks[0]; // the first member
$people['tom']; // the member with the key 'tom'
objects and classes have members which are accessed using the object operator:
$person->name; // name property of a person object
$person->save(); // might be a method to save the person back to the database
Interestingly wordpress has a built in internal function called _get_cron_array() which should return the cron jobs as an array.
Currently that source code is here:
https://github.com/WordPress/WordPress/blob/056b9c47a2114a23e9a892df2d5f79856dbe5a73/wp-includes/cron.php#L924-L945
But even in their own code they are casting it to array, which seems odd given the function professes to return an array in it's name!
That one casting example:
https://github.com/WordPress/WordPress/blob/056b9c47a2114a23e9a892df2d5f79856dbe5a73/wp-includes/cron.php#L95
Anyway this was fun to explore :D
This is infact known as type-juggling, or type-casting.
In some cases (such as int to float, int to string, string to an array, int to an array) the type will be converted (allowing a regular string or int to be looped though as in the example above).
However, some types cannot be effectively converted to another, such as an array to a string, int, or class object for some examples and PHP will issue a notice such as:
Notice: Array to string conversion in /path/file.php on line 10
and the array will be converted to a string with the contents "Array". However your PHP will not throw an error so the script will continue and not work as you probably expected.
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'
It was my understanding for a while that foreach clones the individual objects in its iterations, and I had used & to iterate by reference. However, in my latest machine which runs PHP 5.5.10, I'm able to omit & and still update the original object. I don't see anything in the release notes. Am I misunderstanding something?
foreach ($items as $item) { // No "&"
$item->setData('123'); // Updates the respective object in $items
// Checked object ID hash, and they're the same with or without "&"
}
PHP Change Log: http://php.net/ChangeLog-5.php
PHP paradigm is that objects (and resources) are always references, while other types (base types or arrays) are copied, so the & operator has no effect on objects (and is meaningless on resources since only "special functions" i.e. external library modules can take them as parameters), but allows to pass variables of other types by reference.
In PHP objects are always passed by reference. How it work? Objects has identifier which is passed, we can call this handle. So if you:
$a = new A();
$b = $a;
$b->foo = 2;
echo $a->foo;
return value will be 2. Why? Because the handle of the object is copied to $b and they both - $a and $b - point to the same object. We can term this as a reference, but it isn't reference meaning in strict way, but behaviour of this implementation is similar to reference. So you do not need any use of references because PHP make it for you by default.
Read the OOP references documentation.
Foreach assigns each element of the array to the given variable, as in simple assignment, with =, which copies the value of the element to the variable. This is always true regardless of what type the value happens to have. (Only if you do as &$item will it be a reference.)
$item is not "an object". $item is an object reference, basically a pointer to an object. In PHP 5, you cannot have a variable whose value "is an object" -- when you do new something(), you get a reference to an object; when you access a field or method with ->, the left side must be a reference to an object. Basically, you always deal with references to objects, never objects themselves.
Two object references can point to the same object, and if you modify an object by calling a method on it using one object reference, the result is visible through another object reference that points to the same object.
In other programming languages the definition of arrays is something which can hold similar kind of elements. For example if I declare something like int i[] it will store integers, but in PHP a single array seems to be holding strings and numbers together.
Will the number/integer be treated as string in such type of array in PHP?
According to the PHP manual you can indeed store heterogeneous types inside a PHP "array" - scroll down to example 3.
Note that even though the example is about keys being ints or strings, the values assigned in the example are also both ints and strings, demonstrating that it is possible to store heterogeneous types.
Be aware that in the case of different-typed keys there is automatic casting involved so you may have surprising results in the case where e.g. a string contains a valid decimal representation.
Yes. A PHP array can have multiple data types in it.
Also, you should note that arrays in PHP actually are represented in the form of key-value pairs, where the elements you will input into the array are values.
You can explicitly define keys too, when entering elements into the array, but when you don't, PHP will use indices starting from 0.
Example:
<?php
$array = array(
"foo" => "bar",
"bar" => "foo",
100 => -100,
-100 => 100,
);
var_dump($array);
?>
PHP will interpret as
array(4) {
["foo"]=>
string(3) "bar"
["bar"]=>
string(3) "foo"
[100]=>
int(-100)
[-100]=>
int(100)
}
Reference- http://php.net/manual/en/language.types.array.php
You can store anything you want in an array.
Will the number/integer be treated as string in such type of array in PHP?
Not upon storing it. However, when you use a value as such, PHP will convert it. The usage of a value determines its interpretation. (Attention, the key is converted upon storing, however, if it is considered numerical)
Not going to put oil on the fire of the PHP Arrays are no arrays here…
But yes, you can put different variable types (string, int, …) together in a PHP thing called Array.
$arr = array(array(array()));
foreach($arr as $subarr)
{
$subarr[] = 1;
}
var_dump($arr);
Output:
array(1) {
[0]=>
array(1) {
[0]=>
array(0) {
}
}
}
But for object,it's reference:
class testclass {
}
$arr = array(new testclass());
foreach($arr as $subarr)
{
$subarr->new = 1;
}
var_dump($arr);
Output:
array(1) {
[0]=>
object(testclass)#1 (1) {
["new"]=>
int(1)
}
}
Why treat array different from object?
PHP passes all objects by reference. (PHP5?)
PHP passes all arrays by value.
Originally PHP passed both objects and arrays by value, but in order to cut down on the number of objects created, they switch objects to automatically pass by reference.
There is not really a logical reason why PHP does not pass arrays by reference, but that is just how the language works. If you need to it is possible to iterate over arrays by value but you have to declare the value explicitly by-reference:
foreach ( $myArray as &$val ){
$val = 1; //updates the element in $myArray
}
Thanks to Yacoby for the example.
Frankly I prefer arrays to be passed by value because arrays are a type of basic data structure, while objects are more sophisticated data structures. The current system makes sense, at least to me.
Foreach copies the iterated array because it allows you to modify the original array while inside the foreach, and it's easier to use that way. Wouldn't it be the case, your first example would blow up. There is no way to keep PHP from copying the array inside a foreach. Even when using the pass-item-by-reference syntax foreach($foo as &$bar), you'll still work on a copy of the array, one that contains references instead of actual values.
Objects, on the other hand, are expected from most object-oriented developers to always be passed by reference. This became the case in PHP 5. And when you copy an array that contains objects, you actually copy the references to objects; so even though you're working on an copy of the array, you're working on the same objects.
If you want to copy an object, use the clone operator. As far as I know, there is no way to have certain objects always be passed by value.
$foo = new Foo;
$bar = clone $foo;
Why would array and object be treated the same?
PHP simply passes objects by reference (->), and passes all arrays by value.
If all objects were passed by value, the script would make many copies of the same class, thereby using more memory.