Processing Multi-Dimensional array into existing object

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
User avatar
Weiry
Forum Contributor
Posts: 323
Joined: Wed Sep 09, 2009 5:55 am
Location: Australia

Processing Multi-Dimensional array into existing object

Post by Weiry »

Hi All,

Have another interesting conundrum which involves JSON, arrays and objects.

I am making use of a PHP library which helps to render JavaScript graphs.
This library you can set a large number of settings for the graph itself by means of the following format:

Code: Select all

$chart->chart->renderTo = 'linechart';
$chart->title->text = 'Example chart';
$chart->yAxis->title->text = 'Total';
$chart->addSeries( $series1 );
The available variables to set are quite large in number, so i was thinking about storing all the information required to set up one of these graphs using JSON potentially storing everything as an array which parses nicely into a JSON format.

The problem though, is retrieving and setting all of this information without having to explicitly define everything after the JSON has been retrieved.

Now i did experiment with converting the original array into an object after being retrieved, however for ease, i decided not to go down this path due to issues accessing arrays at the end of the tree which were being converted into objects. Followed by the issue that if i did manage to keep the *actual* arrays stored correctly, they were difficult to access where you had to access them via $obj->myvariable->{1} which wasn't ideal.

Code: Select all

function arrayToObject( $array )
{
	if ( !is_array( $array ) ) {
		return $array;
	}

	$object = new stdClass();
	if ( is_array( $array ) && count( $array ) > 0 ) {
		foreach ( $array as $name => $value ) {
			$name = strtolower( trim( $name ) );
			if ( !empty( $name ) ) {
				if(($name+0) > 0){
					$object->$name = $value;
				}else{
					$object->$name = arrayToObject( $value );
				}
			}
		}
		return $object;
	} else {
		return FALSE;
	}
}
So in the end i opted to just keep everything as an array as i thought it would be easier in the long term to handle.

So the problem i am facing is, How do you recursively add array data into an object as object variables?
If we take the example above:

Code: Select all

$chart->chart->renderTo = 'graphDiv';
$chart->title->text = 'Example chart';
$chart->yAxis->title->text = 'Total';
The equivalent array structure would be:

Code: Select all

$chart = array(
  'chart' => array(
    'renderTo ' => 'graphDiv'
  ),
  'title' => array(
    'text' => 'Example Chart'
  ),
  'yAxis' => array(
    'title' => 'Total'
  )
);
Initially i thought simply looping through the array would be good enough to start setting variables until you got to the second level of variables.

Code: Select all

foreach ( $settings as $name => $value ){
  $chart->$name = ??
}
If you try to palm $value off to a method that goes and does the same sort of thing as the above code, i'm not sure you would ever get the structure of

Code: Select all

$chart->$tier1->$tier2->$tier3 //...
Not only that, but you cant simply do something like:

Code: Select all

$chart->chart = $settings['chart'];
Because the contents of $chart->chart is not an array, so it cant be assigned as such.

So my question is, what would be the easiest method of assigning multiple levels of variables in this case?
My feeling is that there may be a way using stdClass or something.

PS Here is the full PHP array and the full list of possible assignments that i know of.
PHP Equivalent Array:

Code: Select all

$settings = array(
	'title' => new Settings(
		'title', array(
			'text' => 'Most Popular Topics',
			'align' => 'left',
			'floating' => true,
			'style' => array(
				'font' => '18px Metrophobic, Arial, sans-serif',
				'color' => '#0099ff'
			),
			'x' => 20,
			'y' => 20
		)
	),
	'chart' => array(
		"renderTo" => "linechart",
		"width" => 500,
		"height" => 300,
		"marginTop" => 60,
		"marginLeft" => 90,
		"marginRight" => 30,
		"marginBottom" => 110,
		"spacingRight" => 10,
		"spacingBottom" => 15,
		"spacingLeft" => 0,
		"backgroundColor" => array(
			"linearGradient" => array(0,0,0,300),
			"stops" => array(
				array(0,'rgb(217, 217, 217)'),array(1,'rgb(255, 255, 255)')
			)
		),
		"alignTicks" => false
	),
	"legend" => array(
		"enabled" => true,
		"layout" => 'horizontal',
		"align" => 'center',
		"verticalAlign" => 'bottom',
		"itemStyle" => array(
			'color' => '#222'
		),
		"backgroundColor" => array(
		
		)
	),
	"tooltip" => array(
		"formatter" => 'Formatter', //class reference
		"backgroundColor" => array(
			"linearGradient" => array(0,0,0,50),
			"stops" => array(array(0,'rgb(217, 217, 217)'),array(1,'rgb(255, 255, 255)'))
		)
	),
	"plotOptions" => array(
		"line" => array(
			"pointStart" => (strtotime('-30 day') * 1000),
			"pointInterval" => (24 * 3600 * 1000)
		)
	),
	"xAxis" => array(
		"type" => 'datetime',
		"tickInterval" => 'plotOptions.line.pointInterval', //settings reference
		"startOnTick" => true,
		"tickmarkPlacement" => 'on',
		"tickLength" => 10,
		"minorTickLength" => 5,
		"labels" => array(
			"align" => 'right',
			"step" => 2,
			"rotation" => -35,
			"x" => 5,
			"y" => 20
		),
		"dataLabels" => array(
			"formatter" => 'Formatter' //class reference
		)
	),
	"yAxis" => array(
		"labels" => array(
			"formatter" => 'Formatter' //class reference
		),
		"min" => 0,
		"maxPadding" => 0.2,
		"endOnTick" => true,
		"minorGridLineWidth" => 0,
		"minorTickInterval" => 'auto',
		"minorTickLength" => 1,
		"tickLength" => 2,
		"minorTickWidth" => 1,
		"title" => array(
			"text" => 'Pageviews',
			"align" => 'high',
			"style" => array(
				"font" => '14px Metrophobic, Arial, sans-serif'
			),
			"rotation" => 0,
			"x" => 60,
			"y" => -10,
		),
		"plotLines" => array(
			array('color' => '#808080', 'width' => 1, 'value' => 0 )
		)
	)

);
Assignments:

Code: Select all

$linechart->title->text = "Most Popular Topics";
$linechart->title->align = "left";
$linechart->title->floating = true;
$linechart->title->style->font = '18px Metrophobic, Arial, sans-serif';
$linechart->title->style->color = '#0099ff';
$linechart->title->x = 20;
$linechart->title->y = 20;
$linechart->chart->renderTo = 'linechart';
$linechart->chart->width = 500;
$linechart->chart->height = 300;
$linechart->chart->marginTop = 60;
$linechart->chart->marginLeft = 90;
$linechart->chart->marginRight = 30;
$linechart->chart->marginBottom = 110;
$linechart->chart->spacingRight = 10;
$linechart->chart->spacingBottom = 15;
$linechart->chart->spacingLeft = 0;
$linechart->chart->backgroundColor->linearGradient = array(0,0,0,300);
$linechart->chart->backgroundColor->stops = array(array(0,'rgb(217, 217, 217)'),array(1,'rgb(255, 255, 255)'));
$linechart->chart->alignTicks = false;
$linechart->legend->enabled = true;
$linechart->legend->layout = 'horizontal';
$linechart->legend->align = 'center';
$linechart->legend->verticalAlign = 'bottom';
$linechart->legend->itemStyle = array('color' => '#222');
$linechart->legend->backgroundColor->linearGradient = array(0,0,0,25);
$linechart->legend->backgroundColor->stops = array(array(0,'rgb(217, 217, 217)'),array(1,'rgb(255, 255, 255)'));
$linechart->tooltip->formatter = new HighRollerFormatter(); // TOOLTIP FORMATTER
$linechart->tooltip->backgroundColor->linearGradient = array(0,0,0,50);
$linechart->tooltip->backgroundColor->stops = array(array(0,'rgb(217, 217, 217)'),array(1,'rgb(255, 255, 255)'));
$linechart->plotOptions->line->pointStart = strtotime('-30 day') * 1000;
$linechart->plotOptions->line->pointInterval = 24 * 3600 * 1000; // one day
$linechart->xAxis->type = 'datetime';
$linechart->xAxis->tickInterval = $linechart->plotOptions->line->pointInterval;
$linechart->xAxis->startOnTick = true;
$linechart->xAxis->tickmarkPlacement = 'on';
$linechart->xAxis->tickLength = 10;
$linechart->xAxis->minorTickLength = 5;
$linechart->xAxis->labels->align = 'right';
$linechart->xAxis->labels->step = 2;
$linechart->xAxis->labels->rotation = -35;
$linechart->xAxis->labels->x = 5;
$linechart->xAxis->labels->y = 20;
$linechart->xAxis->dataLabels->formatter = new Formatter();
$linechart->yAxis->labels->formatter = new Formatter();
$linechart->yAxis->min = 0;
$linechart->yAxis->maxPadding = 0.2;
$linechart->yAxis->endOnTick = true;
$linechart->yAxis->minorGridLineWidth = 0;
$linechart->yAxis->minorTickInterval = 'auto';
$linechart->yAxis->minorTickLength = 1;
$linechart->yAxis->tickLength = 2;
$linechart->yAxis->minorTickWidth = 1;
$linechart->yAxis->title->text = 'Pageviews';
$linechart->yAxis->title->align = 'high';
$linechart->yAxis->title->style->font = '14px Metrophobic, Arial, sans-serif';
$linechart->yAxis->title->rotation = 0;
$linechart->yAxis->title->x = 60  ;
$linechart->yAxis->title->y = -10;
$linechart->yAxis->plotLines = array( array('color' => '#808080', 'width' => 1, 'value' => 0 ));
cheers

Weiry.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Processing Multi-Dimensional array into existing object

Post by Christopher »

I think you are either going to need a recursive function or use eval(). I would recommend recursion. Walk each array. If the node is an object or array then add an object property to $linechart. If it is a scalar then add a property and assign the value. You need to pass references into the array and object hierarchy to the recursive function so it deals with the current node.
(#10850)
User avatar
Weiry
Forum Contributor
Posts: 323
Joined: Wed Sep 09, 2009 5:55 am
Location: Australia

Re: Processing Multi-Dimensional array into existing object

Post by Weiry »

Well i was able to solve the problem using a modified version of the original function i posted!

Code: Select all

function arrayToObject( $array )
{
	if ( !is_array( $array ) ) {
		return $array;
	}

	$object = new stdClass();
	if ( is_array( $array ) && count( $array ) > 0 ) {
		foreach ( $array as $name => $value ) {
			

			$name = trim( $name );
			if ( !empty( $name ) ) {

				if(($name+0) > 0){
					//$object->$name = $value;
				}else{
				/*
				 * If the item was defined as an array, we need to keep
				 * this type rather than putting it into an object so that
				 * it can be returned correctly
				 */
					if($name == "array"){
						return $value;
					}else{
						$object->$name = arrayToObject( $value );
					}
				}
			}
		}
		return $object;
	} else {
		return FALSE;
	}
}
I had to modify the array structure a little in order to accommodate the crude but effective definitions for arrays within the the actual storage array.
Example:

Code: Select all

	"tooltip" => array(
		"backgroundColor" => array(
			"linearGradient" => array(
				"array" => array(array(0,0,0,50))
			),
			"stops" => array(
				"array" => array(array(0,'rgb(217, 217, 217)'),array(1,'rgb(255, 255, 255)'))
			)
		)
	),
Basically the solution was to wrap the value that was supposed to BE an array, inside an array and define the key as "array" as well in order to allow the recursive function to pick up the fact that it shouldn't attempt to convert this particular array into an stdClass object and return it as an actual array instead.

In the end, because everything was using stdClass even in the library, i was simply able to assign the whole objects into the required section (which i didn't think i would be able to do until i did a var_dump on the libraries main object with a few settings assigned).

Code: Select all

$newSettings = arrayToObject($newGraph['settings']);
$chart = new LineChart();
$chart->chart = $newSettings->chart;
$chart->title = $newSettings->title;
$chart->yAxis = $newSettings->yAxis;
$chart->xAxis = $newSettings->xAxis;
$chart->plotOptions = $newSettings->plotOptions;
$chart->tooltip = $newSettings->tooltip;
$chart->legend = $newSettings->legend;
Solved :D
Post Reply