Why doesn't PHP increment array index properly? - php

Let's say I have this array:
array(12) {
[0] =>
array(0) {
}
// [...]
[9] =>
string(5) "test"
[10] =>
array(0) {
}
[11] =>
class stdClass#5 (0) {
}
}
All items in this array where added like this $a[] = $somevalue.
Now I add another item: $a[] = new Foobar('bar');
However this results in:
array(13) {
[0] =>
array(0) {
}
// [...]
[9] =>
string(5) "test"
[10] =>
array(0) {
}
[11] =>
class stdClass#5 (0) {
}
[21] =>
class Foobar#8 (3) {
protected $id =>
string(11) "bar"
}
}
My Foobar object is not $a[12], why? Is there anything I can do such that PHP iterates properly?
Update:
I was not able to reproduce this behaviour in a single file. Unfortunately, a whole framework is involved in my code. As I'm only in control of the last statement ($a[] = new Foobar('id');), can I do something before that expression to force PHP to iterate properly?

I'm not sure if you mean this, but here it goes: PHP arrays do not have to be indexed in the standard way; If you unset 12-20, the last item (namely 21 will not have its index changed). You can also have strings as keys; it's more like a key-value pair than a standard table you might find in other languages. You can use foreach to iterate over an array.
$a = array(
0 => 2,
21 => 30,
"foo" => "bar"
);
foreach ($a as $val)
{
echo $val;
echo "\n";
}
will print:
2
30
bar

Related

How to get hyrarchy from key elements and create new elements based on that - PHP

I have a very big array, I will try to explain the issue in small examples:
Input:
Array (
[alert:accountDisabled:heading] => XYZ
[alert:accountDisabled:message] => XYZ
[alert:accountExpired:heading] => XYZ
[alert:accountExpired:message] => XYZ
[alert:errorResponse:heading] => XYZ
[button:back] => XYZ
)
What I need to get is:
array() {
["alert"]=> array(7) {
["accountDisabled"]=> array(2) {
["heading"]=> string(3) "XYZ"
["message"]=> string(3) "XYZ" }
["accountExpired"]=> array(2) {
["heading"]=> string(3) "XYZ"
["message"]=> string(3) "XYZ" }
["clientError"]=> array(2) {
["heading"]=> string(3) "XYZ"
["message"]=> string(3) "XYZ" }
["errorResponse"]=> array(1) {
["heading"]=> string(3) "XYZ" }
}
["button"]=> array(1) {
["back"]=> string(3) "XYZ"
}
As I said this is a very small example, but the point is to get hierarchy from keys from array number one, hierarchy is divided by this character in key :
I checked for those questions that look similar to this one but they are not helpful lat all
How to access and manipulate multi-dimensional array by key names / path?
Using a string path to set nested array data
SO please read carefully the description of my issue.
I tried to use it for each loop, and I succeed to divide elements from the key, for one element, but I'm not sure where I need to store those hierarchy values for the next elements, any ideas?
$input = [
'alert:accountDisabled:heading' => 'XYZ_1',
'alert:accountDisabled:message' => 'XYZ_2',
'alert:accountExpired:heading' => 'XYZ_3',
'alert:accountExpired:message' => 'XYZ_4',
'alert:errorResponse:heading' => 'XYZ_5',
'button:back' => 'XYZ_6'
];
$results = [];
foreach ($input as $key => $value) {
$arr = explode(':', $key);
$result = $value;
for ($i = count($arr) - 1; $i >= 0; $i--) {
$result = [ $arr[$i] => $result ];
}
$results[] = $result;
}
$result = array_merge_recursive(...$results);
print_r($result);
Output:
Array
(
[alert] => Array
(
[accountDisabled] => Array
(
[heading] => XYZ_1
[message] => XYZ_2
)
[accountExpired] => Array
(
[heading] => XYZ_3
[message] => XYZ_4
)
[errorResponse] => Array
(
[heading] => XYZ_5
)
)
[button] => Array
(
[back] => XYZ_6
)
)
Based on Lukas.j answer, you can use this function:
function parsePath($array, $separator = ':'){
$result = [];
foreach($array as $key => $value){
if(strpos($key, $separator) !== FALSE){
$keys = explode($separator, $key);
$inner_result = $value;
foreach (array_reverse($keys) as $valueAsKey) $inner_result = [$valueAsKey => $inner_result];
$result[] = $inner_result;
}
}
return array_merge_recursive(...$result);
}

Change key of entries in PHP array

I'm currently trying to build a complex function to re-order the positions of some sections (div's) which are defined in a PHP array.
So for example I've this array here:
$sections = array(
0 => 'section_one',
1 => 'section_two',
2 => 'section_three',
3 => 'section_four',
4 => 'section_five',
5 => 'section_six',
6 => 'section_seven'
);
Which turns out like this:
array(7) { [0]=> string(11) "section_one" [1]=> string(11) "section_two" [2]=> string(13) "section_three" [3]=> string(12) "section_four" [4]=> string(12) "section_five" [5]=> string(11) "section_six" [6]=> string(13) "section_seven" }
When the user moves now the section six before section two on my website, I need to change the key of section six to 1 and move every key one number up after the moved section. So section_two becomes the key 2 and so on...
Any idea how I can do this? I know that I can replace keys this way:
$arr[ $newkey ] = $arr[ $oldkey ];
unset( $arr[ $oldkey ] );
When the user finished a move of an element, I know the name like section_six and the new position of the element.
After re-ordering / re-placing the keys, the array must look like this:
$sections_a = array(
0 => 'section_one',
1 => 'section_six',
2 => 'section_two',
3 => 'section_three',
4 => 'section_four',
5 => 'section_five',
6 => 'section_seven'
);
One option is to copy the array and use array_splice
$sections = array(
0 => 'section_one',
1 => 'section_two',
2 => 'section_three',
3 => 'section_four',
4 => 'section_five',
5 => 'section_six',
6 => 'section_seven'
);
$oldkey = 5;
$newkey = 1;
$sections_a = $sections;
array_splice( $sections_a, $newkey, 0, array_splice( $sections_a, $oldkey, 1) );
This will result to:
Array
(
[0] => section_one
[1] => section_six
[2] => section_two
[3] => section_three
[4] => section_four
[5] => section_five
[6] => section_seven
)
I see #Eddie has given a better solution than mine thanks Eddie,
but here is the custom function I made for this if it helps anyone.
// This function returns the new array
function reindexArray($array, // Array
$index, // Index of the value that you to change the index of
$indexToMove) // Index that you want to move that value
{
// Store the value to be reindex in a variable
$reIndexedValue = $array[$index];
// Remove that value from the array
unset($array[$index]);
// Reorder the original array
$oldArrayInOrder = array_values($array);
// Create a new array
$newArray = array();
// Now reindex all the value into the new array in the proper order
for ($j=0, $i=0; $i < count($oldArrayInOrder)+1; $i++) {
if($i == $indexToMove)
{
$newArray[$i]=$reIndexedValue;
continue;
}
else
{
$newArray[$i] = $oldArrayInOrder[$j];
$j++;
}
}
return($newArray);
}

Create multidimensional array from flat file array

I have an array like this:
$arr = array(
'home.js' => new File(),
'view/index.html' => new File(),
'src/index.js' => new File(),
'src/libs/jquery.js' => new File()
);
Now I want to convert in a structure like this:
Array
(
[0] => Array
(
[text] => home.js
)
[1] => Array
(
[text] => view
[children] => Array
(
[0] => Array
(
[text] => index.html
)
)
)
[2] => Array
(
[text] => src
[children] => Array
(
[0] => Array
(
[text] => index.js
)
[1] => Array
(
[text] => libs
[children] => Array
(
[0] => Array
(
[text] => jquery.js
)
)
)
)
)
)
I tried for hours, with help of StackOverfow answers but I couldn't come up with a solution as all other questions have a different setup.
Edit:
What I got so far with the help of SO is (can't remember the exact answer though):
$out = array();
foreach($arr as $path => $file) {
$parts = explode('/', trim($path, '/'));
applyChain($out, $parts, $file);
}
function applyChain(&$arr, $parts, $value)
{
if (!is_array($parts)) {
return;
}
if (count($parts) == 0) {
$arr = $value;
} else {
array_shift($parts);
applyChain($arr[], $parts, $value);
}
}
print_r($out);
I don't know how exactly it works, especially the part applyChain($arr[] ...). It kinda works with the depth, but not with the file names. I get following output:
Array
(
[0] => File Object
(
)
[1] => Array
(
[0] => File Object
(
)
)
[2] => Array
(
[0] => File Object
(
)
)
[3] => Array
(
[0] => Array
(
[0] => File Object
(
)
)
)
)
There would be a solution in a few lines using explode() and eval(). But eval() is not considered clean, so lets try recursion:
<?php
class File {
}
$arr = array(
'home.js' => new File(),
'view/index.html' => new File(),
'src/index.js' => new File(),
'src/libs/jquery.js' => new File()
);
function sub($path) {
$rv = array();
$parts = explode('/', $path, 2); // strip off one level
$rv['text'] = $parts[0]; // put it into 'text' element
if (count($parts)>1) // is there anything left?
$rv['children'] = sub($parts[1]); // do the same for the rest of the path
return $rv;
}
$new = array();
foreach (array_keys($arr) as $file) {
$new[] = sub($file);
}
var_dump($new);
?>
But, as Peter commented, this creates seperate substructures even if the pathes have some part in common (like src/libs/jquery.js and src/libs/melon.js).
With the use of ugly eval() (which can be replaced later) I got the following code:
<?php
class File {
}
$arr = array(
'home.js' => new File(),
'view/index.html' => new File(),
'src/index.js' => new File(),
'src/libs/jquery.js' => new File(),
'src/libs/melon.js' => new File(),
);
// conversion
function sub($element) {
$rv = array();
foreach (array_keys($element) as $sub) {
$part['text'] = $sub;
if (is_array($element[$sub])) {
$part['children'] = sub($element[$sub]);
}
$rv[] = $part;
}
return $rv;
}
// create array with path file/folder names as keys
$new = array();
foreach (array_keys($arr) as $row) {
$def = '$new["'.preg_replace('&/&', '"]["', $row).'"] = 1;';
eval($def);
}
// run
$new2 = sub($new);
var_dump($new2);
?>
This outputs
array(3) {
[0]=>
array(1) {
["text"]=>
string(7) "home.js"
}
[1]=>
array(2) {
["text"]=>
string(4) "view"
["children"]=>
array(1) {
[0]=>
array(1) {
["text"]=>
string(10) "index.html"
}
}
}
[2]=>
array(2) {
["text"]=>
string(3) "src"
["children"]=>
array(2) {
[0]=>
array(1) {
["text"]=>
string(8) "index.js"
}
[1]=>
array(2) {
["text"]=>
string(4) "libs"
["children"]=>
array(2) {
[0]=>
array(1) {
["text"]=>
string(9) "jquery.js"
}
[1]=>
array(1) {
["text"]=>
string(8) "melon.js"
}
}
}
}
}
}

Sort object(SimpleXMLElement) php

I'm trying to find a way to sort my array from SimpleXMLElement. I'd like to sort by start time which I can get from event_start_dt. I'd also like to sort by room ID as a separate process. Currently the array is in order by object(SimpleXMLElement) #. Here is the var_dump($array):
object(SimpleXMLElement)#275 (1) {
["reservation"]=> array(3)
{
[0]=> object(SimpleXMLElement)#287 (28) {
["reservation_id"]=> string(7) "8644894"
["event_start_dt"]=> string(25) "2013-12-02T12:00:00-08:00"
["event_end_dt"]=> string(25) "2013-12-02T13:00:00-08:00"
["event_id"]=> string(6) "314147"
["event_name"]=> string(24) "Practice"
["room_id"]=> string(3) "202"
}
[1]=> object(SimpleXMLElement)#288 (28) {
["reservation_id"]=> string(7) "8595185"
["event_start_dt"]=> string(25) "2013-12-02T08:00:00-08:00"
["event_end_dt"]=> string(25) "2013-12-02T09:00:00-08:00"
["event_id"]=> string(6) "314005"
["event_name"]=> string(24) "Meeting"
["room_id"]=> string(3) "207"
}
[2]=> object(SimpleXMLElement)#289 (28) {
["reservation_id"]=> string(7) "8718654"
["event_start_dt"]=> string(25) "2013-12-02T10:00:00-08:00"
["event_end_dt"]=> string(25) "2013-12-02T11:00:00-08:00"
["event_id"]=> string(6) "315811"
["event_name"]=> string(20) "Maintenance"
["room_id"]=> string(3) "202"
}
} }
I've tried usort and asort but haven't gotten it to work with either method.
usort method:
function sortByTime($a, $b){
$a = strtotime($array->event_start_dt);
$b = strtotime($array->event_start_dt);
if ($a==$b) return 0;
return ($a < $b) ?-1 : 1;
}
usort($arrTimes, 'sortByTime');
var_dump($arrTimes);
Trying the code below gives me warning: usort() expects parameter 1 to be array, object given.
foreach ($rez->reservation as $value){
$var1 = $value->space_reservation->space_name;
$var2 = substr($value->event_start_dt,11,5);
}
sort_obj_arr($value,$var1,SORT_DESC);
echo "<pre>SORTED ";
print_r($value);
echo "</pre>";
function sort_obj_arr(& $arr, $sort_field, $sort_direction)
{
$sort_func = function($obj_1, $obj_2) use ($sort_field, &$sort_direction)
{
if ($sort_direction == SORT_ASC) {
return strnatcasecmp($obj_1->$sort_field, $obj_2->$sort_field);
} else {
return strnatcasecmp($obj_2->$sort_field, $obj_1->$sort_field);
}
};
usort($arr, $sort_func);
}
I have an array from my controler but cannot get usort working:
I get either: usort() expects parameter 1 to be array, object given or null.
$array = array($this->data);
print_r($array);
array(1) {
[0]=> object(SimpleXMLElement)#280 (1) { ["reservation"]=> array(3) {
[0]=> object(SimpleXMLElement)#287 (28) {
["reservation_id"]=> string(7) "8644894"
["event_start_dt"]=> string(25) "2013-12-02T12:00:00-08:00"
["event_end_dt"]=> string(25) "2013-12-02T13:00:00-08:00"
["event_id"]=> string(6) "314147"
["event_name"]=> string(24) "Practice"
["room_id"]=> string(3) "202"
}
[1]=> object(SimpleXMLElement)#288 (28) {
["reservation_id"]=> string(7) "8595185"
["event_start_dt"]=> string(25) "2013-12-02T08:00:00-08:00"
["event_end_dt"]=> string(25) "2013-12-02T09:00:00-08:00"
["event_id"]=> string(6) "314005"
["event_name"]=> string(24) "Meeting"
["room_id"]=> string(3) "207"
}
[2]=> object(SimpleXMLElement)#289 (28) {
["reservation_id"]=> string(7) "8718654"
["event_start_dt"]=> string(25) "2013-12-02T10:00:00-08:00"
["event_end_dt"]=> string(25) "2013-12-02T11:00:00-08:00"
["event_id"]=> string(6) "315811"
["event_name"]=> string(20) "Maintenance"
["room_id"]=> string(3) "202"
}
} }
Request for print_r:
SimpleXMLElement Object
(
[reservation] => Array(3)
(
[0] => SimpleXMLElement Object
(
[reservation_id] => 8604174
[event_start_dt] => 2013-12-31T06:00:00-08:00
[event_end_dt] => 2013-12-31T08:00:00-08:00
[event_id] => 314147
[event_name] => Practice
[room_id] => 202
)
[1] => SimpleXMLElement Object
(
[reservation_id] => 8604177
[event_start_dt] => 2013-12-31T05:00:00-08:00
[event_end_dt] => 2013-12-31T06:00:00-08:00
[event_id] => 314150
[event_name] => Meeting
[room_id] => 216
)
[2] => SimpleXMLElement Object
(
[reservation_id] => 8604189
[event_start_dt] => 2013-12-31T10:00:00-08:00
[event_end_dt] => 2013-12-31T11:00:00-08:00
[event_id] => 314150
[event_name] => Maintenance
[room_id] => 220
)
)
)
$arrTimes = xml2array($array->reservation);
var_dump($arrTimes)
array(5) {
["reservation_id"]=> string(7) "8604175"
["event_start_dt"]=> string(25) "2014-01-02T06:00:00-08:00"
["event_end_dt"]=> string(25) "2014-01-02T08:00:00-08:00"
["event_id"]=> string(6) "314147"
["event_name"]=> string(24) "Practice"
}
Use array_multisort
foreach ($rez->reservation as $value)
{
$dateTime[] = $value->event_start_dt;
}
array_multisort($dateTime,SORT_ASC,SORT_STRING,$rez->reservation);
echo "<pre>";
print_r($rez->reservation);
Check it. this is my code
<?php
$myarray=array(
0 => array
(
'dateTime' => '2013-12-02T10:00:00-08:00',
'chanl1' => '20.10',
'chanl2' => '45.4',
'chanl3' => '',
),
1 => array
(
'dateTime' => '2013-12-02T11:00:00-08:00',
'chanl1' => '20.11',
'chanl2' => '45.4',
'chanl3' => '',
),
2 => array
(
'dateTime' => '2013-12-02T12:00:00-08:00',
'chanl1' => '20.12',
'chanl2' => '33.8',
'chanl3' => '',
),
3 => array
(
'dateTime' => '2013-12-02T09:00:00-08:00',
'chanl1' => '20.9',
'chanl2' => '33.9',
'chanl3' => ''
));
foreach($myarray as $c=>$key) {
$dateTime[] = $key['dateTime'];
}
array_multisort($dateTime,SORT_ASC,SORT_STRING,$myarray);
echo "<pre>";
print_r($myarray);
?>
Output is :
Array
(
[0] => Array
(
[dateTime] => 2013-12-02T09:00:00-08:00
[chanl1] => 20.9
[chanl2] => 33.9
[chanl3] =>
)
[1] => Array
(
[dateTime] => 2013-12-02T10:00:00-08:00
[chanl1] => 20.10
[chanl2] => 45.4
[chanl3] =>
)
[2] => Array
(
[dateTime] => 2013-12-02T11:00:00-08:00
[chanl1] => 20.11
[chanl2] => 45.4
[chanl3] =>
)
[3] => Array
(
[dateTime] => 2013-12-02T12:00:00-08:00
[chanl1] => 20.12
[chanl2] => 33.8
[chanl3] =>
)
)
FIDDLE
Before you can sort the data, you need to create an array which contains as its values the separate items you want to sort. From your debug outputs, these are multiple <reservation> nodes in the input XML, which are children of the element represented by $array/$this->data in those samples (it doesn't matter if it's the root of the document or not, SimpleXML has no Document object).
Your print_r and var_dump output shows that you do not currently have such an array, only a SimpleXML object:
Your first example shows var_dump($array) giving output beginning object(SimpleXMLElement)#275 (1) { - ignore the word array further in, that's just how var_dump is rendering the insides of the object.
Later, you have a print_r($array); beginning array(1) { - but this is only because you've wrapped the real data in a single-element array on the line above ($array = array($this->data);) and that one element ($array[0]) shows as object(SimpleXMLElement)#280 (1) { ....
Note that there's no need to go further and convert all the inner SimpleXML objects into arrays - you just need a list that is sortable, containing the items you are interested in. I would personally use a simple and explicit foreach loop, for maximum code readability, although "cleverer" solutions are available.
Once you have a sortable list, you need a callback function for usort which compares its two parameters. The attempt you've made is along the right lines, but refers to the non-existent (in that function) variable $array; the values you need to compare are the function's arguments, which you've called $a and $b - specifically, you want to compare strtotime($a->event_start_dt) with strtotime($b->event_start_dt).
You can also make the function much simpler, because it follows the common misconception that the return value of the callback should be -1, 0, or 1. In fact, it can be any integer, and only its sign matters - returning -42 will have the same effect as returning -999, namely placing item $a before $b in the resulting array.
I can't easily give a tested example, because you haven't provided the underlying XML to reproduce your input (e.g. echo $this->data->asXML();), but the basic approach I would take would be this:
// Start with an empty array, and add all the items we're interested in to it
$sortable_array = array();
// Loop over all <reservation> children of the SimpleXML object $this->data
// See http://php.net/manual/en/simplexml.examples-basic.php
foreach ( $this->data->reservation as $reservation_node )
{
// Add the individual node to our array
$sortable_array[] = $reservation_node;
}
// Now let's sort out the callback function for the sorting
// This could also be an anonymous function passed directly to usort
function sort_callback_event_start($a, $b)
{
// $a and $b are both items in our $sortable_array, and therefore
// <reservation> nodes which we expect to each have a child
// called <event_start_dt>
// If we convert both dates to Unix timestamps, we have two integers
// to compare, and a simple subtraction gives the desired result
// of <0, 0, or >0 as documented at http://php.net/usort
return
strtotime((string)$a->event_start_dt)
-
strtotime((string)$b->event_start_dt);
}
// Now, we have everything we need to do the actual sorting
usort($sortable_array, 'sort_callback_event_start');
// $sortable_array is now sorted as desired! :D
// Note that the items within it are still SimpleXML objects,
// so you still need to access their properties to do something useful
// e.g. some HTML output with the names listed in order of their start date:
echo '<ol>';
foreach ( $sortable_array as $reservation_node )
{
echo '<li>', (string)$reservation_node->event_name, '</li>';
}
echo '</ol>';
I would just cast it as an array using this function (example function from php.net). But note that this will not sort the XML, rather sort the new array
/**
* function xml2array
*
* This function is part of the PHP manual.
*
* The PHP manual text and comments are covered by the Creative Commons
* Attribution 3.0 License, copyright (c) the PHP Documentation Group
*
* #author k dot antczak at livedata dot pl
* #date 2011-04-22 06:08 UTC
* #link http://www.php.net/manual/en/ref.simplexml.php#103617
* #license http://www.php.net/license/index.php#doc-lic
* #license http://creativecommons.org/licenses/by/3.0/
* #license CC-BY-3.0 <http://spdx.org/licenses/CC-BY-3.0>
*/
function xml2array ( $xmlObject, $out = array () )
{
foreach ( (array) $xmlObject as $index => $node )
$out[$index] = ( is_object ( $node ) ) ? xml2array ( $node ) : $node;
return $out;
}
and pass it the XMLObject
$arrTimes = xml2array(YourSimpleXMLElement);
and then use your original usort function on the new array
function sortByTime($a, $b){
$a = strtotime($a['event_start_dt']);
$b = strtotime($b['event_start_dt']);
if ($a==$b)
return 0;
return ($a < $b) ? -1 : 1;
}
Finally
usort($arrTimes, 'sortByTime');
You have to convert frist in to xml to array using json encode decode
$xml_array = json_decode(json_encode((array)$xml), TRUE);
u will get list of array....than u can sory according to date using strtotime function.

Getting an attribute from xml based associative array

I have a xml parser that returns an associative array. Here's the structure:
<addresses id="123">
<anonymous-address> <test>data 1 </test>
</anonymous-address>
</addresses>
print_r($this->params) will return
Array
(
[addresses] => Array
(
[id] => 123
[anonymous-address] => Array
(
[0] => Array
(
[test] => Array
(
[VALUE] => data 1
)))))
This array then gets passed through list() as:
list($root, $data) = $parsedXML;
Then I attempt to get an id attribute from root and read to do it this way:
$id = $data[$root]['id'];
Edit: This works for all but one $root value which it 400's for. This case of the switch ($root) has the same code as the others, so I have no idea why it errs only there.
var_dump($root, $data) outputs
string (9) "addresses"
array(1) {
["addresses"]=>
array(2) {
["id"]=>
string(3) "123"
["anonymous-address"]=>
array(1) {
["VALUE"]=>
string(7) "data 1 "
}
}
}
$a = list($root, $data);
now $a will be:
array(0 => $root, 1 => $data);
so you can get them like this:
$a[0], $a[1]
so probably
$id = $a[1][$a[0]]['id'];

Categories