How to generate a unique id in a recursive function in PHP? - php

In my recursive function I can't generate a unique id (via an incremented variable) for my multidimensional array, here is what I tried.
I need to assign a unique ID for each directory name I find: $data[] = ['id'=>$i,'text'=>$dir];
function scandir_rec($root, $i)
{
$data = [];
if (!is_dir($root)) {
return;
}
$dirs = scandir($root);
foreach ($dirs as $dir) {
$i++;
if ($dir == '.' || $dir == '..') {
continue;
}
$path = $root . '/' . $dir;
if (is_file($path)) {
continue;
}
if (is_dir($path)) {
$nodes = scandir_rec($path, $i); // <--- unique ID
if (!empty($nodes)) $chunk['children'] = $nodes;
}
$data[] = [ 'id' => $i, 'text' => $dir ]; // <--- unique ID
}
return $data;
}
$rootDir = '/var/www/html';
$i = 0;
$json_data = json_encode(scandir_rec($rootDir, $i), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); // <--- unique ID
file_put_contents('tree.json', $json_data);
Desired result
[
{
"id":1,
"text":"Root folder",
"children":[
{
"id":2,
"text":"Child folder 1"
},
{
"id":3,
"text":"Child folder 2"
}
]
},
{
"id":4,
"text":"Root folder"
},
{
"id":5,
"text":"Root folder",
"children":[
{
"id":6,
"text":"Child folder 1"
},
{
"id":7,
"text":"Child folder 2"
}
]
}
]

Personally I hate returning from recursive functions, it tends to get weird unless you are returning a status for the caller to do something with. Instead, it is often easier to use byref parameters.
I've commented the code below, hopefully it makes sense. We're passing two parameters byref, one is "the current array to add stuff to" and the other is "the global index". We're also keeping track of depth because you are treating the root differently than children.
function scandir_rec(string $root, array &$current_parent, int &$global_index, int $depth = 0): void
{
// Bail early if we don't have a directory
if (!is_dir($root)) {
return;
}
// Get the children
$dirs = scandir($root);
// This is the local child index and resets for successive calls
$local_index = 1;
foreach ($dirs as $dir) {
// Once again, bail early if we can
if ($dir === '.' || $dir === '..') {
continue;
}
// The next path to scan
$next_path = $root . '/' . $dir;
// Make sure it isn't a file
if (is_file($next_path)) {
continue;
}
// Everyone gets this
$current_node = [
// Set the ID and increment so that the next gets a bigger one
'id' => $global_index++,
// Root node is treated differently thant children
'text' => 0 === $depth ? 'Root node' : "Child node ${local_index}",
];
// If the next node is a directory
if (is_dir($next_path)) {
// Create a temporary holder for the contents
$children = [];
// Scan again, incrementing the depth by one
scandir_rec($next_path, $children, $global_index, ++$depth);
// If the above did something to our array, append to our local (the master)
if ($children) {
$current_node['children'] = $children;
}
}
// Append the current node
$current_parent[] = $current_node;
}
}
To call this, remember that it doesn't return so we need to run it by itself, We also want to pass the global index in by ref so we need to create a variable to hold it.
$current_parent = [];
$global_index = 1;
scandir_rec('/var/www', $current_parent, $global_index);
$json_data = json_encode($current_parent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
This code is written against PHP 7.4 do you might need to tweak a couple of things if you are on an older version.
edit
In hindsight, I was taking your text too literally, I think. You can probably get rid of the $depth parameter completely and just set your text to:
'text' => $root,
I'm running this from Windows and I just ran a quick test folder which produced:
[
{
"id": 1,
"text": "/Dell",
"children": [
{
"id": 2,
"text": "/Dell/25tgw",
"children": [
{
"id": 3,
"text": "/Dell/25tgw/RealtekHDAudio"
},
{
"id": 4,
"text": "/Dell/25tgw/RealtekHDAudio"
},
{
"id": 5,
"text": "/Dell/25tgw/RealtekHDAudio"
},
{
"id": 6,
"text": "/Dell/25tgw/RealtekHDAudio"
}
]
}
]
},
{
"id": 7,
"text": "/Dell",
"children": [
{
"id": 8,
"text": "/Dell/UpdatePackage"
}
]
},
{
"id": 9,
"text": "/Dell"
}
]

The issue is that each recursive call starts it's own count. Let's say $i is 10 inside the loop at a given time. You start a recursive call that increases it up to 15 but when the call ends and returns to parent function, $i there is still 10.
Perhaps the simplest fix is to pass the counter by reference:
function scandir_rec($root, &$i)
{
}
Another solution is to avoid recursion altogether. PHP has builtin iterators that can walk a three without recursion, e.g.:
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootDir),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($files as $name => $object) {
echo sprintf("[%s] %s\n", $object->isDir() ? 'DIR' : ' ', $name);
}

Related

Find a specific word in an object using strpos in php

I'm trying to set up a test question that will find certain words in a short answer. The words, which will mark the answer as correct, will be stored as values in an object. I was trying to figure out how to do it with strpos(), but every alternative that I come up with gives me a blank screen.
PHP:
$myJSON = file_get_contents('quiz.json');
$json = json_decode($myJSON);
foreach($json as $value) {
foreach($value->answer as $index => $options) {
$findme = "application";
$pos = strpos($options, $findme);
if ($pos === true) {
echo $options;
//echo $value->text->type->answer;
//echo ($index. ' '. $options . '<br>');
//echo current($value);
}
}
}
JSON:
{
"question1": {
"text": "What are the two types of permission lists that DSA concentrates on?",
"type": "short_answer",
"answer": {
"1": "application",
"2": "row-level"
}
},
"question2": {
"text": "What are the building blocks for EmpowHR Security?",
"type": "short_answer",
"answer": {
"1": "permission lists"
}
},
"question3": {
"text": "Who is the bomb?",
"type": "short_answer",
"answer": {
"1": "permission"
}
}
}
Tested the following using your given JSON file.
Important: First I added the , true to $json = json_decode($myJSON); --> $json = json_decode($myJSON, true); This turns the obj into an array
After var_dumping the json encoded I noticed you had mixed string and array types in the level you were trying to parse, so used in_array() to filter out the strings and only iterate through the arrays and was able to locate all instances of the "answers" section in its current build within that obj.
$stmt = NULL;
$find = "application";
$myJSON = file_get_contents('quiz.json');
$json = json_decode($myJSON, true);
foreach( $json as $content ){
foreach( $content as $target){
if(is_array($target)){
// we must find the key of the value within the next level of the array
// and use it as index for the value $target to use in strpos() --> $target[$index]
foreach($target as $index => $value){
if(strpos($target[$index], $find) !== false){
$stmt = '<span>'.$target[$index].': CORRECT</span>';
}
}
}
}
}
echo $stmt;
foreach( $json as $question=>$content ){
foreach( $content as $key=>$value ){
if( strpos( $value, 'applikation' ) !== false
echo $value;
}
}
}
strpos returns the found position and false if not found.
returnvalue === true: allways false
returnvalue == true: false if not found or found on first position (0), true if found after first position (all numbers != 0 are true)
returnvalue !== false: correct result

How to check a JSON object Boolean value that then creates a webpage from a php template if True

I have a json file.
{
"whitepapers": {
"1": {
"name": "The Title of the Doc",
"file": "the-title-of-the-doc",
"asset_description": "blah blah",
"image": "/img.png",
"formId": "xxx",
"confirmation": true
},
"0": {
"name": "The Title of the Doc 2",
"file": "the-title-of-the-doc-2",
"asset_description": "blah blah",
"image": "/img.png",
"formId": "xxx",
"confirmation": true
}
},
there is a page-handler.php that already takes information from this json file and creates pages from them:
if (strpos($shortPage, 'whitepapers') !== false) { // test for Whitepapers overview page
require $contentPath . 'resources_whitepapers.php';
} else if (($aPosition=strpos($shortPage, 'whitepaper')) !== false) { // test for whitepaper asset page & grab where in string if positive
$aString = mb_substr($shortPage, ($aPosition + 10)); // strip everything from the string up to the end of the found string
$aString = ltrim($aString, '/'); // remove the preceding "/" if it exists
$json_string = $contentPath . 'asset-manifest.json'; // point to the asset manifest list [in json format] TODO: error handling here
$jsondata = file_get_contents($json_string); // load it into memory
$obj = json_decode($jsondata,true); // decode it into an array
foreach ($obj['whitepapers'] as $key => $value) { // Find the parent of our string's key/value pair in the multidimensional json array
foreach ($value as $_key => $_value) {
if ($_key === 'file' && $_value === $aString) { // look for the asset string value in the 'file' key
$match = $key; // found a match, grab the pair's parent array
}}}
if ($match >= 0) { // found a match in the manifest, render the page
require $contentPath . 'whitepaper-template.php';
} else { // can't find a match in the manifest
include $errorPagePath; // return a 404 page and handle the error
}
} else if....
The dev who made this originally is no longer here so I can't ask him to walk me through it.
I want to be able to check if "confirmation": true and then funnel into a confirmation-temaplte.php like how the above funnels into a whitepaper-template.php and then else "confirmation": false it doesn't do anything
I've tried copying the code over from page-handler.php into a else if ("confirmation":true){blah} and making a confirmation-template.php but I wasn't sure if I was targeting "confirmation" correctly.
Then I noticed it started messing up the other pages that are dependent on the json file. It seems when I make a confirmation-template.php page it messes up the other pages using a xxx-template.php file and I'm unsure why.
As you see, I'm a bit new at PHP Templating and JSON information access. Thanks for the help
foreach ($obj['whitepapers'] as $key => $value) { // Find the parent of our string's key/value pair in the multidimensional json array
if ($value['confirmation'] ?? false) {
// here Is your confirmation === true
}
foreach ($value as $_key => $_value) {
if ($_key === 'file' && $_value === $aString) { // look for the asset string value in the 'file' key
$match = $key; // found a match, grab the pair's parent array
}}}

Variable for the dimensions of a multivariable array

I am trying to generalize some code in a function so that I can read different JSON formatted input and determine system information from each. For brevity, I am not including all of the code. In the actual code, I am retrieving the value of $length from a database.
Here is an example:
function readHostname($json, $length) {
$content = json_decode($json, true);
$hostname = $content[$length];
}
$json = file_get_contents($url1, false, $context);
$length = "[0]['cluster']['nodes'][0][hostName]";
echo readHostname($json, $length);
$json = file_get_contents($url2, false, $context);
$length = "[0]['components']['serviceName']";
echo readHostname($json, $length);
For reference url1 would return JSON such as:
[
{
"cluster": {
"nodes": [
{ "name": "cluster1",
"hostName": "alpha"
},
{ "name": "cluster2",
"hostName": "beta"
}
]
}
},
{
"cluster": {
"nodes": [
{ "name": "prod_cluster1",
"hostName": "oscar"
},
{
"name": "prod_cluster2",
"hostName": "delta"
}
]
}
}
]
and url2 would return json:
[
{
"compenents": {
"serviceName" : "hostname1",
"environment" : "produciton"
}
}
]
You want flexible access to nested array structures. Replace your readHostname function with this:
function readHostname($json, $length) {
$content = json_decode($json, true);
preg_replace_callback('/\[([^]]+)\]+/', function($m) use(&$content) {
$index = $m[1];
$content = $content[preg_match('/[0-9]+/', $index) ? intval($index) : trim($index, "'\"")];
}, $length);
return $content;
}
You can abstract the process and use recursion.
function getHost($payload,$hostName){
$array = is_array($payload) ? $payload : json_decode($payload,true);
if(json_last_error() != JSON_ERROR_NONE){
return;
}
foreach(array_keys($array) as $key){
if($array[$key] == $hostName){
unset($array[$key]);
return $array[key($array)];
}
}
return getHost($array[key($array)],$hostName);
}
echo getHost($payload,"cluster1");
echo getHost($payload,"hostname1");

Getting first object from a JSON file

How do I get access to the "thumburl" if I do not know the "pageid"?
{
"continue": {
"iistart": "2004-12-19T12:37:26Z",
"continue": "||"
},
"query": {
"pages": {
"30260": {
"pageid": 30260,
"ns": 6,
"title": "File:Japanese diet inside.jpg",
"imagerepository": "local",
"imageinfo": [
{
"thumburl": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Japanese_diet_inside.jpg/130px-Japanese_diet_inside.jpg",
"thumbwidth": 130,
"thumbheight": 95,
"url": "https://upload.wikimedia.org/wikipedia/commons/e/e1/Japanese_diet_inside.jpg",
"descriptionurl": "https://commons.wikimedia.org/wiki/File:Japanese_diet_inside.jpg",
"descriptionshorturl": "https://commons.wikimedia.org/w/index.php?curid=30260"
}
]
}
}
}
}
With multiple objects in php I can do this imageinfo[0] but If I put $imageurl = $data->query->pages[0]->imageinfo[0]->thumburl; It is not working because it is an object not an array.
How can I do that?
You can call get_object_vars to get an associative array of the object properties, then get the first of these.
$props = array_values(get_object_vars($data->query->pages));
$imageurl = $props[0]->imageinfo[0]->thumburl;
You could use reset() to get the first element :
$data = json_decode($json) ;
$elem = reset($data->query->pages) ;
$imageurl = $elem->imageinfo[0]->thumburl ;
Another alternative is to decode as an array and re-index so it always starts at 0:
$result = array_values(json_decode($json, true)['query']['pages'])[0];
Then you can access $result['imageinfo']['thumburl'] or append it to the above.
You can loop through it, so you don't need array key like this:
$pages = (array) $data->query->pages;
foreach($pages as $page) {
$imageinfo = $page->imageinfo[0]->thumburl;
}
But this will get you only last from list of pages. So in case you know there are more pages, you need to store these thumburl in array. Or in case you are sure you want only first, just exit after first loop.

PHP array check if value in specific key exists

I am using PHP 5.5.12.
I have the following multidimensional array:
[
{
"id": 1,
"type":"elephant",
"title":"Title of elephant"
},
{
"id": 2,
"type":"tiger",
"title":"Title of tiger"
},
{
"id": 3,
"type":"lion",
"title":"Title of lion",
"children":[{
"id": 4,
"type":"cow",
"title":"Title of cow"
},
{
"type":"elephant",
"title":"Title of elephant"
},
{
"type":"buffalo",
"title":"Title of buffalo"
}]
}
]
I am iterating this array using foreach loop.
The array key type must be in elephant, tiger and lion. If not, then the result should return false.
How can I achieve this?
So you want to check if your $myArray contains a value or not:
// first get all types as an array
$type = array_column($myArray, "type");
// specify allowed types values
$allowed_types = ["lion", "elephant", "tiger"];
$count = count($type);
$illegal = false;
// for loop is better
for($i = 0; $i < $count; $i++)
{
// if current type value is not an element of allowed types
// array, then both set the $illegal flag as true and break the
// loop
if(!in_array($type[$i], $allowed_types)
$illegal = true;
break;
}
Since you're using PHP5.5.12, you can make use of array_column.
$arr = json_decode($json, true);
//Walk through each element, only paying attention to type
array_walk( array_column($arr, 'type'), function($element, $k) use(&$arr) {
$arr[$k]['valid_type'] = in_array($element, array('lion', 'tiger', 'elephant'));
});
From here, each element in the array ($arr) will have a new key valid_type with a boolean value - 1 if the type is valid, 0 if it isn't.
https://eval.in/350322
Is this something that you are looking for?
foreach($your_array as $item) {
if (!array_key_exists('type', $item)) {
return FALSE;
}
}
function keyExists($arr, $key) {
$flag = true;
foreach($arr as $v) {
if(!isset($v[$key])) {
$flag = false;
break;
}
}
return $flag;
}
Hope this helps :)

Categories