I have an ugly piece of code, but for the life of me I can't figure out how to rework it. Mainly I want to lose the "eval", but the implode doesn't seem good either. It does what I want, which is dynamically assign an array location like $array[x][y][z] where the number of parameters are unknown (i.e. $array[x][y][z][a] or $array[x][y]) here is the concept:

$path_as_array = array('0', '1', '2'); //the unknown array keys
$keys = implode( '][', $path_as_array );
$eval_str = '$array[' . $keys . '] = $value;'; //assigns $value to $array['0']['1']['2']
eval( $eval_str );

aside from possible typos it works, but is there an easier way?

    This recursive function should make the job:

    function assign_rec($path_as_array, $value) {
    	if (count($path_as_array) == 0) {
    		return $value;
    	} else {
    		$array = array();
    		$key = array_shift($path_as_array);
    		$array[$key] = assign_rec($path_as_array, $value);
    		return $array;
    	}
    }
    
    print_r(assign_rec($path_as_array, $value));
      function assign_path(&$array, $path, $value)
      {
      	$walk = &$array;
      	foreach($path as $key)
      	{
      		if(!isset($walk[$key]) || !is_array($walk[$key]))
      			$walk[$key] = array();
      		$walk = &$walk[$key];
      	}
      	$walk = $value;
      }
      
      $array = array();
      $path1 = array('a','b','d');
      $path2 = array('a','e','f');
      $path3 = array('a','b','e','e','g');
      $path4 = array('g','e','e','b','a');
      $path5 = array('g','e','e');
      assign_path($array, $path1, 12);
      assign_path($array, $path2, 34);
      assign_path($array, $path3, 56);
      assign_path($array, $path4, 78);
      assign_path($array, $path5, 90);
      
      print_r($array);

      There is of course the issue of what happens if you try to assign a value to both $array['foo'] and $array['foo']['bar'] - in this case later assignments overwrite earlier ones. An alternative would be to have the first value stored in $array['foo'][0]. This assumes that the path indices aren't themselves numeric, but does make for simpler code:

      function assign_path(&$array, $path, $value)
      {
      	$new = array();
      	$walk = &$new;
      	foreach($path as $key)
      	{
      		$walk[$key] = array();
      		$walk = &$walk[$key];
      	}
      	$walk = $value;
      	$array = array_merge_recursive($array, $new);
      }
      
        weedpacket wrote:

        ...There is of course the issue of what happens if you try to assign a value to both $array['foo'] and $array['foo']['bar'] - in this case later assignments overwrite earlier ones....

        This was the issue I saw with a couple approaches I thought of.

        Perhaps this is all an indication that some thought should go into refactoring the program design that has led us into this situation to begin with?

          Thanks for your input guys, I don't think either of these will do exactly what I need though. To put it in context, here is a scaled down version of the class and an example piece of html to put in it. Forgive my lazy coding shortcuts (like defining , so I don't have to quote it). It is just a simple html parser, so that I can manipulate a html dom with php (say curl in a page and alter a div). I think it is elegant, except the aforementioned eval lines. I left out the meat of it, but this condenses an html string to an array with tags at '' and attributes at '@'

          class pxhtml
          {
              public static $tag_stack;
              public static $pdom;
              public static $iteration;
          
          public static $depth = 0; //current dom depth
          public static $last_tag_closed; // for debugging
          
          public function __construct( $data = '' )
          {
              define('tag', '|\<([\/]*)(\w+)([^\/\>]*)([/]*)\>([^<]*)|'); //preg exp
              define('attributes', '|(\w+)[ ]*\=[ ]*["]*(\w+)["]*|x'); //preg exp
              define('_', '_'); //just for less typing
          
              $this->iteration = array( );  // iterations by depth
              $this->pdom = array( );  // the html dom
              $this->tag_stack = array( ); // a list of open tags
          
              if( is_string( $data ) && $data != '' )
              {
                  $this->process_html( $data );
              }
              return $this->pdom;
          }
          
          public function process_atts( $string )
          {//turns attributes string into an array
              $array = array();
              preg_match_all( attributes, $string, $atts_array, PREG_SET_ORDER );
              $return_array = array();
              foreach($atts_array as $a)
              {
                  $return_array[$a[1]] = $a[2];
              }
              return $return_array;
          }
          
          public function process_html( $string )
          {//tag by tag processing of string
              $elements = array();
              //find all the tags and surrounding text
              preg_match_all( tag, $string, $elements, PREG_SET_ORDER );
              foreach( $elements as $element )
              {
                  $this->pdom_append( $element ); 
              }
          }
          
          private function pdom_append( $element )
          { // determines tag type and organizes the structure
              list( $full_tag, $closing_tag, $tag, $atts, $empty_tag, $text ) = $element;
              if( !$closing_tag & !$empty_tag )
              { // opening tag
                  $this->open_tag( $tag, $atts, $text );
              }
              elseif ( $closing_tag )
              { // closing tag
                  $this->close_tag( $tag, $text );
              }
              elseif ( $empty_tag )
              { // empty tag
                  $this->empty_tag( $tag, $atts, $text );
              }
          }
          
          private function open_tag( $tag, $atts = '', $text = '' )
          { // adds an open tag, attributes, and text node to the dom
              $this->tag_stack[] = $tag;
              $this->depth = count( $this->tag_stack ) - 1;
              $this->iteration[$this->depth] = ( isset( $this->iteration[$this->depth] ) ) ? $this->iteration[$this->depth] + 1 : 0;
              $this->depth++;
              $this->set( array( $tag, $atts, $text ) );
              $this->iteration[$this->depth] = 0;
          }
          
          private function close_tag( $text = '' )
          { // close out the last tag
          
              $this->last_tag_closed = array_pop( $this->tag_stack );
              array_pop( $this->iteration );
              //unset( $this->iteration[$this->depth] );
              $this->depth = count( $this->tag_stack );
              if( trim( $text ) != '' )
              {
                  $this->iteration[$this->depth]++;
                  $this->add_text( $text );
              }
          }
          
          private function empty_tag( $tag, $atts = '', $text = '' )
          { // open and close tag, needs work still
              $this->open_tag( $tag, $atts, '<!-- empty tag -->' );
              $this->close_tag( $tag, $text );
          }
          
          private function set( $array )
          {// set the current pdom node
              $keys = implode( '][', $this->iteration );
              $eval_str = '$this->pdom[' . $keys . '] = array( _ => $array[0] ); ';
              if( trim( $array[1] ) != '' )
              {
                  $eval_str .= '$this->pdom[' . $keys . '][\'@\'] = $this->process_atts( $array[1] ); ';
              }        
              if( trim( $array[2] ) != '' )
              {
                  $this->iteration[$this->depth] = 0;
                  $eval_str .= '$this->pdom[' . $keys . '][0] = $array[2]; '; 
              }
              eval( $eval_str );
          }
          
          private function add_text( $text )
          { // add a text node at current pdom node
              $keys = implode( '][', $this->iteration );
              $this->iteration[$this->depth] = ( isset( $this->iteration[$this->depth] ) ) ? $this->iteration[$this->depth] + 1 : 0;
              $eval_str .= '$this->pdom[' . $keys . '] = $text; ';
              eval( $eval_str );
          }
          
          
          
          }

          &#91;php]...[/code] tags added by moderator

            Um, PHP's [man]DOM[/man] extension can already parse and assemble HTML....

              4 days later

              I know, don't reinvent the wheel. I knew about the dom extension, but I wanted more functionality in some places (parse style sheets w/ relational tag tables, parse js scripts) and less in others (the dom ext feels clunky).

              I use the full class for generating pages, just to cut down on typing, so short and sweet is everything for the ui. Not for on-the-fly gen yet, as it still feels a bit unsafe.

              Anyhow, all other arguments aside, this really became a "proof of concept" problem. The ability to manipulate an array in this way could be very useful, and I wanted to know if a construct existed to approximate it. Eval makes me feel dirty.

              If anyone knows of one, please post it.

                All I can say for this is, "It doesn't use eval()." I'll have to think about it more to decide if it's any more secure than eval(), and I doubt either performs meaningfully better than the other.

                function addArrayElement(&$array, $keys, $value)
                {
                   $keyString = "['" . implode("']['", $keys) . "']";
                   $func = create_function('&$arr,$val', '$arr'.$keyString.'=$val;');
                   $func($array, $value);
                }
                // TEST:
                $test = array('one', array('two', 'three'));
                echo "<pre>".print_r($test,1)."</pre>\n";
                addArrayElement($test, array(1,2,3), 'Did it work?');
                echo "<pre>".print_r($test,1)."</pre>\n";
                

                  Sorry if I've missed the boat here - but is this right?

                  <?php
                  $keys = array(1, 'two', 3, 9);
                  $value = 'fishcake';
                  
                  $array = array();
                  $target =& $array;
                  
                  foreach ($keys as $k)
                  {
                  	$target =& $target[$k];
                  }
                  
                  $target = $value;
                  
                  echo 'ARRAY:<pre>'.print_r($array, 1).'</pre>';
                  
                  $keys = array(1, 'two', 'buckle', 'shoe');
                  $value = 'spanner';
                  $target =& $array;
                  
                  foreach ($keys as $k)
                  {
                  	$target =& $target[$k];
                  }
                  
                  $target = $value;
                  
                  echo 'ARRAY:<pre>'.print_r($array, 1).'</pre>';
                  ?>

                    I think we have a winner(Drakla)!

                    I can't thoroughly test this right now(at work) but it seems to do what i want it to!

                    wow, in retrospect.. so simple.. sometimes I think my brain gets stuck in an infinite loop.

                    Thank you everyone for your help. -- I'll post again soon to confirm that the thread is in fact resolved completely.

                    -- can you believe I've been doing this for nearly a decade now?

                      I get a fatal error if I pre-populate the array with some data:

                      Fatal error: Cannot create references to/from string offsets nor overloaded objects in C:\wamp\www\test\test.php on line 11

                      Test code:

                      <?php
                      $array = array(1 => array('two' => "Don't overwrite me."));
                      
                      $keys = array(1, 'two', 3, 9);
                      $value = 'fishcake';
                      
                      $target =& $array;
                      
                      foreach ($keys as $k)
                      {
                          $target =& $target[$k];
                      }
                      
                      $target = $value;
                      
                      echo 'ARRAY:<pre>'.print_r($array, 1).'</pre>';
                      
                      $keys = array(1, 'two', 'buckle', 'shoe');
                      $value = 'spanner';
                      $target =& $array;
                      
                      foreach ($keys as $k)
                      {
                          $target =& $target[$k];
                      }
                      
                      $target = $value;
                      
                      echo 'ARRAY:<pre>'.print_r($array, 1).'</pre>';
                      ?>
                      

                        Hmmm... well it can be patched by doing this, but I'd need to know more about the problem

                        foreach ($keys as $k)
                        {
                        	if (isset($target[$k]) && !is_array($target[$k]))
                        	{
                        		$target[$k] = (array)$target[$k];
                        	}
                        
                        $target =& $target[$k];
                        }

                        That will put the string into index zero, which might not be the right thing to do. If you use the keys 1, two, 0, 0 it will be overwritten, so we need to know what strategy is required.

                          It took me a little bit to wrap my head around this one, but it looks like it is only doing this when the array navigation is blocked by a string. I'm not sure this is a desired behavior, but the only alternative is to unset every string along the way to make sure the array tree is valid, it seems.

                          One thing I did not think about, in the eval code, you could pass in an empty string as the key, and of course it would read "[]" and increment the array. Unfortunately, passing in "" as a key evaluates to NULL and sets the array at NULL.

                          -- Honestly, who new you could set [""]? Haven't figured out how to access the data you put in there yet, though.

                            You don't have to unset the string, if you want to keep the value and it's position to have some relevance then that branch has to be converted to an array and the value placed somewhere within that - what key to use? Well that's up to you.

                            Don't know if this will be of any use but your problem might be a bit like an XML parser I wrote which converts values to arrays if they're going to stack.

                            	/**
                            	*	Class to parse xml into an array the way I like it.
                            	*
                            	*	@version 0.0.3
                            	*/
                            
                            class XMLThing
                            {
                            	    /** @var string xml passed during constructor */
                                var $rawXML;
                            		/** @var array values array filled by xml_parse_into_struct */
                                var $valueArray = array();
                            		/** @var array key array filled by xml_parse_into_struct */
                                var $keyArray = array();
                            
                                /** @var array parsed data */
                            var $parsed = array();
                            	/** @var int counter used while parsing */
                            var $index = 0;
                            
                            	/** @var string what the key will be for any attributes found */
                            var $attribKey = 'attributes';
                            	/** @var string name used when translating complete tags with attribs to an attrib/value array */
                            var $valueKey = 'value';
                            	/** @var string name for the key used when adding cdata */
                            var $cdataKey = 'cdata';
                            
                            	/** @var string processing status */
                            var $status;
                            	/** @var bool whether an error occured */
                            var $isError = false;
                            	/** @var string error description */
                            var $error = '';
                            
                            
                            	/**
                            	*	Constructor
                            	*	@param string xml data to parse - optional
                            	*/
                            
                            function XMLThing($xml = NULL)
                            {
                                $this->rawXML = $xml;
                            }
                            
                            
                            	/**
                            	*	Turns xml into a lovely array
                            	*	@param string xml to parse - optional.  That supplied to constructor used otherwise.
                            	*	@return array parsed data
                            	*/
                            
                            function parse($xml = NULL)
                            {
                            	if (!is_null($xml))
                            	{
                            		$this->rawXML = $xml;
                            	}
                            
                            	$this->status = 'parsing uninitialised';
                            	$this->isError = false;
                            		// setup
                            	if (!$this->parse_init())
                            	{
                            		$this->status = 'parsing init fail';
                            		return false;
                            	}
                            
                            	$this->index = 0;
                            	$this->parsed = $this->parse_recurse();
                            	$this->status = 'parsing complete';
                            
                            	return $this->parsed;
                            }
                            
                            
                            	/**
                            	*	Turns the struct array into a nested one
                            	*	@return array
                            	*/
                            
                            function parse_recurse()
                            {		// data found at this level
                            	$found = array();
                            		// count of the number of times a tag has been found at this level
                            	$tagCount = array();
                            
                            		// loop through the tags - I use a lazy referencing thing for where data should go
                            
                            	while (isset($this->valueArray[$this->index]))
                            	{
                            		$tag = $this->valueArray[$this->index];
                            		$this->index++;
                            
                            			// if it's a close then return straight away
                            
                            		if ($tag['type'] == 'close')
                            		{
                            			return $found;
                            		}
                            			// if it's cdata translate it as a complete tag named $this->cdataKey
                            		if ($tag['type'] == 'cdata')
                            		{
                            			$tag['tag'] = $this->cdataKey;
                            			$tag['type'] = 'complete';
                            		}
                            
                            		$tagName = $tag['tag'];
                            
                            			// this bit below creates a reference to where the data should be going,
                            			// it saves on some conditions in the switch, but it probably annoyingly obfuscating
                            			// has this tag already appeared at this level ?  If so we need mods
                            
                            		if (isset($tagCount[$tagName]))
                            		{		// transform to an array IF only one of the tags been before
                            			if ($tagCount[$tagName] == 1)
                            			{
                            				$found[$tagName] = array($found[$tagName]);
                            			}
                            				//$found[$tagName][$tagCount[$tagName]] = '';	// doesn't need to happen, the reference below is enough
                            			$tagRef =& $found[$tagName][$tagCount[$tagName]];
                            			$tagCount[$tagName]++;
                            		}
                            		else	// just use the variable
                            		{
                            			$tagCount[$tagName] = 1;
                            			$tagRef =& $found[$tagName];
                            		}
                            
                            			// now do the work
                            
                            		switch ($tag['type'])
                            		{
                            			case 'open':
                            				$tagRef = $this->parse_recurse();
                            
                            				if (isset($tag['attributes']))
                            				{
                            					$tagRef[$this->attribKey] = $tag['attributes'];
                            				}
                            					// open CAN have a value - it makes sense to include it as cdata
                            				if (isset($tag['value']))
                            				{
                            					if (isset($tagRef[$this->cdataKey]))	// push it to the start of the cdata array if it's present
                            					{
                            						$tagRef[$this->cdataKey] = (array)$tagRef[$this->cdataKey];	// <-- needed for php5 compatibility];
                            						array_unshift($tagRef[$this->cdataKey], $tag['value']);
                            					}
                            					else
                            					{
                            						$tagRef[$this->cdataKey] = $tag['value'];
                            					}
                            				}
                            				break;
                            
                            			case 'complete':
                            				if (isset($tag['attributes']))
                            				{
                            					$tagRef[$this->attribKey] = $tag['attributes'];
                            					$tagRef =& $tagRef[$this->valueKey];
                            				}
                            
                            				if (isset($tag['value']))
                            				{
                            					$tagRef = $tag['value'];
                            				}
                            				break;
                            		}
                            	}
                            
                            	return $found;
                            }
                            
                            
                            	/**
                            	*	Turns the xml into the array pairs with xml_parse_into_struct
                            	*	@return bool
                            	*/
                            
                            function parse_init()
                            {
                                $parser = xml_parser_create();
                            
                                xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); 	// Dont mess with my cAsE sEtTings
                                xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);		// Dont bother with empty info
                            
                                if (!$res = (bool)xml_parse_into_struct($parser, $this->rawXML, $this->valueArray, $this->keyArray))
                            	{
                            		$this->isError = true;
                                    $this->error = 'error: '.xml_error_string(xml_get_error_code($parser)).' at line '.xml_get_current_line_number($parser);
                                }
                            
                                xml_parser_free($parser);
                            
                            	return $res;
                            }
                            }	// *** CLASS ENDS *** //
                              Drakla wrote:
                              foreach ($keys as $k)
                              {
                                  if (isset($target[$k]) && !is_array($target[$k]))
                                  {
                                      $target[$k] = (array)$target[$k];
                                  }
                              
                              $target =& $target[$k];
                              } 

                              Gee, that's starting to look familiar 😃

                                Drakla wrote:

                                Why's that then?

                                See my first post in this thread. Modulo what to do with collisions.

                                  Write a Reply...