There must be a simple explanation for this. Unfortunately, it has eluded me for quite some time.

<?php

$campaigns = array(
	'1' => array(
		'name' => 'Campaign 1',
	),
	'2' => array(
		'name' => 'Campaign 2',
	),
	'3' => array(
		'name' => 'Campaign 3',
	),
	'4' => array(
		'name' => 'Campaign 4',
	),
);

var_dump(key($campaigns));

foreach ($campaigns as $id => $data) {
	var_dump($id);
	var_dump(key($campaigns));
}

exit;

This produces the following output (with xdebug enabled):

int 1

int 1

int 2

int 2

int 2

int 3

int 2

int 4

int 2

The first instance of (int) 1 is expected; it's from the var_dump() call before the foreach is executed.

The second instance of (int) 1 is expected, too; it's from "var_dump($id);" (where $id is the key name on the current iteration for the foreach loop).

That said, is anyone able to explain why "var_dump(key($campaigns));" outputs 2 on the first iteration of the foreach loop and every iteration thereafter?

Am I just a complete boob?

    [man]foreach[/man] iterates over a copy of the array it's given, not the original; the iteration pointer of the original array therefore doesn't advance during the loop.

    Why it does advance to 2 on the first iteration.... Each iteration is "yield the current element then fetch the next": fetching the first element has to be done on the original because PHP also has to verify that the original is actually something that has elements that can be fetched. In the process the original's internal pointer gets advanced as per normal operation.

      Thank you for the prompt and thorough response, Weedpacket. It makes perfect sense.

      If you wanted to retrieve the previous and next elements in an associative array, and you know the "target" key name, how would you go about it?

      Is there a better method that something like this?

      //For project navigation ("Next" and "Previous" links).
      
      $campaigns = Portfolio::getCampaign($get['client']);
      
      $prevId = 0;
      $nextId = 0;
      $currentId = 0;
      
      foreach ($campaigns as $id => $data) {
      	if ($id == $campaign['campaignId']) {
      		//Set the current ID, so the loop can terminate on the next iteration.
      		$currentId = $id;
      	}
      	else if ($currentId > 0) {
      		//If the current ID has been set, it's time to capture the next ID and end this loop.
      		$nextId = $id;
      		break;
      	}
      	else {
      		//Keep overwriting the previous ID variable until the current ID is matched.
      		$prevId = $id;
      	}
      }
      

      It should be possible to do this in the database query, too, if that seems preferable.

      Thanks again!

        If you know the target key to begin with, and you want the previous and next keys, then you don't need the loop at all (what are you looping over?)

        $cids = array_keys($campaigns);
        $cid_position = array_search($cids, $campaign['campaignId']);
        if($cid_position === false)
        {
        	// campaign ID not found.
        }
        else
        {
        	$next_cid_position = $cid_position + 1;
        	$prev_cid_position = $cid_position - 1;
        
        if(!isset($cids[$next_cid_position]))
        {
        	// no next campaign.
        }
        else
        {
        	$next_cid = $cids[$next_cid_position];
        	$next_campaign = $campaigns[$next_cid];
        }
        
        if(!isset($cids[$prev_cid_position]))
        {
        	// no previous campaign.
        }
        else
        {
        	$prev_cid = $cids[$prev_cid_position];
        	$prev_campaign = $campaigns[$prev_cid];
        }
        }

        It should be possible to do this in the database query, too, if that seems preferable.

        Relational databases don't have a concept of "next row" or "previous row", which is why you have to specify an ORDER BY clause when you want the results of a SELECT query to be in a particular order. And even when you do that, it only applies to the results of the SELECT, so you can't use it to make the selection in the first place.

          There's nothing wrong with doing it as Weedpacket suggests. However, I prefer to solve this type of problem with a more generic type of paging solution to give me more versatility in how to build my interfaces. Using a framework such as CodeIgniter or Kohana for example, in a function such as:

          $campaigns = Portfolio::getCampaign($get['client']);

          I'd generally include parameters such as $page and $limit. Such as:

          $data= Portfolio::getCampaigns($get['client'],$get['page'],$get['limit']);

          Then, in the Portfolio class' getCampaigns method I'd do something like:

          public function getCampaigns($client_id=null,$page=1,$limit=1){
          
           //we'll want to return the data and a little useful info for paging
           $return= array(
                                   'campaigns'=>array(),
                                   'total_records' => 0,
                                   'total_pages' => 0
                              );
          
           if($client_id !== null){
          
                //find out how many records/pages there are given the current page size
                $query = sprintf("SELECT COUNT(client_id) as count FROM campaigns WHERE client_id=%s",
                                        $this->db->escape($query)
                                      );
                $return['total_records']=$this->db->query($query)->current()->count;
          
                //don't bother querying the rows if there aren't any
                if(!$return['total_records'])return $return;
          
                //calculate total pages
                $return['total_pages'] = ceil($return['total_records']/$limit);
          
                //now before calculating our offset for the paged query, we need to account for dummies 
                //that try to page past beginning or end of data
                if($page > $return['total_pages']){
                     $page = $return['total_pages'];
                }
                if($page < 1)$page=1;
          
                //calculate our offset for the current result set based on the page and the limit
                $offset = ($page-1) * $limit;
          
                //finally, get our page of records
                $query = sprintf("SELECT * FROM campaigns WHERE client_id=%s LIMIT %s OFFSET %s",
                                   $this->db->escape($client_id)),
                                   $this->db->escape($page), 
                                   $this->db->escape($limit)
                              );
                $result = $this->db->query($query);
          
                //assign the records.           
                if($result->count() > 0){
                     $return['campaigns']=$result->result_array();
                }
          
           }
          
           return $return;
          }

          Then in your frontend code, it's easy to construct next/prev links and display info about the current page, how many pages there are, etc. You'd just pass a $page and $limit var in every next/prev link.

          echo "Page $page of {$data['total_pages']}";
          echo "(showing ".($page*$limit)-$limit+1." through ".($page*$limit)." of {$data['total_records'})<br/>";
          if($page > 1){
                echo "<a href='{$_SERVER['PHP_SELF']}?page=".($page-1)."&limit=1' title='previous'>previous</a>";
          }
          
          if($page < $data['total_pages']){
                echo "<a href='{$_SERVER['PHP_SELF']}?page=".($page+1)."&limit=1' title='next'>next</a>";
          }
          

          I just wrote all that off the top of my head, so there may be some typos or other errors, but you get the idea. It maybe overkill for your purposes, but if you set the limit to 1, you get the effect of paging through the records 1 at a time like you're trying to do and if you ever want to add interface elements to let people choose their page size arbitrarily you then have that option.

            Write a Reply...