PHP - Access value from previously-defined key during array initialization - php

I'm looking to see if it's possible to access the value of a key I previously defined within the same array.
Something like:
$test = array(
'foo' => 1,
'bar' => $test['foo']
);
I know I can always do so after initialization, I am just wondering if it's possible during initialization?

No, $test doesn't exist until the full constructor is evaluated.
For example: http://codepad.viper-7.com/naUprJ
Notice: Undefined variable: test..
array(2) { ["foo"]=> int(1) ["bar"]=> NULL }
It's probably for the best. Imagine of this worked:
$test = array('foo' => $test['foo']); // mwahaha
If you need to do this a lot, you could create a class that takes keys of a particular format that flag to the class constructor that it should be parsed until all relevant keys are evaluated.

Related

Reassignment of a variable referencing an array reassigns the original variable

I have this code:
$originalBar = [
'baz' => 18
];
function modify (&$bar) {
$bar['test'] = true;
$bar = [
'data' => 42
];
global $originalBar;
echo 'same ' . ($bar === $originalBar) . PHP_EOL;
}
modify($originalBar);
var_dump($originalBar);
I know that since the function accepts an argument by reference, any passed array will be modified. So I expect to change the original array with:
$bar['test'] = true;
...and set key test of $originalBar to true. However, when I reassign $bar I expect that the variable no longer points to the original array ($originalBar) and any changes onwards don't modify it. Obviously, that's not the case because the output is:
same 1
array(1) {
["data"]=>
int(42)
}
By reassigning $bar, I reassigned $originalBar too. I expected that this would function the same way as it does in JavaScript, which is why I was confused at first.
My question is - is this documented somewhere? I read the Passing by Reference documentation but I didn't find that there.
Edit: If instead of doing this in the modify function:
$bar = [
'data' => 42
];
...I do this:
$arr = [
'data' => 42
];
$bar = &$arr;
...I get my initially expected result:
same
array(2) {
["baz"]=>
int(18)
["test"]=>
bool(true)
}
It's interesting that in this case, $originalBar is not assigned to the value of $arr and keeps its old value instead.
I'd like to correct some misapprehensions in the question, comments, and self-answer.
Passing objects in PHP behave similarly to passing objects in JS. This is not passing by reference, but passing an "object pointer" by value; the difference is subtle but important. When you pass a variable into a function by reference, you can assign any value to that variable inside the function, and it will have that value on the outside of the function as well. Passing an object does not give you this ability - you can't replace it with a different object, or with the value 42 - but it does let you mutate the object - you can call a method or set a property, and the code outside the function will be able to see the result.
The difference between JS and PHP here is that arrays in JS are objects, but in PHP they are not, so they do not have this "mutable object pointer" behaviour when passed by value.
Pass by reference, as implemented in PHP and many other languages, means that any assignment or modification of the variable inside the function is reflected outside the function. There is nothing special about arrays here, the same thing happens with $foo=42; or $foo++;.
JS does not have an equivalent for explicit pass-by-reference, but other languages such as C# and Pascal have the same concept with different syntaxes.
Things get more unusual in PHP when you assign by reference ($foo =& $bar); one way to think of it is that you have one variable, and you're giving it two names.
I prefer to spell what you wrote as $bar = &$newValue as $bar =& $newValue, because the action isn't really "assign something to $bar" but "bind the name $bar as an alias for something". In binding that alias, it discards any previous binding for that name, so it undoes the "pass by reference" nature of the name.
A possible point of confusion is that arrays in PHP are not the same as arrays/objects in JS and instead behave like strings or numbers for pass-by-value purposes.
In PHP, arrays passed by value will be copied on write when dirtied in a function, much like strings or numerical types:
function modify($a) {
global $foo;
var_dump($a === $foo); # => true
$a['hello'] = "world";
var_dump($a === $foo); # => false, we wrote to $a and it was copied.
}
$foo = ["baz" => 42];
modify($foo);
var_dump($foo); # => ["baz" => 42] (the original was unchanged after the function call)
From a JS perspective, we might expect that $a['hello'] = "world"; would reflect on the outer object and not cause a copy to be created:
const modify = a => {
console.log(a === foo); // => true
a.hello = "world";
console.log(a === foo); // => true
};
const foo = {bar: "baz"};
modify(foo);
console.log(foo); // => {"bar": "baz", "hello": "world"}
The pass-by-value behavior in PHP is unsurprising on objects:
class A {
function __construct() {
$this->bar = "hello";
}
}
function modify($a) {
global $foo;
var_dump($a === $foo); # => true
$a->bar = "world";
var_dump($a === $foo); # => true
}
$foo = new A();
modify($foo);
var_dump($foo); /* => object(A)#1 (1) {
["bar"]=>
string(5) "world"
}
*/
In PHP, passing by reference enables mutation of the original array:
function modify(&$a) {
global $foo;
var_dump($a === $foo); # => true
$a['hello'] = "world";
var_dump($a === $foo); # => true
}
$foo = ["baz" => 42];
modify($foo);
print_r($foo); # => ["baz" => 42, "hello" => "world"]
A reference variable can also be reassigned to a new value:
function modify(&$a) {
global $foo;
var_dump($a === $foo); # => true
$a = "world";
var_dump($a === $foo); # => true
}
$foo = ["baz" => 42];
modify($foo);
print_r($foo); # => "world"
Since JS does't support passing by reference, there is no clear parallel between JS and PHP for these behaviors, aside from the usage of the reference operator to support JS/object-like mutations of arrays inside a function.
That's not documented anywhere because it's the expected behavior. I have just misread. The PHP docs say:
You can pass a variable by reference to a function so the function can modify the variable.
I've had the wrong impression because objects are passed by reference by default in JavaScript and there, reassigning the argument variable doesn't change the original variable.
The intention of passing by reference in PHP is to be able to modify the variable, not just the object it points to. Meaning, you can not only change the object, but also reassign the passed variable.
As #ggorlen said in the comments to my question:
The reference operator is really doing two things: 1) exposing the original memory location for reassignment and 2) allowing a complex structure such as an array to be mutated. In JS, the first is never possible and the second is always possible. PHP gives some "flexibility" in these regards where on the one hand, function ($var) is much more restrictive than most langs while function (&$var) is more permissive than most languages, which is not exactly intuitive.

PHP Array to Object

Given the following array:
$array = array(
'item_1' => array(
'item_1_1' => array(
'item_1_1_1' => 'Hello',
),
'item_1_2' => 'World',
),
'item_2' => array(),
);
How can I convert that into an Object?
Option 1
$obj = (object) $array;
Or
Option 2
$object = json_decode(json_encode($array), FALSE);
Or something else?
I would like to know the difference in the output between the 2 option and understand the best practice for creating this conversion.
Well you are answering somehow your own question, but if you want to have an object with the attributes like your array you have to cast it, this way an array will remain an array
$obj = (object) $array;
OUTPUT:
object(stdClass)#1 (2) {
["item_1"]=>
array(2) {
["item_1_1"]=>
array(1) {
["item_1_1_1"]=>
string(5) "Hello"
}
["item_1_2"]=>
string(5) "World"
}
["item_2"]=>
array(0) {
}
}
if you are using the json_decode version it will convert arrays to objects too:
object(stdClass)#2 (2) {
["item_1"]=>
object(stdClass)#3 (2) {
["item_1_1"]=>
object(stdClass)#4 (1) {
["item_1_1_1"]=>
string(5) "Hello"
}
["item_1_2"]=>
string(5) "World"
}
["item_2"]=>
array(0) {
}
}
NOTE: just the empty array will be an array here.
To Answer your question: The best practice depends on what YOU need.
It depends, really: if you are working on data that might be an array in one case, and an object the next, it would probably be best to use the json_decode trick, simply because unlike a cast, its result is "recursive". There is one very important thing to keep in mind here, though: numeric indexes can, and probably will cause problems for you at some point in time. Take a look at this bug report
This is documented here, but not in a way that really stands out:
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;
Exampe of the problem:
$data = [
'foo' => 'bar',
123 => 'all is well',
];
$obj = json_decode(json_encode($data));
var_dump($obj->foo);//bar
var_dump($obj->{123});//all is well
$cast = (array) $obj;
var_dump($cast);//shows both keys
var_dump(isset($cast[123]));//FALSE!!!
var_dump(isset($cast['123']));//FALSE
Basically: If you start converting arrays to objects and back again, numeric keys are not reliable anymore. If I were you, I'd simply change the code that is passing the data where possible, or I'd create a value object that can be set using an array or an object, and normalize the data that way.

Wrap superglobal $_GET inside a class property

The goal is to get some precise values of $_GET in a property of a class while:
removing any key not desired
defaulting values when keys are not defined
With this code in a file /request.php:
$req = new request();
var_dump($req);
class request {
private $Get;
function __construct() {
$this->Get = filter_input_array(INPUT_GET,array (
'menu'=>array (
'filter'=>FILTER_VALIDATE_INT,
'options'=>array (
'default'=>30,
),
),
));
}
}
from php's man page for filter_input_array() about the third parameter (true by default):
Add missing keys as NULL to the return value.
I would expect that calling domain.com/request.php would yield a defaulted [sic] array with the integer 30 as menu's value. however when no $_GET are defined (that is, when there's no character after a question mark in the url) filter_input_array returns null therefore the var_dump is this:
object(request)#1 (1) { ["Get":"request":private]=> NULL }
however when $_GET is defined (that is, having at least one character after the question mark in the url) such as calling domain.com/request.php?a will yield:
object(request)#1 (1) { ["Get":"request":private]=> array(1) { ["menu"]=> NULL } }
How can I force filter_input_array() to return an array so that default values will be built even if I call an url with no $_GET value defined like index.php?
It seems possible to rewrite a request from .htaccess so that I could mimic a $_GET value being defined if there are not, but it feels weird.
Why not to use another simple way?
var_dump(filter_var_array($_GET, array(
'key' => FILTER_DEFAULT,
), true));

PHP: What is this object type, and how do I add to it without causing warnings

I've encountered this here and there, and I always worked around it, but I've just gotta know.
Is this an array, an object, or ??? (Let's say I got this via var_export($co))
stdClass::__set_state(array(
'name' => 'Acme Anvil Corp.',
))
Most importantly, how can I add a value to it?
Let's say I want to add a value, like $co->owner = 'Wiley Coyote'. That always throws a warning.
How?
What?
The curiosity is just KILLING me :)
[EDIT]
As a clarification, I guess the root of my question would be "How do I add variables to the object without triggering Warnings?"
Here's the warning I always get:
A PHP Error was encountered
Severity: Warning
Message: Attempt to assign property of non-object
And a var_dump($co) yields: (currently done in a loop, if it's pertinent)
object(stdClass)#185 (1) {
["name"]=>
string(16) "Acme Anvil Corp."
}
[/EDIT]
$co is object of type stdClass. You can create one and add as many properties as you want without any warning:
$obj = new stdClass();
$obj->prop1 = 'val1';
$obj->prop2 = 3.14;
The warning you are facing is most probably Creating default object from null value, meaning that you are trying to assign property to uninitialized variable.
You should not try running the code produced by var_export for stdClass objects, since they don't have method __setState.
Not too sure about your error - but the stdClass class is a default in built class to PHP.
It's basically an empty object
You should be able to set properties with $class->foo = 'bar'
If you're not talking about stdClass - but the fact you're getting an array in var_export - it's simply a representation of the internal object state .
class Foo
{
private $bar = 'baz';
}
$foo = new Foo();
var_export($foo);
Should give output similar to
Foo::__set_state(array(
'bar' => 'baz',
))
The warning you get may be due to the property being protected/private.
var_dump() generally tells you about the access modifiers on internal objects better than var_export()
Can you clarify what the warning you get is and what the question is regarding, and I'll edit up my answer to make it relevant to what you're asking
var_export() prints executable PHP code able to recreate the variable exported.
When encountering objects, they are always exported as a call to their static magic method __set_state(). Compared to other magic methods, this method does not really have much magic in it besides the fact that var_export guarantees to create static calls with this method, so it is simply just the agreed upon name for such a method.
The __set_state() method of a class should return an instance of the object with all exported properties set to the values given as the array parameter.
Example:
class Foo {
public $bar;
public static function __set_state($params) {
$obj = new Foo;
foreach ($params as $key => $value) {
$obj->$key = $value;
}
return $obj;
}
}
$foo = new Foo;
$foo->bar = "Hello World";
var_export($foo);
This prints something like:
Foo::__set_state(array('bar' => 'Hello World'));
If you execute this and save it to a variable again:
$x = Foo::__set_state(array('bar' => 'Hello World'));
it will recreate the object with all given properties set.
It does not work with stdClass objects, because they don't have a __set_state() method.
See http://de3.php.net/manual/en/language.oop5.magic.php#object.set-state for more info.
Update:
This code does not trigger any warnings:
ini_set('display_errors', 1);
error_reporting(E_ALL | E_STRICT);
$co = new stdClass();
$co->name = 'Acme Anvil Corp.';
var_export($co);
$co->owner = 'Wiley Coyote';

Using functions like array_walk (and similar functions) to modify arrays in PHP >= 5.3

PHP has some great functions (like array_walk) that allow you to process each element in an array. They're generally set up so you specify the array you want processed as the first parameter and a callback function to apply to each element as the second. These functions return booleans indicating success, not a copy of the modified array as you might expect. If you want the array to be modified, you have to pass the array in by reference like array_walk(&$my_array, 'my_callback');
However, in PHP 5.3 and above, if you pass by reference to function call you get a E_DEPRECATED error.
Does anyone know (if there exists) a correct way to use these functions to modify arrays without triggering the errors and without explicitly suppressing them? Are there newer alternatives to these old array processing functions.
Values are passed by reference implicitly in PHP >= 5.3 as determined by the function definition.
Function definition for array_walk():
bool array_walk ( array &$array , callable $funcname [, mixed $userdata = NULL ] )
Note &$array. As such, you do not need to explicitly pass the array by reference in the function call in PHP >= 5.3.
array_walk($my_array, 'my_callback');
However, you will need to ensure that the callback accepts it's value by reference accordingly (as demonstrated by nickb).
Also take a look at PHP 5.4 Call-time pass-by-reference - Easy fix available?
Because you should be defining the callback to accept its parameter by reference to modify the array.
array_walk( $my_array, function( &$el, $key) { $el = $el / 2; });
So a quick example like this:
$my_array = range( 2, 10, 2);
array_walk( $my_array, function( &$el, $key) { $el = $el / 2; });
var_dump( $my_array);
Will output:
array(5) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
[4]=>
int(5)
}
You can also pass an argument for the callback as the third parameter of array_walk. The problem is how to pass a reference as the callback argument. This used to be possible using the & prefix when calling array_walk. This is deprecated and subsequently made illegal. Defining the callback with a reference-type third parameter doesn't help here.
A workaround could be to pass a reference inside an array (&$var is allowed as argument of 'array'!) as the third argument and dereference the array in the callback to obtain the reference again, like so:
function cb(&$v, $k, $ar) {
$v='bla'.$k;
$ar[0]++;
}
$count=0;
$arr = array('sint'=>'er','kla'=>'aas','en'=>'zwartepiet');
array_walk($arr,'cb',array(&$count));
var_dump($arr,$count);
Which prints:
array(3) {
["sint"]=>
string(7) "blasint"
["kla"]=>
string(6) "blakla"
["en"]=>
string(5) "blaen"
}
int(3)
When call-time references were still allowed, it used to be possible like so:
function cb(&$v, $k, $ref) {
$v='bla'.$k;
$ref++;
}
$count=0;
$arr = array('sint'=>'er','kla'=>'aas','en'=>'zwartepiet');
array_walk($arr,'cb',&$count);
var_dump($arr,$count);

Categories