PHP: Updating an associative array though a class function? - php

I want to be able to update an associative array within an object based on function input, but I'm at a loss for how to do this. Here's my problem. Let's say there's this data container in my object:
private $vars = Array
(
['user_info'] = Array
(
['name'] => 'John Doe'
['id'] => 46338945
['email'] => 'johndoe#example.com'
['age'] => 35
)
['session_info'] = Array
(
['name'] => 'session_name'
['id'] => 'mGh44Jf0nfNNFmm'
)
)
and I have a public function update to change these values:
public function update($keys, $changeValueTo) {
if (!is_array($keys)) {
$keys = array($keys);
}
nowWhat();
}
Ultimately, I just want to be able to convert something like this
array('user_info', 'name')
into this:
$this->vars['user_info']['name']
so I can set it equal to the $equalTo parameter.
This usually wouldn't be a problem, but in this scenario, I don't know the schema of the $vars array so I can't write a foreach statement based on a fixed number of keys. I also can't just make $vars public, because the function needs to do something within the object every time something is changed.
I'm worried that this is a recursive scenario that will involve resorting to eval(). What would you recommend?

It is not hard to do it. You need passing variable by reference and a loop to achieve. No need eval().
$var = array(
"user_info" => array(
"name" => "Visal"
)
);
function update(&$var, $key, $value) {
// assuming the $key is an array
$t = &$var;
foreach ($key as $v) {
$t = &$t[$v];
}
$t = $value;
}
update($var, array("user_info", "name"), "Hello World");
var_dump($var);

Related

Create an array from the keys of an array

Let's pretend I have this:
$str = "/a/b/c/d/";
$arr = array_filter( explode("/", $str );
At this point $arr contains 4 elements. Is there a way I could create a path in an array with those 4 elements, such as:
$result = [
"a" => [
"b" => [
"c" => [
"d" => [
]
]
]
]
]
...without iterating over $arr?
I know that
$x["a"]["b"]["c"]["d"] = 1;
is perfectly valid and it will create a 4 levels array even if $x wasn't declared as an array, so what I'm asking should be possible.
I DO NOT recommend this as there are security implications when using eval(). However, because I stated in the comments that it couldn't be done without iteration, I felt compelled to post this as an answer (yes, I know implode() iterates internally).
$str = "/a/b/c/d/";
$arr = array_filter( explode("/", $str ));
$keys = '["'.implode('"]["', $arr).'"]';
eval('$x'.$keys.' = 1;');
print_r($x);
For a more practical way see How to write getter/setter to access multi-leveled array by dot separated key names?
I wrote a function once, that had this behaviour as a side effect. It doesn't iterate, but uses recursion.
See: https://github.com/feeela/php-utils/blob/master/function.getArrayValueReference.php
You may call like that:
<?php
$newArray = array();
$keys = 'a/b/c/d';
$referenceToD =& getArrayValueReference( $newArray, explode( '/', $keys ), true );
$referenceToD[0] = 'foo';
$referenceToD[1] = 'bar';
print_r( $newArray );
This modifies the array $newArray and creates all the levels. The functions return value is a reference to the last key ('d' in that example).
…which results in:
Array (
[a] => Array (
[b] => Array (
[c] => Array (
[d] => Array (
[0] => foo
[1] => bar
)
)
)
)
)
There is no way to use all the values of $arr without iterating over it.
I guess you don't want to write a foreach loop but use some PHP function that does the iteration for you.
A simple solution that iterates two times over the array (behind the scene)
This is a possible solution:
$x = array_reduce(
array_reverse($arr),
function ($carry, $item) {
return [$item => $carry];
},
1
);
It generates the same result as:
$x = [];
$x['a']['b']['c']['d'] = 1;
Unfortunately it iterates over $arr two times (array_reverse() and array_reduce()).
Another solution that generates a hierarchy of objects
Another approach that generates the required embedding using objects (stdClass) instead of arrays:
$out = new stdClass;
array_reduce(
$arr,
function ($carry, $item) {
$v = new stdClass;
$carry->{$item} = $v;
return $v;
},
$out
);
It works using a single iteration over $arr but it relies on the way the objects are handled in PHP to work (and this doesn't work with arrays).
PHP handles the objects in a way that makes them look like they are passed by reference. It's a common misconception that the objects are "passed by reference" in PHP but this is not true. A double indirection is responsible for this behaviour.
A recursive solution
function makeArray(array $arr, $initial)
{
if (! count($arr)) {
return $initial;
} else {
$key = array_shift($arr);
return [ $key => makeArray($arr, $initial) ];
}
}
$out = makeArray($arr, 1);
This solution iterates only once over the array and generates a hierarchy of arrays but recursivity is disastrous for large input arrays because it uses a lot of memory.

Is there an elegant way to reduce an structure to a simple array?

This is a typical array structure:
$s = array ('etc'=>'etc', 'fields' =>
array (
0 => array (
'name'=>'year', 'description'=>'Year of ...', 'type'=>'integer',
),
1 => array (
'name'=>'label', 'description'=>'Offical short name', type'=>'string',
),
2 => array (
'name' => 'xx', 'description' => 'Xx ...', 'type' => 'string',
)
));
Here is a non-elegant way (or "not so elegant way") to reduce the big array to a simple array containing just one column:
$fields = array();
foreach ($strut['resources'][0]['schema']['fields'] as $r)
$fields[] = $r['name'];
This works, but is it possible to do the same with only one instruction? Perhaps using like array_reduce(), but I not see how.
Here are other typical "elegance PHP problem":
$fieldsByName = array();
foreach ($strut['resources'][0]['schema']['fields'] as $r)
$fields[$r['name']] = array(
'description' =>$r['description'],
'type' =>$r['type']
);
Is there a PHP alternative? The idea here is to use the keyword (name in the example) as an array key, and the other elements as usual fields, so, the generic non-elegant algorithm is
$fieldsByName = array();
foreach ($strut['resources'][0]['schema']['fields'] as $r){
$key = $r['name'];
unset($r['name']);
$fields[$key] = $r;
}
You can use array_column to extract all values with key name into another array
$names = array_column($strut['resources'][0]['schema']['fields'], 'name');
you could put your array thru this function:
function flatten(array $array) {
$return = array();
array_walk_recursive($array, function($a) use (&$return) { $return[] = $a; });
return $return;
}
it will result in just a literal sequence of just values of your multidimensional array, like so.
Array
(
[0] => etc
[1] => year
[2] => Year of ...
[3] => integer
[4] => day
[5] => Day of the ...
[6] => string
[7] => xx
[8] => Xx ...
[9] => string
)
then, as you know original structure - you can parse this how needed. 4ex: every third value could be new assoc array's key value that holds an array with arrays of first two values.., or as you wish
array_column is first logical announcement, no surprises there.
Depending on how normalized your data is and how often this issues comes up, you could implement a class around your data. You can use the ArrayAccess, Iterator and Countable to make the change completely transparent, and you would be able to implement helper methods to hide the complexity of fetching data.
Here is an example, just using ArrayAccess:
class Strut implements ArrayAccess {
private $data;
private $fieldsByName = null;
public function __construct($data) {
$this->data = $data;
}
public function fieldsByName() {
//If the result has not already been computed
if($this->fieldsByName === null) {
$this->fieldsByName = array();
foreach($this->data['resources'][0]['schema']['fields'] as $r) {
$this->fieldsByName[ $r['name'] ] = array(
'description' =>$r['description'],
'type' =>$r['type']
);
}
}
return $this->fieldsByName;
}
/**
* ArrayAccess Methods
*/
public function offsetSet($offset, $value) {
$this->data[$offset] = $value;
}
public function offsetExists($offset) {
return isset( $this->data[$offset] );
}
public function offsetUnset($offset) {
unset( $this->data[$offset] );
}
public function offsetGet($offset) {
return isset( $this->data[$offset] ) ? $this->data[$offset] : null;
}
}
Using the above code you should be able to access your data just has you have been, but you also have the option of defining additional accessors in a nice container. Note that you also have to implement the Iterator interface to be able to foreach over your data.
This doesn't address the "elegance" issue of the underlying implementation (the other solutions do a nice job of that), but this way hides the complexity completely.

merge and create array from two different sized arrays

I need to take two arrays, and combine them to look like this. Not sure how. Here are the two arrays.
$items = array( 'activity', 'photos');
$activity_subitems = array ('var1', 'var2', 'var3');
$photo_subitems = array ('var1', 'var2');
I would like it to be in an array like this;
$query_vars = array(
'activity' => 'var1',
'activity' => 'var2',
'activity' => 'var3',
'photos' => 'var1',
'photos' => 'var2'
);
If the resulting array is a dictionary, you can't, because you are creating more than one same Key for multiple Values. If it is not a dictionary, rephrase the question please.
If what you want is to create something like this:
Array (
[activity] => Array (
[0] => var1
[1] => var2
[2] => var3
)
[photos] => Array (
[0] => var1
[1] => var2
)
)
Then you can do it like this (assuming this is PHP we are talking about):
$items = array( 'activity', 'photos');
$activity_subitems = array ('var1', 'var2', 'var3');
$photos_subitems = array ('var1', 'var2');
$query_vars = array();
foreach ($items as $item) {
$query_vars[$item] = ${$item.'_subitems'};
}
print_r($query_vars);
Also, keep in mind that the value names for the subarrays are generated from the values from the first array by appending _subitems to them.
I have renamed the variable $photo_subitems to $photos_subitems for this to work, because that is the value from the $items array.
I don't recognize the language, but in a pseudo-code kinda way
query_vars = new array();
for ($i in $items) {
for ($j in $i+'_subitems') {
query_vars.add($i, $j);
}
}
keeping in mind that you're creating the variable $j from from the value stored in $items (located # $j) and a static field _items
I am assuming you are working with PHP. First off, you will not be able to have multiple values with the same key in your array, but if you wish, you can make the values keyed to arrays themselves.
$items = array( 'activity', 'photos');
$activity_subitems = array ('var1', 'var2', 'var3');
$photo_subitems = array ('var1', 'var2');
$query_vars = array();
$query_vars['activity'] = $activity_subitems
$query_vars['photo'] = $photo_subitems
If you would like to have an array where the strings themselves start with photo/activity, but where it is not a dictionary (again, can't have duplicate keys), you can do something like the following:
NOTE: the eval() function is generally considered unsafe, and is not recommended for use in just about every language I know, and this is also true for PHP. Use the following at your own risk, but as long as you have strong control over what text can be placed into an eval() call (i.e., no user input is ever used), it should be somewhat safer. I get the impression you might want to use the values in $items to say what variable to use when building your result.
$items = array( 'activity', 'photos');
$activity_subitems = array ('var1', 'var2', 'var3');
$photos_subitems = array ('var1', 'var2');
$query_vars = array();
foreach ($items as &$item) {
foreach (eval('$'.$item.'_subitems') as &$subitem) {
$query_vars[] = $item.' =>'.$subitem;
}
}

Remove _ (underscore) when converting an object to array

According to the Zend framework naming conventions private variables should start with _ (underscore). But it cause problems when converting an object to an array (casting). The array element keys start with "_". How can I remove the underscore while converting an object to array?
For example
class Book {
private _name;
private _price;
}
will be converted to
array('_name' => 'abc', '_price' => '100')
I want to remove the '_' in array element keys.
Somewhat tough without an exact example but this should be close. Basically loops through, finds elements that start with _, removes them and inserts an underscore-less element to the array
$arr = array(
'foo1' => 'bar1',
'_foo2' => 'bar2',
'_foo3' => 'bar3'
);
foreach ($arr as $key => $val) {
if (substr($key,0,1) == '_') {
unset($arr[$key]);
$arr[substr($key,1)] = $val;
}
}
After this, $arr will look like
Array
(
[foo1] => bar1
[foo2] => bar2
[foo3] => bar3
)
I think maybe you want something like this:
//because of variable scope this method must be in the class where the private propeties are.
public function toArray() {
$vars = get_object_vars($this);
$array = array();
foreach ($vars as $key => $value) {
$array[ltrim($key, '_')] = $value;
}
return $array;
}
This will allow you to call ->toArray() in your model, view or controller.

Accessing a PHP array

I'm sure this has been asked before, but I can't seem to find the answer.
To that end, I have an array that looks like this:
Array
(
[0] => Array
(
[status] => active
[sid] => 1
)
[1] => Array
(
[status] => expired
[sid] => 2
)
)
What I'd like to be able to do is type $arrayName["active"] and it return the SID code. I will be using this like a dictionary object of sorts. It's like I need to reindex the array so that it is the key/value pair that I need. I was just wondering if there was an easier way to do it.
You should convert your nested arrays into a single associative array. Something like this should take your example and turn it into an associative array:
$assoc_array = array();
foreach( $example_array as $values ) {
$assoc_array[$values["status"]] = $values["sid"];
}
You can then access the sid for a given status by using $assoc_array["expired"] (returns 2)
After seeing the others' solutions, I realize this might be bit of an overkill, but I'm still just gonna throw it out there:
$foo = array(
array('status' => 'active', 'sid' => 1),
array('status' => 'expired', 'sid' => 2),
);
// Get all the 'status' elements of each subarray
$keys = array_map(function($element) {
return $element['status'];
}, $foo);
// Get all the 'sid' elements of each subarray
$values = array_map(function($element) {
return $element['sid'];
}, $foo);
// Combine them into a single array, with keys from one and values from another
$bar = array_combine($keys, $values);
print_r($bar);
Which prints:
Array
(
[active] => 1
[expired] => 2
)
Manual pages:
array_map()
array_keys()
array_values()
array_combine()
Anonymous functions
You can use this function:
function findActive($my_array){
foreach($my_array as $array){
foreach($array as $val){
if($val['status']==='active'){
return $val['sid'];
}
}
}
return false;
}
access it via a loop or directly.
if($arrayName[0]['status'] == "active") {
echo $arrayName[0]['sid'];
}
If you want to check all the SIDs
foreach($arrayName as $item) {
if($item['status'] == "active") {
echo $item['sid'];
}
}
A more direct approach is just putting the loop in a function and return an array of all active session IDs
$sidArr = array();
foreach($yourArr as $val) {
if("active" == $val["status"]) {
array_push($sidArr, $val["sid"]);
}
}
reindex would be the best
$arrayName = array()
foreach ($data_array as $data)
$arrayName[$data['status']]=$data['sid'];
Or use a function
function get_sid($status)
{
global $data_array;
foreach ($data_array as $data) {
if ($data['status']==$status)
return $data['sid'];
}
return false;
}

Categories