This is such a simple problem but the PHP doc does not explain why it is happening.
I have this code:
var_dump($newattributes); var_dump($oldattributes);
var_dump(array_diff($newattributes, $oldattributes));
For briefity I am going to omit large parts of the structure I am actually using (since each is 117 elements long) and cut to the case.
I have one array called $newattributes which looks like:
array(117){
// Lots of other attributes here
["deleted"] => int(1)
}
And another called $oldattributes which looks like:
array(117){
// Lots of other attributes here
["deleted"] => string(1) "0"
}
Which looks different right? According to array_diff: no. The output I get from array_diff is:
array(0) { }
I have read the documentation page however it says:
Two elements are considered equal if and only if (string) $elem1 ===
(string) $elem2. In words: when the string representation is the same.
And I am not sure how "1" can object equal "0".
So am I seeing some caveat with array_diff I didn't take into consideration?
The problem might reside in the fact that you are using associative arrays : you should try and use the following for associative arrays : array_diff_assoc():
<?php
$newattributes = array(
"deleted" => 1
);
$oldattributes = array(
"deleted" => "0"
);
$result = array_diff_assoc($newattributes, $oldattributes);
var_dump($result);
?>
result :
array(1) {
["deleted"]=>
int(1)
}
It does happen to me too (when there are more values than one)
$new = array('test' => true, 'bla' => 'test' 'deleted' => 1);
$old = array('test' => true, 'deleted' => '0');
For a full array_diff you need to make some extra work, because in default it returns a relative complement
Try this:
array_diff(array_merge($new, $old), array_intersect($new, $old))
Result:
Array
(
[bla] => test
[deleted] => 0
)
Related
I have a multidimensional array that I am trying to search for a specific value (url) and then retrieve an another value in the same row (value). I also need to divert to an else if it is not found.
array(2) {
[0]=> array(2) {
["url"]=> string(7) "fareham"
["value"]=> string(7) "Fareham"
}
[1]=> array(2) {
["url"]=> string(11) "southampton"
["value"]=> string(11) "Southampton"
}
}
I have been experimenting with array_key_exists() and isset() to check it's set and just coming up null!
For example, if I search for fareham, I want to return Fareham. If I search for nottingham, I expect null.
How can I isolate the qualifying value?
Use array_column() to index the data by the url columns and then use isset() to check if the value is there...
$data = array_column($data, "value", "url");
$search = 'southampton';
$value = isset($data[$search])?$data[$search]:"not found";
echo $value;
or for PHP 7+, you can use the null coalescing operator (??)
$value = $data[$search]??"not found";
Here is the minimal way to do it (no checks)
$a = array (
0 =>
array (
"url" => 'fareham',
"value" => 'Fareham'
),
1 =>
array (
"url" => 'southampton',
"value" => 'Southampton'
)
);
$u = 'fareham';
$i = $a[false===($f=array_search($u,array_column($a,'url')))?-1:$f]['value'];
print_r($i);
Output
Fareham
Sandbox
How it works
First we create an array we can search by using array_column on the column we want to search in this case url. It looks like this [0=>'fareham', 1=>'southampton']
Then we use the normal array_search which returns an index, if you notice above the indexes are correlated to the original array. Which means we can put that in as the top level key, then it's a simple matter of adding the key we want.
Because array_search can return a boolean(false) which PHP sees as 0 or the first index I put a little hack in for that. But a better way is to check it like this:
$a = array (
0 =>
array (
"url" => 'fareham',
"value" => 'Fareham',
"extra" => 'Foo'
),
1 =>
array (
"url" => 'southampton',
"value" => 'Southampton',
"extra" => 'Bar'
)
);
function serchMultiDimensionalValue($needle, $haystack, $fields='value'){
if(false === ($f=array_search($needle,array_column($haystack,'url')))) return false; //or return [];
if(!is_array($fields)) $fields = [$fields];
return array_intersect_key($haystack[$f], array_flip($fields));
}
var_dump(serchMultiDimensionalValue('foo',$a));
var_dump(serchMultiDimensionalValue('fareham',$a));
var_dump(serchMultiDimensionalValue('fareham',$a, 'extra'));
var_dump(serchMultiDimensionalValue('fareham',$a, ['extra','url']));
Ouput
bool(false)
array(1) {
["value"]=>
string(7) "Fareham"
}
array(1) {
["extra"]=>
string(3) "Foo"
}
array(2) {
["url"]=>
string(7) "fareham"
["extra"]=>
string(3) "Foo"
}
Sandbox
I added a bit more "functionality" to it, hope you don't mind.
While it might be alluring to seek out a one-liner like:
array_column($array, $returnColumn, $haystackColumn)[$needle] ?? null
Be aware that array_column() will be unconditionally doubling the memory usage (because it is generating a new array with the same length as the original) for the sake of assigning new first level keys to search by.
Metaphorically speaking, it is like building a new registry of residences in a neighborhood including every homeowner's name and their address. Yes, you can find out who lives at a given home without knocking on the front door, but you had to knock on every door to collect every home's details before enjoying this convenience. Why not just knock on the door of each original house and ask if they are who you are looking for? It is much easier, and you will be able to "quit" early as soon as you find the homeowner that you are looking for.
For best efficiency and for least memory usage, I recommend the below classic loop with conditional break. It is built with dynamic search and return variables for general usage, but you can use hardcoded values if you like.
Code: (Demo)
$array = [
["url" => 'fareham', "value" => 'Fareham'],
["url" => 'southampton', "value" => 'Southampton']
];
$needle = 'fareham';
$haystackColumn = 'url';
$returnColumn = 'value';
$value = null;
foreach ($array as $row) {
if ($row[$haystackColumn] === $needle) {
$value = $row[$returnColumn];
break;
}
}
var_export($value); // 'Fareham'
$array1 = [
'1' => '11',
'b' => 1,
3 => 33,
8 => 8
];
$array2 = [
'1' => '22',
'b' => 2,
3 => 44,
9 => 9
];
$merged = array_merge_recursive($array1, $array2);
and the result is:
array(7) {
[0]=>
string(2) "11"
["b"]=>
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
[1]=>
int(33)
[2]=>
int(8)
[3]=>
string(2) "22"
[4]=>
int(44)
[5]=>
int(9)
}
so lets take a glance: the only part is the 'b' keys, they are actually works. I dont want to overwrite anything of it but putting them together to an array. Thats good! But then keys the other numeric keys (int or string) are screwed up.
I want to have this as result:
[
'1' => ['11', '22']
'b' => [1, 2]
[3] => [33, 44]
[8] => 8,
[9] => 9
]
possible?
EDIT: of course keys "1" and 1 - string vs int key are the same
Let's break down this question into to separate problems:
When a key in the second array exist in the first array, you want to create an array and make the value the first element of that array.
To be honest, I don't know an easy way of solving this. I'm not sure there is one. And even if, I'm not sure you really want it. Such a function will lead to arrays having values that are a string or an array. How would you handle such an array?
Update: Well, there is one. Your example already shows that array_merge_recursive will convert values with a string key into an array. So 1 => 'foo' would be 0 => 'foo', but 'foo' => 'bar' will end up as 'foo' => ['bar']. I really don't understand this behaviour.
Using string keys would help you out in this case, but after learning more about array_merge_recursive I decided to avoid this function when possible. After I asked this question someone filed it as a bug in it since PHP 7.0, since it works differently in PHP 5.x.
You want to keep the keys, while array_merge_resursive doesn't preserve integer keys, while it does for integer keys:
If the input arrays have the same string keys, then the values for
these keys are merged together into an array, and this is done
recursively, so that if one of the values is an array itself, the
function will merge it with a corresponding entry in another array
too. If, however, the arrays have the same numeric key, the later
value will not overwrite the original value, but will be appended.
To make it worse, it handles differently when handling the nested arrays:
$array1 = [30 => [500 => 'foo', 600 => 'bar']];
$array2 = [];
array_merge_recursive($array1, $array2);
//[0 => [500=> 'foo', 600 => 'bar']];
So effectively, array_merge_resursive isn't really resursive.
Using array_replace_resursive solves that problem:
array_replace_recursive($array1, $array2);
//output:
//array:5 [
// 1 => "22"
// "b" => 2
// 3 => 44
// 8 => 8
// 9 => 9
//]
Please note that PHP is very consistent in being inconsistent. While array_merge_recursive isn't recursive, array_replace_recursive doesn't replace (it also appends):
If the key exists in the second array, and not the first, it will be
created in the first array.
In many cases this is desired behavior and since naming functions isn't PHP's strongest point, you can consider this as a minor issue.
Can you rely on a native function to return your exact desired output? No. At least not in any version as of the date of this post (upto PHP8.1).
The good news is that crafting your own solution is very simple.
Code: (Demo)
foreach ($array2 as $key => $value) {
if (!key_exists($key, $array1)) {
$array1[$key] = $value;
} else {
$array1[$key] = (array)$array1[$key];
$array1[$key][] = $value;
}
}
var_export($array1);
I suppose I am less inclined to recommend this output structure because you have potentially different datatypes on a given level. If you were building subsequent code to iterate this data, you'd need to write conditions on every level to see if the data was iterable -- it just feels like you are setting yourself up for code bloat/convolution. I'd prefer a result which has consistent depth and datatypes.
I've got this PHP code:
$array = array( 0 => 'brick', 1 => 'brick', 2 => 'window' );
$array_2 = array( 0 => 'brick', 1 => 'brick', 2 => 'door' );
foreach ( $array as $key => $value)
{
$my_variable = array();
foreach ( $array_2 as $key_2 => $value_2 )
{
$my_variable[ $value_2 ] = !isset( $my_variable[ $value_2 ] ) ?
$key_2 :
'several';
echo var_dump( $my_variable[ $value_2 ] ) . '<br/>';
echo var_dump( $my_variable[ $value_2 ] == 'several' ) . '<br/><br/>';
}
}
It outputs:
int(0)
bool(true)
string(7) "several"
bool(true)
int(2)
bool(false)
int(0)
bool(true)
string(7) "several"
bool(true)
int(2)
bool(false)
int(0)
bool(true)
string(7) "several"
bool(true)
int(2)
bool(false)
The strange part of it is this one (the first output shouln't be true as it's not equal to 'several', as it says it's value is indeed equal to 0):
int(0)
bool(true)
string(7) "several"
bool(true)
Because of this condition:
$my_variable[ $value_2 ] == 'several'
So, the question is: does anyone know why is this happening? Is there something wrong in my code I'm ignoring...?
On the other hand: if I change the array ordering (make it to start from 1, not from 0), it magicaly starts to work (outpust the correct logic):
$array = array( 1 => 'brick', 2 => 'brick', 3 => 'window' );
$array_2 = array( 1 => 'brick', 2 => 'brick', 4 => 'door' );
It's also possible to switch the condition (without a need to change array numbering) to:
!is_string( $my_variable[ $value_2 ] )
It than maybe outputs the correct logic, but the the mystery (why PHP outputs true if it should output false) still remains. And I'm afraid it could bring some troubles in the future when the code will be in the production. I'm also not happy from not knowing whether I'm possibly doing something wrong here...?
Based on the comments I would like to make some statements, just to make clear why I'm doing some things in the code above the way I do:
I'm looping inside other loop in order to chache the values from the array_2, (just in my real code, not here, code here is just substraction of it, to make question simpler, sorry for the confusion).
I'm echoing var_dump() in order to be able to add the <br/> string (thanks for <br/>, I totaly overlooked it.) - corrected
Yes, $my_variable = null; should be $my_variable = array();, but again only here (in the real code I assign array() later and only if needed). - corrected
The issue here is that you are using ==.
This will compare value, and will also convert values so that they are the same type.
0 == 'several'
These two types are not the same. Because of this, PHP will convert 'several' into a number - 0. You will then get 0 == 0, which is true.
The reason it "worked" when you changed the indexes was that you were instead doing
`1 == 'several'`
Which PHP interpreted as 1 == 0 which is false.
What you want to use is ===. This will compare both type and value.
0 === 'several'
This will be false since one is a number and the other a string.
I have following Array
$arr = array(1 => 1, "1" => 50);
When I execute count() on it, it gives me strange answer: 1
echo count($arr);
Whereas an array $arr has two elements.
Why?
It is due to Type Casting . Check Example #2 Type Casting and Overwriting example in Arrays .
If multiple elements in the array declaration use the same key, only the last one will be used as all others are overwritten .
$arr = array(1 => 10, "1" => 20);
var_dump( $arr );
Displays :
array (size=1)
1 => int 20
And so :
echo count( $arr );
Displays :
1
Which is correct .
If multiple elements in the array declaration use the same key, only the last one will be used as all others are overwritten.As all the keys in the below example are cast to 1, the value will be overwritten on every new element.
Sample Code:
$array = array(
1 => "a",
"1" => "b"
);
var_dump($array);
echo count($array);
Sample output:
array(1) {
[1]=>
string(1) "b"
}
1
For details have a look here:http://nz1.php.net/manual/en/language.types.array.php
if you change the "1" to "2" it will count 2. The problem is the fact that you choose the first element in the array to be 1 then you choose it to be 50, so in the final, the array will have one element, which is 50.
See it here!
I have the following array and would like to know what the best way would be of validating and santizing this array to make sure only integers are allowed?
if(is_array($_POST['taxonomy'])) {
$term_ids = array_map('esc_attr', $_POST['taxonomy']);
}
Which looks like this when printed:
Array
(
[0] => 13
[1] => 12
)
I know the esc_attr isn't very secure so would like something a bit more beefed up.
Any help would be great.
Cheers,
Dave
Since it's $_POST data, you'll want to check for ctype_digit (i.e. a string containing only digits):
$sanitizedValues = array_filter($_POST['taxonomy'], 'ctype_digit');
Note that this simply discards non-numeric values.
An alternative would be using phps filter functions:
$array = array(
13, 12, '1', 'a'
);
$result = filter_var($array, FILTER_VALIDATE_INT, array(
'flags' => FILTER_REQUIRE_ARRAY,
'options' => array('min_range' => 1)
));
var_dump($result);
/*
array(4) {
[0]=>
int(13)
[1]=>
int(12)
[2]=>
int(1)
[3]=>
bool(false)
}
*/
if(is_array($_POST['taxonomy'])) {
$term_ids = array_map('intval', $_POST['taxonomy']);
}
Should do the trick. NOTE: this is sanitation. More: http://php.net/manual/en/function.intval.php
foreach( $array as $key => $value) {
$array[$key] = (int) $value;
if( $array[$key] != $value ) {
// error
}
}
If you are looking for one-liner to check against specified condition you can use:
$onlyIntegers = Arr::check($_POST['taxonomy'], 'ctype_digit');
assuming your $_POST['taxonomy'] can contain numeric strings as #deceze suggested, or just plain:
$onlyIntegers = Arr::check($_POST['taxonomy'], 'is_int');
if you are sure that $_POST['taxonomy'] values should be in fact integers.
Arr::check method is a part of this php array library which contains various methods to help you deal with different types of arrays.