Pass key of object via arguments - php

I have this code:
function cmp_asc($a, $b){
$ta = date_create_from_format('Y/m/d', $a['props']['t']); // Y/n/j si no tienen 0 inicial
$tb = date_create_from_format('Y/m/d', $b['props']['t']);
$interval = date_diff($ta, $tb);
if($interval->days != 0){
if($interval->invert == 1){
return 1;
}else{
return -1;
}
}else{
return 0;
}
}
$arr1 = array(
'props' => array('t' => '2012/05/20')
);
$arr2 = array(
'props' => array('t' => '2012/05/21')
);
$arr3 = array(
'props' => array('t' => '2012/04/14')
);
$arr = array($arr1, $arr2, $arr3);
uasort($arr, 'cmp_asc');
and I'd like to know if I can pass the ['props']['t'] via arguments.
So, it will end up like:
function cmp_asc($a, $b, $key){
$ta = date_create_from_format('Y/m/d', $a <-- $key -->);
...
...
I'm thinking about variable variables, but I'm not too sure this is the right way to do it.
Any other ideas?

Not really as argument, because you're not calling the function yourself, but you can use closures to pass other variables into the function:
$key1 = 'props';
$key2 = 't';
uasort($arr, function ($a, $b) use ($key1, $key2) {
$ta = date_create_from_format('Y/m/d', $a[$key1][$key2]);
...
});
That's just a simple example. If you need dynamic key depths, this'll need a bit more code, along these lines:
function getValue(array $value, $key) {
$keys = explode('.', $key);
foreach ($keys as $k) {
$value = $value[$k];
}
return $value;
}
$arr = array('foo' => array('bar' => 'baz'));
echo getValue($arr, 'foo.bar');
Another idea would be using a class, usage of which would look like this:
$cmp = new ComparisonClass;
$cmp->key = 'props.t';
uasort($arr, array($cmp, 'compare'));
I'll let you figure out the implementation of ComparisonClass.
A nicer solution may be to simply standardize the format of the array you're going to sort.

Related

PHP how to delete deep keys programmatically

Imagine you have a deep array like this:
<?php
$array = ['hello' => ['deep' => 'treasure']];
Then you had an array of the keys to access the string 'treasure'
['hello', 'deep'];
How do you delete the string treasure if you did not know the depth of the array till run time
Edit:
My apologises I've definitely not provided enough information for what I'm looking to achieve
Here is some code I've come up with which does what I need but uses unsafe eval (keep in mind the target destination could be an array so array_walk_recursive won't work)
function iterator_keys($iterator, $outer_data) {
$keys = array();
for ($i = 0; $i < $iterator->getDepth() + 1; $i++) {
$sub_iterator = $iterator->getSubIterator($i);
$keys[$i] = ($i == 0 && is_object($outer_data)
|| $i > 0 && is_object($last_iterator->current())) ?
'->{"' . $sub_iterator->key() . '"}' :
'["' . $sub_iterator->key() . '"]';
$last_iterator = $sub_iterator;
}
return $keys;
}
function recursive_filter($data, callable $selector_function, $iterator = NULL) {
$iterator = $iterator ?? new RecursiveIteratorIterator(
new RecursiveArrayIterator($data),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $key => $value) {
if ($selector_function($value, $key, $iterator)) {
eval('unset($data' . implode('', iterator_keys($iterator, $data)) . ');');
}
}
return $data;
}
The intention is to have a deep data structure a function that evalutes each node and if it matches a condition then remove it from the data structure in place, if this can be done without eval that would be amazing but so far I think PHP can't programmatically delete something that is more than one level deep
Hello I think what you want is somethings like this
<?php
$array = ['hello' => ['deep' => ['deep1' => 'treasure']]];
$keys = ["hello", "deep", "deep1"];
function remove_recursive(&$array, $keys, $level = 0)
{
if ($level >= count($keys)) {
return $array;
}
if (isset($array[$keys[$level]]) && $level == count($keys) - 1) {
unset($array[$keys[$level]]);
} elseif (isset($array[$keys[$level]])) {
$array[$keys[$level]] = remove_recursive($array[$keys[$level]], $keys, $level + 1);
}
return $array;
}
var_dump(remove_recursive($array, $keys));
Well you can try this really quick and dirty way that uses eval to achieve your goal:
$array = ['hello' => ['deep' => 'treasure']];
$keys = ['hello', 'deep'];
$expression = 'unset($array[\'' . implode('\'][\'', $keys) . '\']);';
eval($expression);
But maybe you can tell us more about your development and we can help you reorganize it somehow to avoid this problem at all.
This will set the target array element to null. Optionally you could use '':
$array = ['hello' => ['deep' => 'treasure']];
$path = ['hello', 'deep'];
$temp = &$array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = null;
print_r($array);
Yields:
Array
(
[hello] => Array
(
[deep] =>
)
)

PHP Deep Extend Array

How can I do a deep extension of a multi dimensional associative array (for use with decoded JSON objects).
I need the php equivalent of jQuery's $.extend(true, array1, array2) with arrays instead of JSON and in PHP.
Here's an example of what I need (array_merge_recursive didn't seem to do the same thing)
$array1 = ('1'=> ('a'=>'array1a', 'b'=>'array1b'));
$array2 = ('1'=> ('a'=>'array2a', 'c'=>'array2b'));
$array3 = array_extend($array1, $array2);
//$array3 = ('1'=> ('a'=>'array2a', 'b'=>'array1b', 'c'=>'array2b'))
Notice how array2 overrides array1 if it has same value (like how extension of classes works)
If you have PHP 5.3.0+, you can use array_replace_recursive which does exactly what you need:
array_replace_recursive() replaces the values of array1 with the same
values from all the following arrays. If a key from the first array
exists in the second array, its value will be replaced by the value
from the second array. If the key exists in the second array, and not
the first, it will be created in the first array. If a key only exists
in the first array, it will be left as is. If several arrays are
passed for replacement, they will be processed in order, the later
array overwriting the previous values.
This might be what you're looking for:
function array_extend(&$result) {
if (!is_array($result)) {
$result = array();
}
$args = func_get_args();
for ($i = 1; $i < count($args); $i++) {
// we only work on array parameters:
if (!is_array($args[$i])) continue;
// extend current result:
foreach ($args[$i] as $k => $v) {
if (!isset($result[$k])) {
$result[$k] = $v;
}
else {
if (is_array($result[$k]) && is_array($v)) {
array_extend($result[$k], $v);
}
else {
$result[$k] = $v;
}
}
}
}
return $result;
}
Usage:
$arr1 = array('a' => 1, 'b' => 2, 'c' => 3);
$arr2 = array('b' => 'b', 'd' => 'd');
array_extend($arr1, $arr2);
print_r($arr1); // array('a' => 1, 'b' => 'b', 'c' => 3, 'd' => 'd')
// or, to create a new array and leave $arr1 unchanged use:
array_extend($arr3, $arr1, $arr2);
print_r($arr3); // array('a' => 1, 'b' => 'b', 'c' => 3, 'd' => 'd')
// or, use the return value:
print_r(array_extend($arr1, $arr2)); // but this will also modify $arr1
I use this in the same way I use angular.extend(dst, src) and jQuery.extend().
function extend($base = array(), $replacements = array()) {
$base = ! is_array($base) ? array() : $base;
$replacements = ! is_array($replacements) ? array() : $replacements;
return array_replace_recursive($base, $replacements);
}
Example:
si() is a utility sanitize function that grabs $_POST or $_GET and returns an array.
$s = extend(array(
'page' => 1,
'take' => 100,
'completed' => 1,
'incomplete' => 1,
), si());
Taken from array_merge docs:
function array_extend($a, $b) {
foreach($b as $k=>$v) {
if( is_array($v) ) {
if( !isset($a[$k]) ) {
$a[$k] = $v;
} else {
$a[$k] = array_extend($a[$k], $v);
}
} else {
$a[$k] = $v;
}
}
return $a;
}
You should use: https://github.com/appcia/webwork/blob/master/lib/Appcia/Webwork/Storage/Config.php#L64
/**
* Merge two arrays recursive
*
* Overwrite values with associative keys
* Append values with integer keys
*
* #param array $arr1 First array
* #param array $arr2 Second array
*
* #return array
*/
public static function merge(array $arr1, array $arr2)
{
if (empty($arr1)) {
return $arr2;
} else if (empty($arr2)) {
return $arr1;
}
foreach ($arr2 as $key => $value) {
if (is_int($key)) {
$arr1[] = $value;
} elseif (is_array($arr2[$key])) {
if (!isset($arr1[$key])) {
$arr1[$key] = array();
}
if (is_int($key)) {
$arr1[] = static::merge($arr1[$key], $value);
} else {
$arr1[$key] = static::merge($arr1[$key], $value);
}
} else {
$arr1[$key] = $value;
}
}
return $arr1;
}
With a little googling I found this:
/**
* jquery style extend, merges arrays (without errors if the passed values are not arrays)
*
* #return array $extended
**/
function extend() {
$args = func_get_args();
$extended = array();
if(is_array($args) && count($args)) {
foreach($args as $array) {
if(is_array($array)) {
$extended = array_merge($extended, $array);
}
}
}
return $extended;
}
extend($defaults, $new_options);
I guess here is the correct answer, because:
your answer have a bug with warning:
Warning: Cannot use a scalar value as an array in...
Because $a is not always an array and you use $a[$k].
array_merge_recursive does indeed merge arrays, but it converts values with duplicate keys to arrays rather than overwriting the value in the first array with the duplicate value in the second array, as array_merge does.
other aswers are not recursives or not simple.
So, here is my answer: your answer without bugs:
function array_extend(array $a, array $b) {
foreach($b as $k=>$v) {
if( is_array($v) ) {
if( !isset($a[$k]) ) {
$a[$k] = $v;
} else {
if( !is_array($a[$k]){
$a[$k]=array();
}
$a[$k] = array_extend($a[$k], $v);
}
} else {
$a[$k] = $v;
}
}
return $a;
}
And my answer with ternary operator:
function array_extend(array $a, array $b){
foreach($b as $k=>$v)
$a[$k] = is_array($v)&&isset($a[$k])?
array_extend(is_array($a[$k])?
$a[$k]:array(),$v):
$v;
return $a;
}
Edit: And a bonus one with as many arrays you want:
function array_extend(){
$args = func_get_args();
while($extended = array_shift($args))
if(is_array($extended))
break;
if(!is_array($extended))
return FALSE;
while($array = array_shift($args)){
if(is_array($array))
foreach($array as $k=>$v)
$extended[$k] = is_array($v)&&isset($extended[$k])?
array_extend(is_array($extended[$k])?
$extended[$k]:array(),$v):
$v;
}
return $extended;
}

foreach with three variables add

i want to use foreach function for 3 variables
i use this code in my page:
foreach (array_combine($online_order_name, $online_order_q) as $online_order_name1 =>
$online_order_q1) {
mysql_query("insert into .......
}
how can i do that like :
<?
foreach (array_combine($online_order_name, $online_order_q,$third_variable) as
$online_order_name1 => $online_order_q1 => $third_variable2) {
?>
thank you
I guess this will solve your need:
<?php
$A=array(1,2,3);
$B=array('a','b','c');
$C=array('x','y','z');
foreach (array_map(NULL, $A, $B, $C) as $x) {
list($a, $b, $c) = $x;
echo "$a $b $c\n";
}
foreach does only the iteration over one iterator and offering one key and one value. That's how iterators are defined in PHP.
In your question you write that you need multiple values (but not keys, but I think this won't hurt). This does not work out of the box.
However, you can create an iterator that allows you to iterate over multiple iterators at once. A base class ships with PHP it's called MultipleIterator. You can then create your own ForEachMultipleArrayIterator that allows you to easily specify multiple keys and values per each iteration:
$a = array(1,2,3);
$b = array('b0' => 'a', 'b1' => 'b', 'b2' => 'c');
$c = array('c0' => 'x', 'c1' => 'y', 'c2' => 'z');
$it = new ForEachMultipleArrayIterator(
// array keyvar valuevar
array($a, 'akey' => 'avalue'),
array($b, 'bkey' => 'bvalue'),
array($c, 'ckey' => 'cvalue')
);
foreach($it as $vars)
{
extract($vars);
echo "$akey=$avalue, $bkey=$bvalue and $ckey=$cvalue \n";
}
class ForEachMultipleArrayIterator extends MultipleIterator
{
private $vars;
public function __construct()
{
parent::__construct();
$arrays = func_get_args();
foreach($arrays as $set)
{
if (count($set) != 2)
throw new invalidArgumentException('Not well defined.');
$array = array_shift($set);
if (!is_array($array))
throw new InvalidArgumentException('Not an array.');
$this->vars[key($set)] = current($set);
parent::attachIterator(new ArrayIterator($array));
}
}
public function current()
{
return array_combine(array_keys($this->vars), parent::key())
+ array_combine($this->vars, parent::current());
}
}
Demo - But if you've come that far, I'm pretty sure it's clear that what you want to do can be solved much easier by actually iterating over something else. The example above is basically the same as:
foreach(array_map(NULL, array_keys($a), $a, array_keys($b), $b, array_keys($c), $c) as $v)
{
list($akey, $avalue, $bkey, $bvalue, $ckey, $cvalue) = $v;
echo "$akey=$avalue, $bkey=$bvalue and $ckey=$cvalue \n";
}
So better get your arrays into the right order and then process them :)
See also: Multiple index variables in PHP foreach loop.
I don't believe this is possible with array_combine and foreach, because they operate specifically on key/value pairs in an associative array. PHP doesn't have tuples as a builtin data structure.
for ($i = 0, $length = count($array1); $i < $length; $i++) {
list($a, $b, $c) = array($array1[$i], $array2[$i], $array3[$i]);
...
}
You should think about a better array structure though that doesn't require you to pull information from three different arrays. Something like:
$orders = array(
array('name' => 'Foo', 'q' => 'bar', 'third' => 'Baz'),
...
);

generic function for extracting values from an array with one particular key in PHP

Is it possible in PHP to extract values from an array with a particular key path and return an array of those values? I'll explain with an example:
$user =
array (
array(
'id' => 1,
'email' =>'asd#example.com',
'project' => array ('project_id' => 222, 'project_name' => 'design')
),
array(
'id' => 2,
'email' =>'asd2#example.com',
'project' => array ('project_id' => 333, 'project_name' => 'design')
)
);
/** I have to write a function something like: */
$projectIds = extractValuesWithKey($user, array('project', 'project_id'));
print_r($projectIds);
Output:
Array(
[0] => 222,
[1] => 333
)
I would have gone for a different approach (not that there's anything wrong with the array-function-based answers) by using a recursive iterator to flatten the array which makes the key-path comparison fairly simple.
function extractValuesWithKey($array, $keys) {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
$keys_count = count($keys);
// No point going deeper than we have to!
$iterator->setMaxDepth($keys_count);
$result = array();
foreach ($iterator as $value) {
// Skip any level that can never match our keys
if ($iterator->getDepth() !== $keys_count) {
continue;
}
// Build key path to current item for comparison
$key_path = array();
for ($depth = 1; $depth <= $keys_count; $depth++) {
$key_path[] = $iterator->getSubIterator($depth)->key();
}
// If key paths match, add to results
if ($key_path === $keys) {
$result[] = $value;
}
}
return $result;
}
To make the whole thing more useful, you could even wrap the code into a custom FilterIterator rather than a basic function, but I guess that's probably a different question entirely.
Well, that's easier than you think.
function extractValuesWithKey($array, $parts) {
$return = array();
$rawParts = $parts;
foreach ($array as $value) {
$tmp = $value;
$found = true;
foreach ($parts as $key) {
if (!is_array($tmp) || !isset($tmp[$key])) {
$found = false;
continue;
} else {
$tmp = $tmp[$key];
}
}
if ($found) {
$return[] = $tmp;
}
}
return $return;
}
If the 'key path' isn't dynamic, you can do a one-liner with array_map:
$projectIds = array_map(function($arr) { return $arr['project']['project_id']; }, $user);
Alternatively, for dynamic paths:
function extractValuesWithKey($users, $path) {
return array_map(function($array) use ($path) {
array_walk($path, function($key) use (&$array) { $array = $array[$key]; });
return $array;
}, $users);
}
The closures/anonymous functions only work with PHP 5.3+, and I've no idea how this would compare performance-wise to a double foreach loop. Note also that there's no error checking to ensure that the path exists.
I also used a similiar function in one of my projects, maybe you find this useful:
function extractValuesWithKey($data, $path) {
if(!count($path)) return false;
$currentPathKey = $path[0];
if(isset($data[$currentPathKey])) {
$value = $data[$currentPathKey];
return is_array($value) ? extractValuesWithKey($value, array_slice($path, 1)) : $value;
}
else {
$tmp = array();
foreach($data as $key => $value) {
if(is_array($value)) $tmp[] = extractValuesWithKey($value, $path);
}
return $tmp;
}
}

Built-in PHP function to reset the indexes of an array?

Example:
$arr = array(1 => 'Foo', 5 => 'Bar', 6 => 'Foobar');
/*... do some function so $arr now equals:
array(0 => 'Foo', 1 => 'Bar', 2 => 'Foobar');
*/
Use array_values($arr). That will return a regular array of all the values (indexed numerically).
PHP docs for array_values
array_values($arr);
To add to the other answers, array_values() will not preserve string keys. If your array has a mix of string keys and numeric keys (which is probably an indication of bad design, but may happen nonetheless), you can use a function like:
function reset_numeric_keys($array = array(), $recurse = false) {
$returnArray = array();
foreach($array as $key => $value) {
if($recurse && is_array($value)) {
$value = reset_numeric_keys($value, true);
}
if(gettype($key) == 'integer') {
$returnArray[] = $value;
} else {
$returnArray[$key] = $value;
}
}
return $returnArray;
}
Not that I know of, you might have already checked functions here
but I can imagine writing a simple function myself
resetarray($oldarray)
{
for(int $i=0;$i<$oldarray.count;$i++)
$newarray.push(i,$oldarray[i])
return $newarray;
}
I am little edgy on syntax but I guess u got the idea.

Categories