Adding a second dimension to an exisiting PHP array - php

Language: PHP 5
Framework: Joomla 3
I have a single dimension array that I get from get_file_array(). I need to break down the names of the files and eventually query the database for more information. For ease of iteration, I was hoping to add a new dimension to my array rather than create parallel arrays. I thought of iterating through the existing array and adding it one node at a time to a new, specifically multidimensional, perhaps associative array, but this seems inelegant. Is there a way to add a dimension to an existing array in PHP? Here's what I tried last, which obviously doesn't work but conveys the spirit of what I want to accomplish:
require_once "../components/com_esms/models/officelookup.php";
class filearray extends JApplicationCli
{
var $dir = null;
//var_dump($dir);
//error_log("filecopy CWD: ".getcwd());
//error_log("filecopy files: ".print_r($dir, true));
function __construct()
{
$this->dir = scandir("../esms_reports/", 0);
parent::__construct();
}
public function get_file_array()
{
//$this->out('Hello World');
unset($this->dir[0]);
unset($this->dir[1]);
$fa = array_values($this->dir);
return $fa;
}
}
$arr_filearray_obj = new filearray();
$dir = $arr_filearray_obj->get_file_array();
//error_log("filecopy files: ".print_r($dir, true));
/*
foreach($dir as $filename)
{
$fa_underscore = explode("_", $filename);
error_log("filecopy lotid: ".$fa_underscore[1]);
}
*/
//$officeid = array();
for($i = 0; $i < count($dir); $i++)
{
$fa_underscore = explode("_", $dir[$i]);
//error_log("filecopy lotid: ".$fa_underscore[1]);
//error_log("filecopy number of lots: ".$i);
$fa_dash = explode("-", $fa_underscore[1]);
$dir[i][1] = $fa_dash[1];
}
error_log("filecopy officeids: ".print_r($dir, true));
//$result = file_get_contents("http://192.168.1.250/systemname/index.php/option=com_esms&view=officelookup&format=json&officeID=".$officeid[0]);
$officelookup = new EsmsModelofficelookup();
for($o = 0; $o < count($dir); $o++)
{
$result = $officelookup->get_offices($dir[$o][1]);
}
error_log("filecopy JSON: ".$result);
echo("DONE!");
EDIT: Here's an example of the file names I am manipulating. The point is to get the client id, query it, check if it has a parent client id and make a folder structure based on that, using the client names, not IDs.
DDR_1426112931-429_031215.pdf or typeofreport_lotid-clientid_date.pdf
What I would want to add to the array would be the results of my database query, which is a JSON encoded structure containing the client's information and the parent client information, if it exists. It looks something like this:
Array
(
[id] => 123
[name] => Dummy
[parent] => 321
)
Array
(
[id] => 321
[name] => DummyParent
[parent] =>
)

Related

PHP - Create multidimensional recursive array from string (Files/folders structure)

Someone asked same question 10 years ago but there is no answer to that
Reference: Trying to create Array from String (file/folder structure)
Update Jan 8, 2022: This is how array and tree should look like:
https://3v4l.org/6ULBZ#v8.1.1
The SQL output should be formed into this array structure from a string.
[
'id' => $key //Increment from foreach loop,
'file' => $row['name'] //Name of folders and files
'parent_id' => 0 // 0 - if no subfolder, $id if there is subfolder
]
I want to return a tree-level multidimensional array from file list stored in database as a string.
For example I store my work related path in database like this:
SELECT name FROM files;
... SQL Output
2021/Dec/File1.doc
2021/Dec/File2.doc
2021/Dec/File3.doc
2021/Nov/File1.doc
2021/Nov/File2.doc
2021/Nov/File3.doc
2021/Nov/File4.doc
2020/Jan/File1.doc
2020/Jan/File2.doc
2020/Jan/File3.doc
... PHP recursive array output should be categorized by folders, subfolders, more subfolders if exists and files on end.
-2021
--Dec
---File1.doc
---File2.doc
---File3.doc
--Nov
---File1.doc
---File2.doc
---File3.doc
---File4.doc
-2020
--Jan
---File1.doc
---File2.doc
---File3.doc
What would be the best way to achieve this performance wise?
What I got so far...
$files = [];
foreach ($sql as $row)
{
// Separate directories
$separator = explode('/', $row['name']);
/* Output:
Array
(
[0] => 2021
[1] => Dec
[2] => file1.doc
)
*/
// Find the file via regex
if (preg_match('/[^\/]*\.(doc|txt)/', $row['name'], $o))
{
$row['name'] = $o[0]; //Output: file1.doc
}
$files[] = $row;
}
... For now I only have a file names, now I need directories as well and then make a multidimensional array from it.
$files = [];
foreach ($sql as $row)
{
// Separate directories
$separator = explode('/', $row['name']);
/* Output:
Array
(
[0] => 2021
[1] => Dec
[2] => file1.doc
)
*/
if (preg_match('/[^\/]*\.(doc|txt)/', $row['name']))
{
$node = &$files;
while (count($separator) > 1) {
$folder = array_shift($separator);
if (!array_key_exists($folder, $node)) {
$node[$folder] = [];
}
$node = &$node[$folder];
}
$node[] = $separator[0];
}
}
I have one solution for you, I hope this will help.
$files="2021/Dec/File1.doc,
2021/Dec/File2.doc,
2021/Dec/File3.doc,
2021/Nov/File1.doc,
2021/Nov/File2.doc,
2021/Nov/File3.doc,
2021/Nov/File4.doc,
2020/Jan/File1.doc,
2020/Jan/File2.doc,
2020/Jan/File3.doc";
$files=explode(',',$files);
foreach($files as $file)
{
$i=1;
$paths=explode('/',$file);
foreach($paths as $path)
{
for($j=0;$j<$i;++$j){
echo '-';
}
echo $path;
echo "<br/>";
++$i;
}
$i=0;
}
You can use your database files. by removing my files constant value and use your.

Convert PHP array from XML that contains duplicate elements

Up until now, I've been using the snippet below to convert an XML tree to an array:
$a = json_decode(json_encode((array) simplexml_load_string($xml)),1);
..however, I'm now working with an XML that has duplicate key values, so the array is breaking when it loops through the XML. For example:
<users>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
Is there a better method to do this that allows for duplicate Keys, or perhaps a way to add an incremented value to each key when it spits out the array, like this:
$array = array(
users => array(
user_1 => x,
user_2 => y,
user_3 => z
)
)
I'm stumped, so any help would be very appreciated.
Here is a complete universal recursive solution.
This class will parse any XML under any structure, with or without tags, from the simplest to the most complex ones.
It retains all proper values and convert them (bool, txt or int), generates adequate array keys for all elements groups including tags, keep duplicates elements etc etc...
Please forgive the statics, it s part of a large XML tools set I used, before rewriting them all for HHVM or pthreads, I havent got time to properly construct this one, but it will work like a charm for straightforward PHP.
For tags, the declared value is '#attr' in this case but can be whatever your needs are.
$xml = "<body>
<users id='group 1'>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
<users id='group 2'>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
</body>";
$result = xml_utils::xml_to_array($xml);
result:
Array ( [users] => Array ( [0] => Array ( [user] => Array ( [0] => x [1] => y [2] => z ) [#attr] => Array ( [id] => group 1 ) ) [1] => Array ( [user] => Array ( [0] => x [1] => y [2] => z ) [#attr] => Array ( [id] => group 2 ) ) ) )
Class:
class xml_utils {
/*object to array mapper */
public static function objectToArray($object) {
if (!is_object($object) && !is_array($object)) {
return $object;
}
if (is_object($object)) {
$object = get_object_vars($object);
}
return array_map('objectToArray', $object);
}
/* xml DOM loader*/
public static function xml_to_array($xmlstr) {
$doc = new DOMDocument();
$doc->loadXML($xmlstr);
return xml_utils::dom_to_array($doc->documentElement);
}
/* recursive XMl to array parser */
public static function dom_to_array($node) {
$output = array();
switch ($node->nodeType) {
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
$child = $node->childNodes->item($i);
$v = xml_utils::dom_to_array($child);
if (isset($child->tagName)) {
$t = xml_utils::ConvertTypes($child->tagName);
if (!isset($output[$t])) {
$output[$t] = array();
}
$output[$t][] = $v;
} elseif ($v) {
$output = (string) $v;
}
}
if (is_array($output)) {
if ($node->attributes->length) {
$a = array();
foreach ($node->attributes as $attrName => $attrNode) {
$a[$attrName] = xml_utils::ConvertTypes($attrNode->value);
}
$output['#attr'] = $a;
}
foreach ($output as $t => $v) {
if (is_array($v) && count($v) == 1 && $t != '#attr') {
$output[$t] = $v[0];
}
}
}
break;
}
return $output;
}
/* elements converter */
public static function ConvertTypes($org) {
if (is_numeric($org)) {
$val = floatval($org);
} else {
if ($org === 'true') {
$val = true;
} else if ($org === 'false') {
$val = false;
} else {
if ($org === '') {
$val = null;
} else {
$val = $org;
}
}
}
return $val;
}
}
You can loop through each key in your result and if the value is an array (as it is for user that has 3 elements in your example) then you can add each individual value in that array to the parent array and unset the value:
foreach($a as $user_key => $user_values) {
if(!is_array($user_values))
continue; //not an array nothing to do
unset($a[$user_key]); //it's an array so remove it from parent array
$i = 1; //counter for new key
//add each value to the parent array with numbered keys
foreach($user_values as $user_value) {
$new_key = $user_key . '_' . $i++; //create new key i.e 'user_1'
$a[$new_key] = $user_value; //add it to the parent array
}
}
var_dump($a);
First of all this line of code contains a superfluous cast to array:
$a = json_decode(json_encode((array) simplexml_load_string($xml)),1);
^^^^^^^
When you JSON-encode a SimpleXMLElement (which is returned by simplexml_load_string when the parameter could be parsed as XML) this already behaves as-if there would have been an array cast. So it's better to remove it:
$sxml = simplexml_load_string($xml);
$array = json_decode(json_encode($sxml), 1);
Even the result is still the same, this now allows you to create a subtype of SimpleXMLElement implementing the JsonSerialize interface changing the array creation to your needs.
The overall method (as well as the default behaviour) is outlined in a blog-series of mine, on Stackoverflow I have left some more examples already as well:
PHP convert XML to JSON group when there is one child (Jun 2013)
Resolve namespaces with SimpleXML regardless of structure or namespace (Oct 2014)
XML to JSON conversion in PHP SimpleXML (Dec 2014)
Your case I think is similar to what has been asked in the first of those three links.

Create PHP associative array using explode Codeigniter

I am a newbie in Codeigniter and created a form to input list of website from user where user either can insert website urls in a textarea separated by line or upload csv file contain a header named websites. I am using codeigniter library CSVReader to read data from csv and create an array like this:
Array ( [0] => Array ( [websites] => www.google.com ) [1] => Array ( [websites] => www.bing.com ) )
while if I try to convert array from PHP explode function (if user input websites through textarea) Array looks like this:
Array(www.google.com,www.bing.com)
Code of my CSVReader is as:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class CSVReader {
var $fields; /** columns names retrieved after parsing */
var $separator = ';'; /** separator used to explode each line */
var $enclosure = '"'; /** enclosure used to decorate each field */
var $max_row_size = 4096; /** maximum row size to be used for decoding */
/**
* Parse a file containing CSV formatted data.
*
* #access public
* #param string
* #param boolean
* #return array
*/
function parse_file($p_Filepath, $p_NamedFields = true) {
$content = false;
$file = fopen($p_Filepath, 'r');
if($p_NamedFields) {
$this->fields = fgetcsv($file, $this->max_row_size, $this->separator, $this->enclosure);
}
while( ($row = fgetcsv($file, $this->max_row_size, $this->separator, $this->enclosure)) != false ) {
if( $row[0] != null ) { // skip empty lines
if( !$content ) {
$content = array();
}
if( $p_NamedFields ) {
$items = array();
// I prefer to fill the array with values of defined fields
foreach( $this->fields as $id => $field ) {
if( isset($row[$id]) ) {
$items[$field] = $row[$id];
}
}
$content[] = $items;
} else {
$content[] = $row;
}
}
}
fclose($file);
return $content;
}
}
Could someone please help me create array in same styles?
I don't think you need a whole big CSV reader to do that. Also, CSV is meant more for tabular data (if you had multiple columns of data), you're just building a flat list of websites - which greatly simplifies things. I would recommend something like this;
$websites = "foo,bar\nbaz,qux"; // Input
$output = preg_split('#[\n\r\t,]+#', $websites); // Split by newlines, feeds, tabs, and commas
foreach ($output as &$o)
$o = array('website' => trim($o));
// Or a shorter form for PHP 5+
foreach ($output as &$o)
$o = ['website' => trim($o)];
If you're getting the data from a form, you can completely strip out the CSV code unless you expect to be handling quotes or CSV file uploads. In that case, you only need one of the loops to make the array associative.
If the data is a multidimensional array of values you want to make associative, you can use the following loop (I made up the columns, they can be whatever you want):
// For PHP 4-
foreach ($output as &$o)
$o = array( 'website' => $o[0], 'email' => $o[1], 'foo' => $o[2], 'bar'=>$o[3] );
// Or a shorter form for PHP 5+
foreach ($output as &$o)
$o = [ 'website' => $o[0], 'email' => $o[1], 'foo' => $o[2], 'bar'=>$o[3] ];
EDIT: Added trimming in first sample code.
You want both arrays to match, but you did not specify which style you want the arrays to end up in.
If you want to convert the CSV array do the following:
foreach($csv_array as $website) {
$website_array[] = $website['websites'];
}
If you want to convert the textarea array do the following:
$count = 0;
foreach($textarea_input as $website) {
$website_array[$count]['websites'] = $website;
$count++;
}
I think the first one is easier to manage, there's not reason to add a second layer to your array, better to extract your data into a simpler array from the array that has multiple layers.

Moving single array to multi-dimension array

My problem.. I have a PHP array that looks like this:
[1013] => [1154]
[1013] => [1322]
[1154] => [1525]
[1525] => [1526]
How can I take that and move it to something like this:
[1013] => [1154] => [1525] => [1526]
[1013] => [1322]
So it sort of makes a tree that associates to the top level array item. I do not have control over how the data comes to me, it's generated through a third-party API and given to me just like that.
Logic: Client 1013 is the main account. Client 1154 is a client of 1013. Client 1322 is a client of 1013. Client 1525 is a client of 1154. I want to get it to a multi-dimension array so I can show this in a tree format.
Here you go!:
<?php
// dataset
$clientset = array(
array(1013, 1154),
array(1013, 1322),
array(1154, 1525),
array(1525, 1526)
);
$children = array();
// make an array with children to see which nodes have none
foreach($clientset as $set) {
if(!isset($children[$set[0]])) $children[$set[0]] = array($set[1]);
else $children[$set[0]][] = $set[1];
}
// array with parents
$parents = array();
foreach($clientset as $set) {
$parents[$set[1]] = $set[0];
}
// for each node with no children, begin the search!
foreach($clientset as $set) {
if(!isset($children[$set[1]])) {
echo getPath($set[1]).'</br>';
}
}
// recursively search to parents and print them
function getPath($child) {
global $parents;
if($parents[$child]) {
return (getPath($parents[$child]).' => '.$child);
} else return $child;
}
?>
This outputs:
1013 => 1322
1013 => 1154 => 1525 => 1526
The idea is to see which nodes have no children. Then, iterate through their parents. You probably don't need the output like it is right now, but I'm sure you can work it out this way. Enjoy!
You can use array_walk php function to apply callback to each element of source array. Callback should create a new array according to your requirements. Callback function will take 2 parameters: value of current array element and it's key. Using that it's simple to build array you need.
Chris, you should have just emailed me first. :-p
$test_array = array('1','2','3','4','5','6');
$output_string = '';
for ($i = 0; $i < sizeof($test_array) -1; $i++)
{
$output_string .= '{"'.$test_array[$i].'":';
}
$output_string .= $test_array[$i];
for ($i = 0; $i < sizeof($test_array)-1; $i++) { $output_string .= '}'; }
$new_array = json_decode($output_string, true);
var_dump($new_array);

Using func_get_args to edit an array

I wish to use a function with an arbitrary number of arguments to edit an array. The code I have so far is:
function setProperty()
{
$numargs = func_num_args();
$arglist = func_get_args();
$toedit = array();
for ($i = 0; $i < $numargs-1; $i++)
{
$toedit[] = $arglist[$i];
}
$array[] = $arglist[$numargs-1];
}
The idea of the code being I can do the following:
setProperty('array', '2nd-depth', '3rd', 'value1');
setProperty('array', 'something', 'x', 'value2');
setProperty('Another value','value3');
Resulting in the following array:
Array
(
[array] => Array
(
[2nd-depth] => Array
(
[3rd] => value1
)
[something] => Array
(
[x] => value2
)
)
[Another Value] => value3
)
The issue I believe is with the line:
$toedit[] = $arglist[$i];
What does this line need to be to achieve the required functionality?
Cheers,
You need to walk the path to the destination before storing the new value. You can do this with a reference:
function setProperty() {
$numargs = func_num_args();
if ($numargs < 2) return false; // not enough arguments
$arglist = func_get_args();
// reference for array walk
$ar = &$array;
// walk the array to the destination
for ($i=0; $i<$numargs-1; $i++) {
$key = $arglist[$i];
// create array if not already existing
if (!isset($ar[$key])) $ar[$key] = array();
// update array reference
$ar = &$ar[$key];
}
// add value
$ar = $arglist[$numargs-1];
}
But the question where this $array should be stored still remains.
class foo {
private $storage;
function setProperty()
{
$arglist = func_get_args();
if(count($argslist) < 2) return false;
$target = &$this->storage;
while($current = array_shift($arglist)){
if(count($arglist)==1){
$target[$current] = array_shift($arglist);
break;
}
if(!isset($target[$current])) $target[$current] = array();
$target = &$target[$current];
}
}
}
Try using foreach to loop through your array first. Then to handle children, you pass it to a child function that will grab everything.
I also recommend using the function sizeof() to determine how big your arrays are first, so you'll know your upper bounds.

Categories