Given a class with a multidimensional array as private property.
I'm trying to write a method setValue() which could change any value of this private array by passing:
some kind of path to a specific value (e.g. $path = ['lorem' => ['ipsum' => ['dolor' => ['sit' => null]]]];)
the new value
...
Class Tree {
private $tree = array(
'lorem' => array(
'ipsum' => array(
'dolor' => array(
'sit' => 'old value'
)
),
'amet' => array(
'consetetur' => array(
'sadipscing' => 'another value'
)
)
)
);
// setValue has to be a recursive function I guess
public function setValue($path, $value) {
// ???
}
public function getTree() {
return $this->tree;
}
}
Thanks to a comment of Gabriel on php.net I was able to find a working solution.
You can see my solution among the answers below.
What I actualy want to know: What are alternative approaches?
There are some recursive array built-in functions that may help you here:
<?php
class Tree
{
private $tree =
[
'foo' =>
[
'bar' => 'baz',
'qux' => 'quux'
]
];
public function replaceLeaf($key, $value) {
array_walk_recursive(
$this->tree,
function(&$item, $index) use ($key, $value) {
if($key === $index)
$item = $value;
}
);
}
public function replace($value) {
$this->tree = array_replace_recursive(
$this->tree,
$value
);
}
public function getTree()
{
return $this->tree;
}
}
$tree = new Tree;
$tree->replaceLeaf('bar', 'stool');
$tree->replace(
['foo' => ['qux' => 'quack']]
);
var_export($tree->getTree());
Output:
array (
'foo' =>
array (
'bar' => 'stool',
'qux' => 'quack',
),
)
However looking at your original question targeting the array is likely an easier syntax, especially if you are only changing one attribute at a time. You'd have to change the visibility of the array accordingly:
$tree->tree['foo']['qux'] = 'whatever';
With the decisive hint from gabriel.sobrinho#gmail.com at www.php.net
The method rebuildTree does the job. But it doesn't just change a value in $this->tree. It creates a new array.
Class Tree {
private $tree = array(
'lorem' => array(
'ipsum' => array(
'dolor' => array(
'sit' => 'old value'
)
),
'amet' => array(
'consetetur' => array(
'sadipscing' => 'another value'
)
)
)
);
public function setValue(array $path_and_value) {
$this->tree = $this->rebuildTree($this->tree, $path_and_value);
}
public function getTree() {
return $this->tree;
}
private function rebuildTree(array $arr, array $path_and_value) {
foreach($path_and_value AS $key => $value) {
if(
is_array($value)
&& isset($arr[$key])
) {
$arr[$key] = $this->rebuildTree($arr[$key], $value);
}
else {
$arr[$key] = $value;
}
}
return $arr;
}
}
$Tree = new Tree();
$path_and_value = array(
'lorem' => array(
'ipsum' => array(
'dolor' => array(
'sit' => 'new value'
)
)
)
);
$Tree->setValue($path_and_value);
print_r($Tree->getTree());
// ['lorem' => ['ipsum' => ['dolor' => ['sit' => 'new value']]]]
Related
My problem is that I want to create a getter which uses multidimensional array. I also use php version lower than 5.4 so I can't use array dereferencing.
class someClass{
protected someArray; // array( 'key1' => array( 'key2' => 'val'))
function __construct(){
// calling
$this -> getVar( array( 'key1' , 'key2' ) );
}
public function getVar( $keys ){
// return someArray multidimensional values
}
}
You mean something like this?
class someClass{
protected someArray;
function __construct(){
$this->someArray = array(
'key1' => array('name' => 'Akhil', 'loc' => 'india'),
'key2' => array('name' => 'Akash', 'loc' => 'usa'),
'key3' => array('name' => 'Dad', 'loc' => 'dubai'),
'key4' => array('name' => 'Mom', 'loc' => 'uae')
);
// calling
print_r( $this -> getVar( array( 'key1' , 'key2' ) ) );
}
public function getVar( $keys ){
// return someArray multidimensional values
$temp = array();
foreach($keys as $key)
{
$temp[] = $this->someArray[ $key ];
}
return $temp;
}
}
EDIT
As per the comment you provided below in my answer, here's how you could do that:
public function getVar( $main_key, $sub_key ){
// return someArray multidimensional values
return $this->someArray[ $main_key ][ $sub_key ];
}
I've been breaking my head over this one but can't seem to find a solution. I need a function that retrieves all parent keys of a given child key. So for example if I have an array like this:
array(
'apples' => array(
'bananas' => array(
'strawberries' => array(
'fruit' => array()
)
)
)
)
I would call the function like 'key_get_parents($key, $array)', and it would return an array with all the parent keys. In this example that would be array('apples', 'bananas', 'strawberries').
$array = array(
'apples' => array(
'bananas' => array(
'strawberries' => array(
'fruit' => array()
)
)
)
);
function key_get_parents($subject, $array)
{
foreach ($array as $key => $value)
{
if (is_array($value))
{
if (in_array($subject, array_keys($value)))
return array($key);
else
{
$chain = key_get_parents($subject, $value);
if (!is_null($chain))
return array_merge(array($key), $chain);
}
}
}
return null;
}
// Prints "Array ( [0] => apples [1] => bananas )"
print_r(key_get_parents('strawberries', $array));
I have a class that keeps data stores/access data by using words.separated.by.dots keys and it behaves like the following:
$object = new MyArray()
$object->setParam('user.name','marcelo');
$object->setParam('user.email','some#email.com');
$object->getParams();
/*
array(
'user' => array(
'name' => 'marcelo',
'email' => 'some#email.com'
)
);
*/
It is working, but the method unsetParam() was horribly implemented. That happened because i didn't know how to achieve that without eval() function. Although it is working, I found that it was a really challenging algorithm and that you might find fun trying to achieve that without eval().
class MyArray {
/**
* #param string $key
* #return Mura_Session_Abstract
*/
public function unsetParam($key)
{
$params = $this->getParams();
$tmp = $params;
$keys = explode('.', $key);
foreach ($keys as $key) {
if (!isset($tmp[$key])) {
return $this;
}
$tmp = $tmp[$key];
}
// bad code!
$eval = "unset(\$params['" . implode("']['", $keys) . "']);";
eval($eval);
$this->setParams($params);
return $this;
}
}
The test method:
public function testCanUnsetNestedParam()
{
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
'2' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
)
),
'2' => 'something'
);
$session = $this->newSession();
$session->setParams($params);
unset($params['1']['1']['1']);
$session->unsetParam('1.1.1');
$this->assertEquals($params, $session->getParams());
$this->assertEquals($params['1']['1']['2'], $session->getParam('1.1.2'));
}
Is this it?
<?php
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
'2' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
)
),
'2' => 'something'
);
function unsetParam( &$array, $paramString ) {
$cur =& $array;
$splitted = explode( ".", $paramString );
$len = count( $splitted ) - 1;
for( $i = 0; $i < $len; ++$i ) {
if( isset( $cur[ $splitted[ $i ] ] ) ) {
$cur =& $cur[ $splitted[ $i ] ];
}
else {
return false;
}
}
unset( $cur[ $splitted[$i] ] );
}
unsetParam( $params, "1.1.1");
print_r( $params );
/*
Array
(
[1] => Array
(
[1] => Array
(
[2] => Array
(
[1] => one
[2] => two
[3] => three
)
)
)
[2] => something
)
*/
You could make your code easier if you only split to a multidimension array in your getParams method:
class MyArray {
private $params = array();
public function setParam($key, $value) {
$this->params[$key] = $value;
}
/**
* #param string $key
* #return Mura_Session_Abstract
*/
public function unsetParam($key)
{
unset($this->params[$key]);
return $this;
}
public function getParams() {
$retval = array();
foreach ($this->params as $key => $value) {
$aux = &$retval;
foreach (explode(".", $key) as $subkey) {
if (!isset($aux[$subkey])) $aux[$subkey] = array();
$aux = &$aux[$subkey];
}
$aux = $value;
}
return $retval;
}
}
#gustavotkg and #Esailija have both offered some great ideas. Here is another simple, easy to understand, and short approach that avoids unset() altogether (which can get quirky in some cases).
This would, of course, be most useful when $params is limited to less than, say, 1k-10k of values (which starts to get a bit pricey in the CPU/memory dept):
<?php
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one-one',
'2' => 'one-two',
'3' => 'one-three',
),
'2' => array(
'1' => 'two-one',
'2' => 'two-two',
'3' => 'two-three',
),
)
),
'2' => 'something'
);
function filterParams($params, $refKey, $base = '') {
$newvals = array();
foreach($params as $k=>$v) {
$joinedKey = $base? $base . '.' . $k : $k;
if( $joinedKey != $refKey ) {
$newvals[$k] = is_array($v)? filterParams($v, $refKey, $joinedKey) : $v;
}
}
return $newvals;
}
var_dump(filterParams($params, '1.1.2'));
I need to remove empty entries on multilevel arrays. For now I can remove entries with empty sub-arrays, but not empty arrays... confused, so do I... I think the code will help to explain better...
<?php
/**
*
* This function remove empty entries on arrays
* #param array $array
*/
function removeEmptysFromArray($array) {
$filtered = array_filter($array, 'removeEmptyItems');
return $filtered;
}
/**
*
* This is a Callback function to use in array_filter()
* #param array $item
*/
function removeEmptyItems($item) {
if (is_array($item)) {
return array_filter($item, 'removeEmptyItems');
}
if (!empty($item)) {
return true;
}
}
$raw = array(
'firstname' => 'Foo',
'lastname' => 'Bar',
'nickname' => '',
'birthdate' => array(
'day' => '',
'month' => '',
'year' => '',
),
'likes' => array(
'cars' => array('Subaru Impreza WRX STi', 'Mitsubishi Evo', 'Nissan GTR'),
'bikes' => array(),
),
);
print_r(removeEmptysFromArray($raw));
?>
Ok, this code will remove "nickname", "birthdate" but is not removing "bikes" that have an empty array.
My question is... How to remove the "bikes" entry?
Best Regards,
Sorry for my english...
Try this code:
<?php
function array_remove_empty($haystack)
{
foreach ($haystack as $key => $value) {
if (is_array($value)) {
$haystack[$key] = array_remove_empty($haystack[$key]);
}
if (empty($haystack[$key])) {
unset($haystack[$key]);
}
}
return $haystack;
}
$raw = array(
'firstname' => 'Foo',
'lastname' => 'Bar',
'nickname' => '',
'birthdate' => array(
'day' => '',
'month' => '',
'year' => '',
),
'likes' => array(
'cars' => array('Subaru Impreza WRX STi', 'Mitsubishi Evo', 'Nissan GTR'),
'bikes' => array(),
),
);
print_r(array_remove_empty($raw));
array_filter(explode('/', '/home/teste sdsd/ /'), 'trim');
//Result
[
1 => "home",
2 => "teste sdsd",
]
//-----------
array_filter(explode('/', '/home/teste sdsd/ /'), 'strlen')
//Result
[
1 => "home",
2 => "teste sdsd",
3 => " ",
]
I think this should solve your problem.
$retArray =array_filter($array, arrayFilter);
function arrayFilter($array) {
if(!empty($array)) {
return array_filter($array);
}
}
Here is my solution, it will remove exactly specified list of empty values recursively:
/**
* Remove elements from array by exact values list recursively
*
* #param array $haystack
* #param array $values
*
* #return array
*/
function array_remove_by_values(array $haystack, array $values)
{
foreach ($haystack as $key => $value) {
if (is_array($value)) {
$haystack[$key] = array_remove_by_values($haystack[$key], $values);
}
if (in_array($haystack[$key], $values, true)) {
unset($haystack[$key]);
}
}
return $haystack;
}
You can use it like this:
$clean = array_remove_by_values($raw, ['', null, []]);
Note, it removes empty sub arrays if you pass [] as one of values.
Recursively clear multidimensional array of empty'ish items:
final class ArrayCleaner
{
public static function clean(array $value): array
{
foreach ($value as $k => $v) {
if (\is_array($v)) {
$value[$k] = self::clean($v);
if (0 == \count($value[$k])) {
unset($value[$k]);
}
} elseif (empty($v)) {
unset($value[$k]);
}
}
return $value;
}
}
Unit test:
final class ArrayCleanerTest
{
public function testItCleans(): void
{
$input = [
'empty_string_to_remove' => '',
'empty_int_to_remove' => 0,
'empty_string_number_to_remove' => '0',
'value_to_keep' => 5,
'empty_array_to_remove' => [],
'empty_array_of_empty_arrays_to_remove' => [
'one' => [],
'two' => [],
'three' => [false, null, '0'],
],
'array_to_keep_1' => [
'value' => 1,
],
'array_to_keep_2' => [
'empty_to_remove' => [],
'array_to_keep' => ['yes'],
],
];
$expected = [
'value_to_keep' => 5,
'array_to_keep_1' => [
'value' => 1,
],
'array_to_keep_2' => [
'array_to_keep' => ['yes'],
],
];
$this->assertEquals($expected, ArrayCleaner::clean($input));
}
}
Working proof of concept at 3v4l.org
My function:
function removeEmptyItems($item)
{
if (is_array($item)) {
$item = array_filter($item, 'removeEmptyItems');
}
return !empty($item);
}
$nonEmpty = array_filter($raw, 'removeEmptyItems');
If you want array_filter to work recursively, you'll need to make sure that the subsequent calls may edit the deeper nested items of the array. Short: You'll need to pass it by reference:
function removeEmptyItems(&$item) {
if (is_array($item) && $item) {
$item = array_filter(&$item, 'removeEmptyItems');
}
return !!$item;
}
I have the following array that I need to recursively loop through and remove any child arrays that have the key 'fields'. I have tried array filter but I am having trouble getting any of it to work.
$myarray = array(
'Item' => array(
'fields' => array('id', 'name'),
'Part' => array(
'fields' => array('part_number', 'part_name')
)
),
'Owner' => array(
'fields' => array('id', 'name', 'active'),
'Company' => array(
'fields' => array('id', 'name',),
'Locations' => array(
'fields' => array('id', 'name', 'address', 'zip'),
'State' => array(
'fields' => array('id', 'name')
)
)
)
)
);
This is how I need it the result to look like:
$myarray = array(
'Item' => array(
'Part' => array(
)
),
'Owner' => array(
'Company' => array(
'Locations' => array(
'State' => array(
)
)
)
)
);
If you want to operate recursively, you need to pass the array as a reference, otherwise you do a lot of unnecessarily copying:
function recursive_unset(&$array, $unwanted_key) {
unset($array[$unwanted_key]);
foreach ($array as &$value) {
if (is_array($value)) {
recursive_unset($value, $unwanted_key);
}
}
}
you want array_walk
function remove_key(&$a) {
if(is_array($a)) {
unset($a['fields']);
array_walk($a, __FUNCTION__);
}
}
remove_key($myarray);
My suggestion:
function removeKey(&$array, $key)
{
if (is_array($array))
{
if (isset($array[$key]))
{
unset($array[$key]);
}
if (count($array) > 0)
{
foreach ($array as $k => $arr)
{
removeKey($array[$k], $key);
}
}
}
}
removeKey($myarray, 'Part');
function recursive_unset(&$array, $unwanted_key) {
if (!is_array($array) || empty($unwanted_key))
return false;
unset($array[$unwanted_key]);
foreach ($array as &$value) {
if (is_array($value)) {
recursive_unset($value, $unwanted_key);
}
}
}
function sanitize($arr) {
if (is_array($arr)) {
$out = array();
foreach ($arr as $key => $val) {
if ($key != 'fields') {
$out[$key] = sanitize($val);
}
}
} else {
return $arr;
}
return $out;
}
$myarray = sanitize($myarray);
Result:
array (
'Item' =>
array (
'Part' =>
array (
),
),
'Owner' =>
array (
'Company' =>
array (
'Locations' =>
array (
'State' =>
array (
),
),
),
),
)
function removeRecursive($haystack,$needle){
if(is_array($haystack)) {
unset($haystack[$needle]);
foreach ($haystack as $k=>$value) {
$haystack[$k] = removeRecursive($value,$needle);
}
}
return $haystack;
}
$new = removeRecursive($old,'key');
Code:
$sweet = array('a' => 'apple', 'b' => 'banana');
$fruits = array('sweet' => $sweet, 'sour' => $sweet);
function recursive_array_except(&$array, $except)
{
foreach($array as $key => $value){
if(in_array($key, $except, true)){
unset($array[$key]);
}else{
if(is_array($value)){
recursive_array_except($array[$key], $except);
}
}
}
return;
}
recursive_array_except($fruits, array('a'));
print_r($fruits);
Input:
Array
(
[sweet] => Array
(
[a] => apple
[b] => banana
)
[sour] => Array
(
[a] => apple
[b] => banana
)
)
Output:
Array
(
[sweet] => Array
(
[b] => banana
)
[sour] => Array
(
[b] => banana
)
)
I come up with a simple function that you can use to delete multiple array element based on multiple keys.
Detail example here.
Just a little change in code.
function removeRecursive($inputArray,$delKey){
if(is_array($inputArray)){
$moreKey = explode(",",$delKey);
foreach($moreKey as $nKey){
unset($inputArray[$nKey]);
foreach($inputArray as $k=>$value) {
$inputArray[$k] = removeRecursive($value,$nKey);
}
}
}
return $inputArray;
}
$inputNew = removeRecursive($input,'keyOne,keyTwo');
print"<pre>";
print_r($inputNew);
print"</pre>";
Give this function a shot. It will remove the keys with 'fields' and leave the rest of the array.
function unsetFields($myarray) {
if (isset($myarray['fields']))
unset($myarray['fields']);
foreach ($myarray as $key => $value)
$myarray[$key] = unsetFields($value);
return $myarray;
}
Recursively walk the array (by reference) and unset the relevant keys.
clear_fields($myarray);
print_r($myarray);
function clear_fields(&$parent) {
unset($parent['fields']);
foreach ($parent as $k => &$v) {
if (is_array($v)) {
clear_fields($v);
}
}
}
I needed to have a little more granularity in unsetting arrays and I came up with this - with the evil eval and other dirty tricks.
$post = array(); //some huge array
function array_unset(&$arr,$path){
$str = 'unset($arr[\''.implode('\'][\'',explode('/', $path)).'\']);';
eval($str);
}
$junk = array();
$junk[] = 'property_meta/_edit_lock';
$junk[] = 'property_terms/post_tag';
$junk[] = 'property_terms/property-type/0/term_id';
foreach($junk as $path){
array_unset($post,$path);
}
// unset($arr['property_meta']['_edit_lock']);
// unset($arr['property_terms']['post_tag']);
// unset($arr['property_terms']['property-type']['0']['term_id']);