Laravel array_diff() as a changes log - php

I have two json strings in my database that contain data I'm trying to compare against one another to show in an activity log to see what a user has changed about an entry: old_properties and properties.
This is my current controller:
$properties = json_decode($log->properties, true);
$old_properties = json_decode($log->old_properties, true);
$changes = array_diff($properties, $old_properties);
This is my blade:
#foreach ($changes as $key => $value)
{{$key}}: {{$value}}<br>
#endforeach
So for example, if my data is:
old_properties: 'name' => 'Matthew', 'state' => 'Oregon'
properties: 'name' => 'Samuel', 'state' => 'Oregon'
all that I see from my $changes is: name: Samuel
What I am trying to display is: name: Matthew -> Samuel, or some other way to show what the old property was, and what it was changed to in the same line.

It looks like you could do something like:
#foreach ($changes as $key => $value)
{{$key}}: {{$old_properties[$key]}} -> {{$value}}<br>
#endforeach

Do Set calculation can solve this problem:
function printKeptAndUpdated ($setMapKeptAndUpdated, $setA, $setB) {
$changes = [];
foreach ($setMapKeptAndUpdated as $key => $value) {
$changes[] = $key . ": " . $setA[$key] . " -> " . $setB[$key];
}
echo("<pre>");
print_r($changes);
echo("</pre>");
}
$old_properties = [
"ape" => "black",
"cat" => "white",
"dog" => "black",
"fox" => "red",
];
$properties = [
"bear" => "brown",
"cat" => "white",
"dog" => "yellow",
"eagle" => "grey",
"fox" => "white",
"giraffe" => "yellow",
];
// Define 2 Set of array, setA is old set data, setB is new set data.
// Both setA and setB should only be one dimensional array
// which elements are always key => value pair only.
$setA = $old_properties;
$setB = $properties;
$setMap = [];
// Set map will do Set calculation in the future,
// and imply those 4 possibility: deleted, added, kept (updated or untouched),
// but only 3 of them are shown: deleted, added, kept.
// 4 possiblility after Set calculation.
$setMapDeleted = [];
$setMapAdded = [];
$setMapKeptAndUpdated = [];
$setMapKeptAndUntouched = [];
// Put $setA in $setMap first.
foreach($setA as $key => $value) {
$setMap[$key] = "a";
}
// Then put $setB in $setMap too.
foreach($setB as $key => $value) {
if (array_key_exists($key, $setMap)) {
$setMap[$key] = "ab";
} else {
$setMap[$key] = "b";
}
}
// So now the $setMap structure looks like this
// (just example, not as same as current data):
//$setMap = [
// "ape" => "b",
// "bear" => "ab",
// "cat" => "a",
// "dog" => "a",
// "eagle" => "b",
//];
// Where "a" = setA - setB, "b" = setB - setA, "ab" = setA ∩ setB (intersection)
// Now finish the Set calculation and also separate Set "kept" to set "updated" and Set "untouched".
foreach($setMap as $key => $value) {
if ($value === "a") {
$setMapDeleted[$key] = $setA[$key];
} elseif ($value === "b") {
$setMapAdded[$key] = $setB[$key];
} elseif ($value === "ab") {
if ($setB[$key] === $setA[$key]) {
$setMapKeptAndUntouched[$key] = $setB[$key];
} else {
$setMapKeptAndUpdated[$key] = $setB[$key];
}
}
}
printKeptAndUpdated($setMapKeptAndUpdated, $setA, $setB);
// Print the result you want.

Related

Create a new array from a unknown depth multidimensional and keep the same structure

I have a multidimensional array that can have any depth. What im trying to do is to filter the whole path based on dynamic keys and create a new array of it.
Example of the array
$originalArray = [
"title" => "BACKPACK MULTICOLOUR",
"description" => "description here",
"images" => [
[
"id" => 12323123123,
"width" => 635,
"height" => 560,
"src" => "https://example.com",
"variant_ids": [32694976315473, 32863017926737],
],
[
"id" => 4365656656565,
"width" => 635,
"height" => 560,
"src" => "https://example.com",
"variant_ids": [32694976315473, 32863017926737],
]
],
"price" => [
"normal" => 11.00,
"discount" => [
"gold_members" => 9.00,
"silver_members" => 10.00,
"bronze_members" => null
]
]
];
Example how the output should look like with the key "title, width, height, gold_members" filtered out. Only keys from the end of the array tree should be valid, so nothing must happen when images is in the filter
$newArray = [
"title" => "BACKPACK MULTICOLOUR",
"images" => [
[
"width" => 635,
"height" => 560,
],
[
"width" => 635,
"height" => 560,
]
],
"price" => [
"discount" => [
"gold_members" => 9.00,
]
]
];
I guess that i should create a function that loop through each element and when it is an associative array, it should call itself again
Because the filtered paths are unknown i cannot make a hardcoded setter like this:
$newArray["images"][0]["width"] = 635
The following filter will be an example but it should basically be dynamic
example what i have now:
$newArray = handleArray($originalArray);
handleArray($array)
{
$filter = ["title", "width", "height", "gold_members"];
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->handleArray($value);
} else {
if (in_array($key, $filter)) {
// put this full path in the new array
}
}
}
}
[Solved] Update:
I solved my problem thanks to #trincot
I used his code and added an extra check to add an array with multiple values to the new array
My code to solve the issue:
<?php
function isListOfValues($array) {
$listOfArrays = [];
foreach ($array as $key => $value) {
$listOfArrays[] = ! is_array($value) && is_int($key);
}
return array_sum($listOfArrays) === count($listOfArrays);
}
function filterKeysRecursive(&$arr, &$keep) {
$result = [];
foreach ($arr as $key => $value) {
if (is_array($value) && ! isListOfValues($value)) {
$value = filterKeysRecursive($value, $keep);
if (count($value)) {
$result[$key] = $value;
}
} else if (array_key_exists($key, $keep)) {
$result[$key] = $value;
}
}
return $result;
}
$keep = array_flip(["title", "width", "height", "gold_members"]);
$result = filterKeysRecursive($originalArray, $keep);
You could use a recursive function, with following logic:
base case: the value associated with a key is not an array (it is a "leaf"). In that case the new object will have that key/value only when the key is in the list of desired keys.
recursive case: the value associated with a key is an array. Apply recursion to that value. Only add the key when the returned result is not an empty array. In that case associate the filtered value to the key in the result object.
To speed up the look up in the list of keys, it is better to flip that list into an associative array.
Here is the implementation:
function filter_keys_recursive(&$arr, &$keep) {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$value = filter_keys_recursive($value, $keep);
if (count($value)) $result[$key] = $value;
} else if (array_key_exists($key, $keep)) {
$result[$key] = $value;
}
}
return $result;
}
$originalArray = ["title" => "BACKPACK MULTICOLOUR","description" => "description here","images" => [["id" => 12323123123,"width" => 635,"height" => 560,"src" => "https://example.com"],["id" => 4365656656565,"width" => 635,"height" => 560,"src" => "https://example.com"]],"price" => ["normal" => 11.00,"discount" => ["gold_members" => 9.00,"silver_members" => 10.00,"bronze_members" => null]]];
$keep = array_flip(["title", "width", "height", "gold_members"]);
$result = filter_keys_recursive($originalArray, $keep);
My proposition to you is to write a custom function to transform structure from one schema to another:
function transform(array $originalArray): array {
array_walk($originalArray['images'], function (&$a, $k) {
unset($a['id']); unset($a['src']);
});
unset($originalArray['description']);
unset($originalArray['price']['normal']);
unset($originalArray['price']['discount']['silver_members']);
unset($originalArray['price']['discount']['bronze_members']);
return $originalArray;
}
var_dump(transform($originalArray));
If you are familiar with OOP I suggest you to look at how DTO works in API Platform for example and inject this idea into your code by creating custom DataTransformers where you specify which kind of structers you want to support with transformer and a method where you transform one structure to another.
Iterate over the array recursively on each key and subarray.
If the current key in the foreach is a required key in the result then:
If the value is not an array, simply assign the value
If the value is an array, iterate further down over value recursively just in case if there is any other filtering of the subarray keys that needs to be done.
If the current key in the foreach is NOT a required key in the result then:
Iterate over value recursively if it's an array in itself. This is required because there could be one of the filter keys deep down which we would need. Get the result and only include it in the current subresult if it's result is not an empty array. Else, we can skip it safely as there are no required keys down that line.
Snippet:
<?php
function filterKeys($array, $filter_keys) {
$sub_result = [];
foreach ($array as $key => $value) {
if(in_array($key, $filter_keys)){// if $key itself is present in $filter_keys
if(!is_array($value)) $sub_result[$key] = $value;
else{
$temp = filterKeys($value, $filter_keys);
$sub_result[$key] = count($temp) > 0 ? $temp : $value;
}
}else if(is_array($value)){// if $key is not present in $filter_keys - iterate over the remaining subarray for that key
$temp = filterKeys($value, $filter_keys);
if(count($temp) > 0) $sub_result[$key] = $temp;
}
}
return $sub_result;
}
$result = filterKeys($originalArray, ["title", "width", "height", "gold_members"]);
print_r($result);
Online Demo
Try this way.
$expectedKeys = ['title','images','width','height','price','gold_members'];
function removeUnexpectedKeys ($originalArray,$expectedKeys)
{
foreach ($originalArray as $key=>$value) {
if(is_array($value)) {
$originalArray[$key] = removeUnexpectedKeys($value,$expectedKeys);
if(!is_array($originalArray[$key]) or count($originalArray[$key]) == 0) {
unset($originalArray[$key]);
}
} else {
if (!in_array($key,$expectedKeys)){
unset($originalArray[$key]);
}
}
}
return $originalArray;
}
$newArray = removeUnexpectedKeys ($originalArray,$expectedKeys);
print_r($newArray);
check this on editor,
https://www.online-ide.com/vFN69waXMf

php array: if identical key values then choose highest by other key value

$myArray = [
"ID" => "",
"Module" => "",
"Version"=> ""
];
Output:
[
{23,finance,1.0},
{24,finance,1.1},
{25,logistic,1.0}
]
I have an array with the given Keys like above. I need a new array that gives me the highest Version IF module is same. How would I do that?
desired Output:
[
{24,finance,1.1},
{25,logistic,1.0}
]
This is what I tried
$modulesFiltered = [];
$i = 0;
$j = 0;
foreach($modules as $module){
$modulesFiltered[$i]['ID'] = $module['ID'];
foreach($modulesFiltered as $moduleF){
if(!empty($moduleF[$j]['Module'])){
if($module[$i]['Module'] == $moduleF[$j]['Module']){
$modulesFiltered[$i]['Module'] = 'this is doubled';
}
} else {
$modulesFiltered[$i]['Module'] = $module['Module'];
}
$j++;
}
$modulesFiltered[$i]['Module'] = $module['Module'];
$i++;
}
I tried to debug your code though.The problem is that you try to access element [0] of $moduleF. You should change $moduleF[$j]['Module'] to $moduleF['Module'].
Use standard functions where possible. for finding values within (multidimensional) array's you can use array_search. The code beneath works.
Also don't compare strings with == use strcmp(str1, str2) == 0 instead
$inputArray = array(
array(
"ID" => 23,
"Module" => "finance",
"Version"=> 1.0),
array(
"ID" => 24,
"Module" => "finance",
"Version"=> 1.1),
array(
"ID" => 25,
"Module" => "logistiscs",
"Version"=> 1.0));
$output = array();
foreach($inputArray as $element)
{
$key = array_search($element["Module"], array_column($output, "Module"));
if(is_numeric($key))
$output[$key]["Version"] = max($element["Version"], $output[$key]["Version"]);
else
$output[] = $element;
}
print_r($output);

get key or value from array to use in php

I want to assign values from an array to a simple variable, my code is as follows :
$codeval = $_POST['code']; //can be Apple or Banana or Cat or Dog
$systemrefcode = array("a" => "Apple", "b" => "Banana", "C" => "Cat", "D" => "Dog");
foreach($systemrefcode as $code => $value) {
if($codeval == $value){ //if Apple exists in array then assign code and use it further
$codes = $code;//Assign code to codes to use in next step
}
$selection = 'Your Selection is -'.$codes.'and its good.';
echo $selection;
When I check in console it shows no response. What am I doing wrong?
You can get the key of the wanted value with array_search():
$array = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red');
$key = array_search('green', $array); // $key = 2;
So, for your code works, you can use like this:
$codeval = $_POST['code'];
$systemrefcode = array("a" => "Apple", "b" => "Banana", "C" => "Cat", "D" => "Dog");
$code = array_search($codeval, $systemrefcode);
$selection = 'Your Selection is - '.$code.' and its good.';
echo $selection;
OBS.:
array_search() will return false if value is not found;
array_search() is case sensitive, so if you have 'Apple' in the array and search for 'apple', it'll return false.
You can flip the $systemrefcode array so that the values become keys and vice versa.
$coderefsystem = array_flip($systemrefcode);
$codes = $coderefsystem($codeval);
$selection = 'Your Selection is -'.$codes.'and its good.';
echo $selection;
You could break out of the foreach when there is a match and echo the string afterwards.
$post = "1-Apple";
$codeval = explode('-', $post)[1];
$systemrefcode = array("a" => "Apple", "b" => "Banana", "C" => "Cat", "D" => "Dog");
$codes = "";
foreach ($systemrefcode as $code => $value) {
if ($codeval === $value) { //if Apple exists in array then assign code and use it further
$codes = $code;//Assign code to codes to use in next step
break;
}
}
if ($codes !== "") {
$selection = 'Your Selection is -' . $codes . ' and its good.';
echo $selection; // Your Selection is -a and its good.
} else {
echo "codes is empty";
}

detect where elements in array moved

I have to be able to detect all changes in a "guild" (like an activity feed), with new and old data. The data is presented in this fashion, as an array:
{"GUILDMASTER":["foo"],"OFFICER":["bar","baz"],"MEMBER":["foobar","foobaz"]}
I need to detect if, for example, "bar" moves from his current rank down one (to "MEMBER"), it will output an array something like this:
[{"user":"bar","was_found_in":"OFFICER","now_found_in":"MEMBER"}]
What I currently have, below, only detects if a member has joined of left, is there any way to extend this to fulfill what I want it to?
function compareThem($a, $b) {
$flat_new = call_user_func_array('array_merge', $a);
$flat_old = call_user_func_array('array_merge', $b);
$rm = array();
if($flat_new != $flat_old) {
$new_old = array_diff($flat_new, $flat_old);
$old_new = array_diff($flat_old, $flat_new);
$diff = array_merge($new_old, $old_new);
foreach ($diff as $key => $value) {
if(in_array($value, $flat_new) && !in_array($value, $flat_old)) {
$rm[] = array("new"=>true, "left"=>false, "user"=>$value);
} else if(in_array($value, $flat_old) && !in_array($value, $flat_new)) {
$rm[] = array("new"=>false, "left"=>true, "user"=>$value);
}
}
}
return $rm;
}
$new = array("GUILDMASTER" => array("foo"), "OFFICER" => array("bar", "baz"), "MEMBER" => array("foobar", "foobaz"));
$old = array("GUILDMASTER" => array("foo"), "OFFICER" => array("bar", "baz"), "MEMBER" => array("foobar"));
compareThem($new,$old) // will output [{"new":true,"left":false,"user":"foobaz"}]
You can do this. It detects additions, deletions, and modifications:
// your array before the change
$before = ["GUILDMASTER" => ["foo"], "OFFICER" => ["bar","baz"], "MEMBER" => ["foobar","foobaz"]];
// your array after the change (foo was moved from GUILDMASTER to OFFICER)
$after = ["GUILDMASTER" => [], "OFFICER" => ["bar","baz", "foo"], "MEMBER" => ["foobar","foobaz"]];
// create lookup table to check the position a person previously held
$positions_before = [];
foreach($before as $position => $people) {
foreach($people as $person) {
$positions_before[$person] = $position;
}
}
// scan new positions...
$changes = [];
foreach($after as $position => $people) {
foreach($people as $person) {
// track when a new person is added (they wont be in the table)
if(!isset($positions_before[$person])) {
$changes[] = ["user" => $person, "was_found_in" => "DIDNT_EXIST", "now_found_in" => $position];
// track when a change is detected (different value than table)
}elseif($positions_before[$person] != $position) {
$changes[] = ["user" => $person, "was_found_in" => $positions_before[$person], "now_found_in" => $position];
}
// remove this person from the table after parsing them
unset($positions_before[$person]);
}
}
// anyone left in the lookup table is in $before and $after
// so track that they were removed/fired
foreach($positions_before as $person => $position) {
$changes[] = ["user" => $person, "was_found_in" => $position, "now_found_in" => "REMOVED"];
}
print_r($changes);
outputs [user] => foo [was_found_in] => GUILDMASTER [now_found_in] => OFFICER

Create an array with dynamic input in PHP

I have to create a array dynamically like I have a method which will pass keys and based on those keys I need to create arrays inside them
Format will be like-
{
"TEST1":{
"140724":[
{
"A":"1107",
"B":4444,
"C":"1129",
"D":"1129"
},
{
"A":"1010",
"B":2589,
"C":"1040",
"D":"1040"
}
],
"140725":[
]
}
}
So how should I frame this logic inside for loop. I am new to php so formatting the same creating trouble.
$json_Created = array("TEST1" => array());
foreach($val as $key=>$value){
array_push($json_created,array($key = array()));
}
The entire array is dynamic, so like I have 140724 ,,, till 140731 (actually date format yymmdd), any amount if numbers can be considered. SO that part is dynamic moreover some dates may be wont have any values and some will have.
So my main point is to develop that logic so that irrespective the
number of inputs , my array formation must be intact.
You can use json_encode with array to do so
$array = array(
"TEST1" => array(
"140724" => array(
array(
"A" => "1107",
"B" => "4444",
"C" => "1129",
"D" => "1129"
),
array (
"A" => "1010",
"B" => "2589",
"C" => "1040",
"D" => "1040"
)
),
"140725" => array(
)
)
);
echo json_encode($array);
Another way to construct array is
$array = array();
$array["TEST1"]["140724"][] = array(
"A" => "1107",
"B" => "4444",
"C" => "1129",
"D" => "1129"
);
$array["TEST1"]["140724"][] = array (
"A" => "1010",
"B" => "2589",
"C" => "1040",
"D" => "1040"
);
$array["TEST1"]["140725"] = array();
echo json_encode($array);
Finally managed to write the code-
$keys_content = array("starttime", "id", "duration", "endtime");
$dates = array();//140724,140725,140726140727
$mainID =“TEST1”;
$arraySuperMain = array();
$arrayMain = array();
for ($j = 0; $j < count($dates); $j++) {
$array_main = array();
$subset = array();
for ($i = 0; $i < count($keys_content); $i++) {
$key = $keys_content[$i];
$subset = array_push_assoc($subset, $key, "Value".$i);
}
$array_main = array_push_assoc($array_main, $dates[$j], $subset);
array_push($arrayMain, $array_main);
}
$createdJSON = array_push_assoc($arraySuperMain, $mainID, $arrayMain);
public static function array_push_assoc($array, $key, $value) {
$array[$key] = $value;
return $array;
}

Categories