Using a path to an array item - php

Is there a way to reference an item within a multidimensional array by using a path or an array of path elements? EG.
$multi = array
(
'array_1' => array
(
'array_2' => array
(
'option_1' => 'value_1',
'option_2' => 'value_2',
)
)
);
$path = array('level_1', 'level_2', 'option_1');
$result = $multi[$path];
And have $result = 'value_1'?
The reason being, I have a recursive function for searching thru $multi and finding the key I need, and returning the $path. I know i can hard code in the path from my own code but i'm trying to make this reusable so that i can edit the $multi and the function will still work.

There's nothing built into PHP to do this but you can write a function for it, using a moving reference:
/**
* #param string $path path in the form 'item_1.item_2.[...].item_n'
* #param array $array original array
*/
function &get_from_array($path, &$array)
{
$current =& $array;
foreach(explode('.', $path) as $key) {
$current =& $current[$key];
}
return $current;
}
Example:
// get element:
$result = get_from_array('level_1.level_2.option_1', $multi);
echo $result; // --> value_1
$result = 'changed option';
echo $multi['level_1']['level_2']['option_1']; // --> changed_option
I wrote it to convert names from configuration files to arrays, if you want to pass the path itself as an array like in your example, just leave out the explode.

Related

PHP - iterate through different value types

I have object of class $values like:
Array
(
[0] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => CONNECT_NETWORKS_ON_SIGN_UP
[value:App\ValueObject\Features:private] => 1
)
[1] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => SHOW_BILLING
[value:App\ValueObject\Features:private] => 1
)
[2] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => FEATURE_FLAGS
[value:App\ValueObject\Features:private] => 'activity'
)
)
All array keys are returning boolean type value expect one, which returns string value.
My result with the code:
$arrays = array_map(
function($value) { return [strtolower((string) $value->getFeature())]; },
iterator_to_array($values)
);
return array_merge(...$arrays);
returns list of feature names like:
"features": [
"connect_networks_on_sign_up",
"show_billing",
"feature_flags"
]
What I want to edit is that for the last one we write its value NOT feature name ($value->getValue())
I am assuming that using in_array() PHP function would be the best approach here but I can't find a way to use it within my current method.
Tried with foreach() loop but nothing happens, like it's something wrong:
$features = [];
foreach ($values as $value)
{
$setParam = $value->getFeature();
if ($value == 'FEATURE_FLAGS') {
$setParam = $value->getValue();
}
$features[] = strtolower((string) $setParam);
}
return $features;
Can someone help?
Thanks
You should probably operate on the feature code FEATURE_FLAGS, rather than assuming that the last feature in the array always contains the flags. Using your existing code, that could be as simple as:
$arrays = array_map(
function($value)
{
/*
* If the current Features object has the feature code FEATURE_FLAGS,
* return the value itself, otherwise return the feature code in lowercase
*/
return ($value->getFeature() == 'FEATURE_FLAGS') ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
If you want to define an array of feature codes that you need to treat this way, you can define it internally in the callback, but it is probably a better idea to define it externally. You can then pass it into the callback with use
/*
* Define an array of feature codes that we want to return
* values for
*/
$valueCaptureFeatures = ['FEATURE_FLAGS'];
$arrays = array_map(
function($value) use ($valueCaptureFeatures) // <-- Put our $valueCaptureFeatures in the scope of the callback
{
/*
* If the current Features object has a feature code in the $valueCaptureFeatures array,
* return the value itself, otherwise return the feature code in lowercase
*/
return (in_array($value->getFeature(), $valueCaptureFeatures)) ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
Working example:
// Mock the Features class
class Features
{
private $feature;
private $value;
public function __construct($feature, $value)
{
$this->feature = $feature;
$this->value = $value;
}
public function getFeature()
{
return $this->feature;
}
public function setFeature($feature): void
{
$this->feature = $feature;
}
public function getValue()
{
return $this->value;
}
public function setValue($value): void
{
$this->value = $value;
}
}
// Mock an iterator with test Feature instances
$values = new ArrayIterator( [
new Features('CONNECT_NETWORKS_ON_SIGN_UP', 1),
new Features('SHOW_BILLING', 1),
new Features('FEATURE_FLAGS', 'activity')
]);
/*
* Define an array of feature codes that we want to return
* values for
*/
$valueCaptureFeatures = ['FEATURE_FLAGS'];
$arrays = array_map(
function($value) use ($valueCaptureFeatures) // <-- Put our $valueCaptureFeatures in the scope of the callback
{
/*
* If the current Features object has a feature code in the $valueCaptureFeatures array,
* return the value itself, otherwise return the feature code in lowercase
*/
return (in_array($value->getFeature(), $valueCaptureFeatures)) ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
$output = array_merge(...$arrays);
$expectedResult = [
'connect_networks_on_sign_up',
'show_billing',
'activity'
];
assert($output == $expectedResult, 'Result should match expectations');
print_r($output);

Array format adding extra layer using '=>'

I'm struggling to find a way to convert my object to the correct format.
I want to replace a function that we currently use on generating detailed array, as you can see below everything is static.
private function departmentArray($content=[])
{
return [ static::$A_DEPT_ID => $content
, static::$O_DEPT_ID => $content
];
}
A sample result when that runs is this
{"3":{"complete":0,"incomplete":0},"5":{"complete":0,"incomplete":0}}
I converted the method
private function departmentArray($content=[])
{
$depts = d::getAllMainDepartment();
$dept_array = [];
foreach ($depts as $dept) {
$dept_array[] = array($dept->id => $content);
}
return $dept_array;
}
The resulting format looks like this
[{"3":{"complete":0,"incomplete":0}},{"5":{"complete":0,"incomplete":0}}]
How can I maintain the same format on the first version of code?
You don't push into an associative array, you use the new key as an index.
$dept_array[$dept->id] = $content;

PHP: array search when array doesn't start on zero

I'm trying to find a key in an array that doesn't start with zero.
This is my not so elegant solution:
private $imagetypes = [
1 => [
'name' => 'banner_big',
'path' => 'campaigns/big/'
],
2 => [
'name' => 'banner_small',
'path' => 'campaigns/small/'
],
// ...
If i use $key = array_search('banner_big', array_column($this->imagetypes, 'name')); the result is 0
I came up with this solution but I feel like I needlessly complicated the code:
/**
* #param string $name
* #return int
*/
public function getImagetypeFromName($name)
{
$keys = array_keys($this->imagetypes);
$key = array_search($name, array_column($this->imagetypes, 'name'));
if ($key !== false && array_key_exists($key, $keys)) {
return $keys[$key];
}
return -1;
}
Is there a better solution then this.
I can't change the keys in.
Just save indexes
$key = array_search('banner_big',
array_combine(array_keys($imagetypes),
array_column($imagetypes, 'name')));
demo on eval.in
The problem is array_column will return a new array (without the existing indexes)
So in your example.
$key = array_search('banner_big', array_column($this->imagetypes, 'name'));
var_dump($key);
//$key is 0 as 0 is the key for the first element in the array returned by array_column.
You can mitigate against this by creating a new array with the existing keys.
That's because array_column() generates another array (starting at
index zero), as you may have imagined. An idea to solve this would be to
transform the array with array_map(), reducing it to key and image
name (which is what you're searching for). The keys will be the same,
and this can be achieved with a simple callback:
function($e) {
return $e['name'];
}
So, a full implementation for your case:
public function
getImagetypeFromName($name)
{
$key = array_search($name, array_map(function($e) {
return $e['name'];
}, $this->imagetypes));
return $key ?: -1;
}

PHP turn string into php post indexes

I am trying to figure out the best way to use dot notation when passing in a key or set of keys into a function and getting that post value.
Example
shipping.first_name
What it looks like in actual $_POST array:
$_POST[shipping][first_name] = 'some value'
I would like to be able to pass in (as a parameter) the string, and have the function return the post value.
function get_post($str = NULL){
return $_POST[$key1][$key1]..etc.
}
Current attempt (working as intended, but need to put into $_POST):
From: SO Question
function assignArrayByPath(&$arr, $path) {
$keys = explode('.', $path);
while ($key = array_shift($keys)) {
$arr = &$arr[$key];
}
}
$output = array();
assignArrayByPath($output, $str);
This produces an array of:
Array ( [shipping] => Array ( [first_name] => ) )
I would like then to do something like this:
return isset($_POST.$output) ? true : false;
So how do I take that array created from the period separated string and check if it exists in POST?
I think this might be a duplicate, but I am not positive. I apologize in advance if it is. Any help is much appreciated.
See Laravel array_set implement http://laravel.com/api/source-function-array_set.html#319
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* #param array $array
* #param string $key
* #param mixed $value
* #return array
*/
function array_set(&$array, $key, $value)
{
if (is_null($key)) return $array = $value;
$keys = explode('.', $key);
while (count($keys) > 1)
{
$key = array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if ( ! isset($array[$key]) || ! is_array($array[$key]))
{
$array[$key] = array();
}
$array =& $array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
Check exists you can see array_get http://laravel.com/api/source-function-array_get.html#224
/**
* Get an item from an array using "dot" notation.
*
* #param array $array
* #param string $key
* #param mixed $default
* #return mixed
*/
function array_get($array, $key, $default = null)
{
if (is_null($key)) return $array;
if (isset($array[$key])) return $array[$key];
foreach (explode('.', $key) as $segment)
{
if ( ! is_array($array) || ! array_key_exists($segment, $array))
{
return value($default);
}
$array = $array[$segment];
}
return $array;
}

Property is null in method 2 after being set in method 1

hoping you can help me out with this question!
Index.php
include_once 'files.class.php';
$file_object = new FileObject('resources');
$file_object->ReturnCurrentDirectoryList();
files.class.php
class FileObject{
public $directory_list;
function __construct($current_directory_in){
$this->directory_list = $this->BuildCurrentDirectoryList($current_directory_in);
}
function BuildCurrentDirectoryList($current_directory_in){
$i = 0;
$iterator = new DirectoryIterator($current_directory_in);
foreach ($iterator as $fileinfo){
if ($fileinfo->isDir()){
$this->directory_list[$i]['pathname'] = $fileinfo->getPathname();
}elseif($fileinfo->isFile()){
$this->directory_list[$i]['filename'] = $fileinfo->getFilename();
}
$i++;
}
}
function ReturnCurrentDirectoryList(){
var_dump($this->directory_list);
}
}
At the end of all this, what is returned is
null
but what should be returned is
array 0 => array 'pathname' => string 'resources\.', 1 => array 'pathname' => string 'resources\..', 2 => array 'pathname' => string 'resources\Images'
I'm somewhat new to classes/methods..
This is wrong:
$this->directory_list = $this->BuildCurrentDirectoryList($current_directory_in);
You assign to $this->directory_list but BuildCurrentDirectoryList does not return anything. The function have side-effects only, no return value.
Remove the assignment so the constructor looks like this and you should be good to go:
$this->directory_list = array(); //I like to initialise arrays to the empty array
$this->BuildCurrentDirectoryList($current_directory_in);
In your constructor, you are assigning directory_list to the return of BuildCurrentDirectoryList, but you are not returning nothing in BuildCurrentDirectoryList, you are assigning directory_list directly in that method. At the end, BuildCurrentDirectoryList returns NULL. So, either return the directory_list, or else just don't assign it like this:
function __construct($current_directory_in){
$this->BuildCurrentDirectoryList($current_directory_in);
}

Categories