Accessing deeper array keys using function arguments - php

I am trying to create a function (unless one already exists?) which is supposed to accept an "unlimited" amount of arguments (using func_get_args()) to find the value of an array key. Regardless of how deep within the array it is to be found.
Say if I would use $registry->getSetting( 'template', 'default' ); I am supposed to get the value of $this->properties['settings']['template']['default'] or if I would you $registry->getSetting( 'users', 1, 'name', 'first' ); I would expect it to return the value of $this->properties['users'][1]['name']['first'] (just a second example with a couple of extra arguments).
Now, to do something like this, I could count the amount of arguments passed using func_num_args() and then do a switch with different cases. Although, this would limit it to a certain amount of keys.
So I am asking you if there is a way of doing this, to allow an "unlimited" amount rather than a fixed amount of arguments to access a deeper key of an array.
<?PHP
class Registry
{
// Values are actually fetched from a config file, but for easier understanding
private $properties = array(
'settings' => array(
'template' => array(
'default' => 'default',
'caching' => TRUE
)
)
);
public function getSetting( )
{
// do something like
// return $this->properties['settings'][func_get_args( )];
}
}
?>
Any help whatsoever is highly appreciated.
Thank you!

<?PHP
class Registry
{
// Values are actually fetched from a config file, but for easier understanding
private $properties = array(
'settings' => array(
'template' => array(
'default' => 'default',
'caching' => TRUE
)
)
);
public function getSetting()
{
$result = $this->properties;
foreach (func_get_args() as $arg) {
if (isset($result[$arg])) {
$result = $result[$arg];
} else {
return;
}
}
return $result;
}
}
$r = new Registry();
var_dump($r->getSetting('settings', 'template', 'default'));
?>

Hmm, I'm not sure if this is what you want, but this is straight from the PHP manual:
Variable-length argument lists
PHP 4 and above has support for
variable-length argument lists in
user-defined functions. This is really
quite easy, using the func_num_args(),
func_get_arg(), and func_get_args()
functions.
No special syntax is required, and
argument lists may still be explicitly
provided with function definitions and
will behave as normal.
I would just loop over the arguments and output them into a single array (I might be misinterpreting your question):
$output = array();
$arguments = func_get_args();
foreach ($argument as $arg)
{
$output[$arg] = $this->properties['settings'][$arg];
}
return $output;
Good luck?

I don't know if there is a PHP function which can do this, but my way is working fine too:
class Registry
{
// Values are actually fetched from a config file, but for easier understanding
private $properties = array(
'settings' => array(
'template' => array(
'default' => 'default',
'caching' => TRUE
)
)
);
public function getSetting( $array = array() )
{
$return = $this->properties['settings'];
foreach( $array as $a )
{
$return = $return[$a];
}
return $return;
}
}
then use:
$Registry = new Registry();
$template = $Registry->getSetting(array( 'template' ) );
$template_caching = $Registry->getSetting(array( 'template', 'caching' ) );

Use dot as separator for nested arrays:
Registry::getAttribute('first.second.1.value'); // will return $ar['first']['second'][1]['value']
In getAttribute explode param by . and map it into registry values.

Something like this (untested):
function getSetting ( /* .. */ )
{
$args = func_get_args();
array_unshift( $args, $this->properties );
return getSettingInternal( $args );
}
function getSettingInternal ( $args )
{
$arr = $args[0];
if ( count( $args ) > 1 )
{
$key = array_slice( $args, 1, 1 );
if ( !is_array( $arr ) )
return $arr;
$arr = array_key_exists( $arr, $key ) ? $arr[$key] : $arr['default'];
$args[0] = $arr;
return getSettingInternal( $args );
}
else
return $arr;
}

Related

PHP sorting dependency array list - Topological Sorting

I have an array of files with dependencies. I need them sorted so all dependent files are indexed after their dependency. I've made a lot of attempts with forEach loops, while loops, but once a dependency is moved, the loops don't account for previous iterations and indexed elements wind up out of order.
I have a simplified version of the data I am working with:
$dependencies = array(
array(
'handle' => 'jquery',
'requires' => array(
'jquery-core',
'jquery-migrate'
)
),
array(
'handle' => 'jquery-migrate',
'requires' => array()
),
array(
'handle' => 'common',
'requires' => array(
'utils',
'jquery',
'jquery-core',
'jquery-migrate',
'jquery-effects-core',
'backbone'
)
),
array(
'handle' => 'jquery-effects-core',
'requires' => array(
'jquery',
'jquery-core',
'jquery-migrate'
)
),
array(
'handle' => 'backbone',
'requires' => array(
'underscore',
'jquery'
)
),
array(
'handle' => 'underscore',
'requires' => array()
),
array(
'handle' => 'utils',
'requires' => array()
),
array(
'handle' => 'jquery-core',
'requires' => array()
)
);
If [handle] appears in an elements [requires] array, that element needs to be moved ahead of the indicated [requires] element, all while maintaining any other dependencies previously accounted for.
function moveEle(&$array, $a, $b){
$out = array_splice($array, $a, 1);
array_splice($array, $b, 0, $out);
}
foreach($dependencies as $i=>$dependency){
if( count($dependency['requires'])>0 ){
$itr = count($dependency['requires']);
echo $dependency['handle']."<br/>";
while($itr > 0){
// loop through current files required files
foreach( $dependency['requires'] as $k=>$dep ){
// loop through dependencies array again to find required file handle
echo "-- " . $dep . "<br/>";
foreach($dependencies as $j=>$jDep){
// $j = index in dependencies array of required file, $i = index in dependencies array of dependent file.
if( $dep === $jDep['handle'] && $j > $i ){
echo "found " . $jDep['handle'] . "# " . $j."<br/>";
moveEle(&$dependencies, $j, $i );
}
}
$itr--;
}
}
}
}
I'm taking it there is probably some recursive way to do this that is a little beyond my scope of skill at this point. Any help would be greatly appreciated.
I did find this solution:
PHP Order array based on elements dependency
which does work, however, once the array gets to 150 files (the actual data size), it times out or takes an incredibly long time. I'd like a more efficient solution, if one exists.
Well, I got the desired results with this:
/*
*
* #description return nested array with all dependencies & sub dependencies
*
**/
function getWithDependencies($dependency, $collection){
$helper = new Insight_WP_Scripts_Helpers();
$set = array(
'handle' => $dependency,
'dependencies' => array()
);
foreach($collection as $index=>$data){
if( $data['handle'] === $set['handle'] ){
// echo "<h4>".$data['handle']."</h4>";
if(count($helper->getDependencies($set, $collection))>0){
$dependencies = $helper->getDependencies($set['handle'], $collection);
foreach($dependencies as $i=>$dependency){
// echo $dependency . "<br/>";
$set['dependencies'][$i] = array(
'handle' => $dependency,
'dependencies' => array()
);
if( count($helper->getDependencies($dependency, $collection)) > 0 ){
foreach(getWithDependencies($dependency, $collection) as $k=>$dep){
$set['dependencies'][$i]['dependencies'] = $dep;
}
}
}
}
}
}
return $set;
}
/*
*
* #description recursively sorts dependencies to depth returned by getWithDependencies()
*
**/
function sortDependency($data,&$collection){
$helper = new Insight_WP_Scripts_Helpers();
foreach($collection as $index=>$inst){
if( $inst['handle'] === $data['handle'] && count($data['dependencies'])>0){
// iterate over each dependency
foreach( $inst['dependencies'] as $i=>$dependency ){
// iterate over the collection to find dependencies position for comparison against dependent file.
foreach( $collection as $k=>$kdep ){
if($kdep['handle'] === $dependency['handle'] && $index < $k){
$helper->moveArrayElement($collection, $k, $index);
// call recursively to search full depth
if( count($dependency['dependencies']) > 0 ){
sortDependency($dependency,$collection);
}
}
}
}
}
}
}
foreach( $dependencies as $index=>$data ){
$fullDependencies = array();
foreach($dependencies as $i=>$dependency){
$fullDependencies[] = getWithDependencies($dependency['handle'], $dependencies);
}
}
$_fullDependencies = $fullDependencies;
foreach( $fullDependencies as $index=>$data ){
sortDependency($data,$_fullDependencies);
}
$fullDependencies = $_fullDependencies;
return $fullDependencies;

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.

Check for key-value pair in multidimensional array

I have the follow array:
Array
(
[0] => Array
(
[type] => foo
)
[1] => Array
(
[type] => bar
)
[2] => Array
(
[type] => bar
)
)
and need to know if exists one or more type which value is bar, without do this:
foreach ($arrayOfTypes as $type) {
if ($type['type'] == 'bar')
{
// Stuff here
}
}
(Only for learning purposes)
I'd go with array_filter();
$filteredArray = array_filter($stuff, function($item){
return $item['type'] == 'bar';
});
if( count($filteredArray) > 0 ){
echo 'There was a bar item in array.';
}else{
echo 'No luck sorry!';
}
Use in_array: http://se.php.net/manual/en/function.in-array.php
Combine it with array_map to flatten what you have. Like so:
$new_array = array_map( function( $arr ) {
return $arr['type'];
}, $array );
in_array( 'bar', $new_array );
Honestly a foreach loop or moonwave99's array-filter answer is probably going to be your best bet, but if what you're looking is the shortest code possible and creativity that will make most programmers gag, you could try serialize-ing the array and using string-searching functions:
serialize(array(
0=>array(
'type' => 'foo'
),
1=>array(
'type' => 'bar'
),
2=>array(
'type' => 'bar'
)
))
becomes
a:3:{i:0;a:1:{s:4:"type";s:3:"foo";}i:1;a:1:{s:4:"type";s:3:"bar";}i:2;a:1:{s:4:"type";s:3:"bar";}}
So you can now run a strpos() or preg_match() function to find them. So your whole function would look like:
$exists = strpos('{s:4:"type";s:3:"bar";}',serialize($arrayOfTypes)); //returns number or false
It's short, it's snappy, and get's the job done for simple string keypairs.
This is basically the same as moonwave99's solution but slightly more useful as it's in a function that you can feed a key/value pair to as well, so it can be used to search for any key/value combo:
function isKVInArray($k, $v, $array) {
$filtered = array_filter($array, function($item) use($k,$v) {
return $item[$k] == $v;
});
if(count($filtered)>=1) return true;
else return false;
}
$k = 'live';
$v = 'y';
$my_array = array(
0=>array(
'live'=>'n',
'something_else'=>'a'
),
1=>array(
'live'=>'y',
'something_else'=>'b'
),
2=>array(
'live'=>'n',
'something_else'=>'c'
)
);
if(isKVInArray($k, $v, $my_array)) echo "$k=>$v was found in the array.";
else echo 'No luck sorry!';
there's another simple way which is useful when you need to count all the different values
foreach ($arrayOfTypes as $type) $cnt[$type['type']]++;
To get the number of 'bar' (or to get the count of another value):
echo($cnt['bar']);

use strings to access (potentially large) multidimensional arrays

I am having trouble figuring out a way to simply parse a string input and find the correct location within a multidimensional array.
I am hoping for one or two lines to do this, as the solutions I have seen rely on long (10-20 line) loops.
Given the following code (note that the nesting could, in theory, be of any arbitrary depth):
function get($string)
{
$vars = array(
'one' => array(
'one-one' => "hello",
'one-two' => "goodbye"
),
'two' => array(
'two-one' => "foo",
'two-two' => "bar"
)
);
return $vars[$string]; //this syntax isn't required, just here to give an idea
}
get("two['two-two']"); //desired output: "bar". Actual output: null
Is there a simple use of built-in functions or something else easy that would recreate my desired output?
Considering $vars being your variables you would like to get one['one-one'] or two['two-two']['more'] from (Demo):
$vars = function($str) use ($vars)
{
$c = function($v, $w) {return $w ? $v[$w] : $v;};
return array_reduce(preg_split('~\[\'|\'\]~', $str), $c, $vars);
};
echo $vars("one['one-one']"); # hello
echo $vars("two['two-two']['more']"); # tea-time!
This is lexing the string into key tokens and then traverse the $vars array on the keyed values while the $vars array has been turned into a function.
Older Stuff:
Overload the array with a function that just eval's:
$vars = array(
'one' => array(
'one-one' => "hello",
'one-two' => "goodbye"
),
'two' => array(
'two-one' => "foo",
'two-two' => "bar"
)
);
$vars = function($str) use ($vars)
{
return eval('return $vars'.$str.';');
};
echo $vars("['one']['one-two']"); # goodbye
If you're not a fan of eval, change the implementation:
$vars = function($str) use ($vars)
{
$r = preg_match_all('~\[\'([a-z-]+)\']~', $str, $keys);
$var = $vars;
foreach($keys[1] as $key)
$var = $var[$key];
return $var;
};
echo $vars("['one']['one-two']"); # goodbye
How about
$vars = array(
'one' => array(
'one-one' => "hello",
'one-two' => "goodbye"
),
'two' => array(
'two-one' => "foo",
'two-two' => "bar"
)
);
function get( $string, $vars )
{
$keys = explode( '][', substr( $string, 1, -1 ) );
foreach( $keys as $key ) {
$vars = $vars[$key];
}
return $vars;
}
echo get( '[two][two-one]', $vars );
For one, you've not got a $var in your get() function. $var was defined outside the function, and PHP scoping rules do not make "higher" vars visible in lower scopes unless explictly made global in the lower scope:
function get($string) {
global $vars;
eval('$x = $vars' . $string);
return $x;
}
get("['two']['two-two']");
might work, but this isn't tested, and using eval is almost always a very bad idea.
Kohana has a nice Config class which alows something like this:
echo Config::get("two.two-two");
You can check it out here: http://kohanaframework.org/3.1/guide/api/Config

PHP: Expose 'get' and 'set' for object with nested associative arrays

I have a class which stores values with a multi-level associative array:
I need to add a way to access and modify nested values. Here is a working solution for my problem, but it is rather slow. Is there a better way of doing this?
Note: The use of get / set functions is not mandatory, but there needs to be an efficient way to define a default value.
class Demo {
protected $_values = array();
function __construct(array $values) {
$this->_values = $values;
}
public function get($name, $default = null) {
$token = strtok($name, '.#');
$node = $this->_values;
while ($token !== false) {
if (!isset($node[$token]))
return $default;
$node = $node[$token];
$token = strtok('.#');
}
return $node;
}
public function set($name, $value) {
$next_token = strtok($name, '.#');
$node = &$this->_values;
while ($next_token !== false) {
$token = $next_token;
$next_token = strtok('.#');
if ($next_token === false) {
$node[ $token ] = $value;
break;
}
else if (!isset($node[ $token ]))
$node[ $token ] = array();
$node = &$node[ $token ];
}
unset($node);
}
}
Which would be used as follows:
$test = new Demo(array(
'simple' => 27,
'general' => array(
0 => array(
'something' => 'Hello World!',
'message' => 'Another message',
'special' => array(
'number' => 27
)
),
1 => array(
'something' => 'Hello World! #2',
'message' => 'Another message #2'
),
)
));
$simple = $test->get('simple'); // === 27
$general_0_something = $test->get('general#0.something'); // === 'Hello World!'
$general_0_special_number = $test->get('general#0.special.number'); === 27
Note: 'general.0.something' is the same as 'general#0.something', the alternative punctuation is for the purpose of clarity.
Well, the question was interesting enough that I couldn't resist tinkering a bit more. :-)
So, here are my conclusions. Your implementation is probably the most straightforward and clear. And it's working, so I wouldn't really bother about searching for another solution. In fact, how much calls are you gonna get in the end? Is the difference in performance worth the trouble (I mean between "super ultra blazingly fast" and "almost half as fast")?
Put aside though, if performance is really an issue (getting thousands of calls), then there's a way to reduce the execution time if you repetitively lookup the array.
In your version the greatest burden falls on string operations in your get function. Everything that touches string manipulation is doomed to fail in this context. And that was indeed the case with all my initial attempts at solving this problem.
It's hard not to touch strings if we want such a syntax, but we can at least limit how much string operations we do.
If you create a hash map (hash table) so that you can flatten your multidimensional array to a one level deep structure, then most of the computations done are a one time expense. It pays off, because this way you can almost directly lookup your values by the string provided in your get call.
I've come up with something roughly like this:
<?php
class Demo {
protected $_values = array();
protected $_valuesByHash = array();
function createHashMap(&$array, $path = null) {
foreach ($array as $key => &$value) {
if (is_array($value)) {
$this->createHashMap($value, $path.$key.'.');
} else {
$this->_valuesByHash[$path.$key] =& $value;
}
}
}
function __construct(array $values) {
$this->_values = $values;
$this->createHashMap($this->_values);
// Check that references indeed work
// $this->_values['general'][0]['special']['number'] = 28;
// print_r($this->_values);
// print_r($this->_valuesByHash);
// $this->_valuesByHash['general.0.special.number'] = 29;
// print_r($this->_values);
// print_r($this->_valuesByHash);
}
public function get($hash, $default = null) {
return isset($this->_valuesByHash[$hash]) ? $this->_valuesByHash[$hash] : $default;
}
}
$test = new Demo(array(
'simple' => 27,
'general' => array(
'0' => array(
'something' => 'Hello World!',
'message' => 'Another message',
'special' => array(
'number' => 27
)
),
'1' => array(
'something' => 'Hello World! #2',
'message' => 'Another message #2'
),
)
));
$start = microtime(true);
for ($i = 0; $i < 10000; ++$i) {
$simple = $test->get('simple', 'default');
$general_0_something = $test->get('general.0.something', 'default');
$general_0_special_number = $test->get('general.0.special.number', 'default');
}
$stop = microtime(true);
echo $stop-$start;
?>
The setter is not yet implemented, and you would have to modify it for alternative syntax (# separator), but I think it conveys the idea.
At least on my testbed it takes half the time to execute this compared to the original implementation. Still raw array access is faster, but the difference in my case is around 30-40%. At the moment that was the best I could achieve. I hope that your actual case is not big enough that I've hit some memory constraints on the way. :-)
Ok, my first approached missed the goal I was aiming for. Here is the solution to using native PHP array syntax (at least for access) and still being able to set a default value.
Update: Added missing functionality for get/set and on the fly converting.
By the way, this is not an approach to take if you are optimizing for performance. This is perhaps 20 times slower than regular array access.
class Demo extends ArrayObject {
protected $_default;
public function __construct($array,$default = null) {
parent::__construct($array);
$this->_default = $default;
}
public function offsetGet($index) {
if (!parent::offsetExists($index)) return $this->_default;
$ret = parent::offsetGet($index);
if ($ret && is_array($ret)) {
parent::offsetSet($index, $this->newObject($ret));
return parent::offsetGet($index);
}
return $ret;
}
protected function newObject(array $array=null) {
return new self($array,$this->_default);
}
}
Init
$test = new Demo(array(
'general' => array(
0 => array(
'something' => 'Hello World!'
)
)
),'Default Value');
Result
$something = $test['general'][0]['something']; // 'Hello World!'
$notfound = $test['general'][0]['notfound']; // 'Default Value'
You're looking for something like that? Essentially the get() method uses references to descend into the $values array and breaks out of the method if a requirement could not be met.
class Demo {
protected $_values = array();
public function __construct(array $values) {
$this->_values = $values;
}
public function get($name, $default = null) {
$parts = preg_split('/[#.]/', $name);
if (!is_array($parts) || empty($parts)) {
return null;
}
$value = &$this->_values;
foreach ($parts as $p) {
if (array_key_exists($p, $value)) {
$value = &$value[$p];
} else {
return null;
}
}
return $value;
}
/**
* setter missing
*/
}
$test = new Demo(array(
'simple' => 2,
'general' => array(
0 => array(
'something' => 'Hello World!',
'message' => 'Another message',
'special' => array(
'number' => 4
)
),
1 => array(
'something' => 'Hello World! #2',
'message' => 'Another message #2'
)
)
));
$v = $test->get('simple');
var_dump($v);
$v = $test->get('general');
var_dump($v);
$v = $test->get('general.0');
var_dump($v);
$v = $test->get('general#0');
var_dump($v);
$v = $test->get('general.0.something');
var_dump($v);
$v = $test->get('general#0.something');
var_dump($v);
$v = $test->get('general.0.message');
var_dump($v);
$v = $test->get('general#0.message');
var_dump($v);
$v = $test->get('general.0.special');
var_dump($v);
$v = $test->get('general#0.special');
var_dump($v);
$v = $test->get('general.0.special.number');
var_dump($v);
$v = $test->get('general#0.special.number');
var_dump($v);
$v = $test->get('general.1');
var_dump($v);
$v = $test->get('general#1');
var_dump($v);
$v = $test->get('general.1.something');
var_dump($v);
$v = $test->get('general#1.something');
var_dump($v);
$v = $test->get('general.1.message');
var_dump($v);
$v = $test->get('general#1.message');
var_dump($v);
This is how multidimensional array work in general in PHP:
$data = array(
'general' => array(
0 => array(
'something' => 'Hello World!'
)
)
);
To receive Hello World:
echo $data['general'][0]['something'];

Categories